Repository: burrunan/gradle-cache-action Branch: main Commit: 719a4e72b21e Files: 124 Total size: 365.4 KB Directory structure: gitextract_dd0zirf7/ ├── .editorconfig ├── .gitattributes ├── .github/ │ └── workflows/ │ ├── check-action-typing.yml │ ├── gradle-wrapper-validation.yml │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── NOTICE ├── README.md ├── action-types.yml ├── action.yml ├── build.gradle.kts ├── cache-action-entrypoint/ │ ├── build.gradle.kts │ ├── src/ │ │ ├── jsMain/ │ │ │ └── kotlin/ │ │ │ ├── main.kt │ │ │ └── stringArgv.kt │ │ └── jsTest/ │ │ └── kotlin/ │ │ └── com/ │ │ └── github/ │ │ └── burrunan/ │ │ ├── ArgumentsTest.kt │ │ └── SplitLinesTest.kt │ └── webpack.config.d/ │ └── config.js ├── cache-proxy/ │ ├── build.gradle.kts │ └── src/ │ ├── jsMain/ │ │ └── kotlin/ │ │ └── com/ │ │ └── github/ │ │ └── burrunan/ │ │ └── gradle/ │ │ └── proxy/ │ │ └── CacheProxy.kt │ └── jsTest/ │ └── kotlin/ │ └── com/ │ └── github/ │ └── burrunan/ │ └── gradle/ │ └── proxy/ │ └── CacheProxyTest.kt ├── cache-service-mock/ │ ├── build.gradle.kts │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── com/ │ └── github/ │ └── burrunan/ │ └── gradle/ │ └── cache/ │ ├── CacheService.kt │ ├── CacheStorage.kt │ ├── HttpException.kt │ └── HttpExtensions.kt ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle-launcher/ │ ├── build.gradle.kts │ └── src/ │ ├── jsMain/ │ │ └── kotlin/ │ │ └── com/ │ │ └── github/ │ │ └── burrunan/ │ │ └── launcher/ │ │ ├── GradleDistribution.kt │ │ ├── GradleInstaller.kt │ │ ├── GradleLauncher.kt │ │ ├── GradleVersion.kt │ │ ├── GradleVersionResponse.kt │ │ ├── LaunchParams.kt │ │ └── internal/ │ │ ├── GradleError.kt │ │ ├── GradleErrorCollector.kt │ │ └── GradleOutErrorCollector.kt │ └── jsTest/ │ └── kotlin/ │ └── com/ │ └── github/ │ └── burrunan/ │ └── launcher/ │ ├── PropertiesParserTest.kt │ ├── RetrieveGradleVersionTest.kt │ └── internal/ │ ├── GradleErrorCollectorTest.kt │ └── GradleOutCollectorTest.kt ├── gradle.properties ├── gradlew ├── gradlew.bat ├── hashing/ │ ├── build.gradle.kts │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── com/ │ └── github/ │ └── burrunan/ │ └── hashing/ │ ├── HashDetails.kt │ ├── diff.kt │ └── hashFiles.kt ├── layered-cache/ │ ├── build.gradle.kts │ └── src/ │ ├── jsMain/ │ │ └── kotlin/ │ │ └── com/ │ │ └── github/ │ │ └── burrunan/ │ │ └── gradle/ │ │ ├── GradleCacheAction.kt │ │ ├── Parameters.kt │ │ ├── cache/ │ │ │ ├── ActionsTriggerExtensions.kt │ │ │ ├── Cache.kt │ │ │ ├── CompositeCache.kt │ │ │ ├── DefaultCache.kt │ │ │ ├── GradleGeneratedJarsCache.kt │ │ │ ├── LayeredCache.kt │ │ │ ├── MetadataFile.kt │ │ │ ├── dependenciesCache.kt │ │ │ └── localBuildCache.kt │ │ └── github/ │ │ └── StateExtensions.kt │ └── jsTest/ │ ├── kotlin/ │ │ └── com/ │ │ └── github/ │ │ └── burrunan/ │ │ └── gradle/ │ │ ├── CacheServerTest.kt │ │ └── GlobTest.kt │ └── resources/ │ └── readme.txt ├── renovate.json ├── settings.gradle.kts ├── test-library/ │ ├── build.gradle.kts │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── com/ │ └── github/ │ └── burrunan/ │ └── test/ │ └── testExtensions.kt └── wrappers/ ├── actions-cache/ │ ├── build.gradle.kts │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── actions/ │ └── cache/ │ ├── CacheExtensions.kt │ ├── RestoreType.kt │ ├── internal/ │ │ ├── CacheContract.kt │ │ └── httpclient.kt │ └── types.kt ├── actions-toolkit/ │ ├── build.gradle.kts │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── actions/ │ ├── core/ │ │ ├── ActionFailedException.kt │ │ ├── ActionStage.kt │ │ ├── ActionsEnvironment.kt │ │ ├── LogLevel.kt │ │ ├── LoggingExtensions.kt │ │ └── ext/ │ │ ├── Group.kt │ │ └── InputExtensions.kt │ ├── exec/ │ │ └── ExecExtensions.kt │ └── glob/ │ └── removeFiles.kt ├── java-properties/ │ ├── build.gradle.kts │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── javaproperties/ │ ├── index.module_java-properties.kt │ └── parseString.kt ├── js/ │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── com/ │ └── github/ │ └── burrunan/ │ ├── formatBytes.kt │ └── wrappers/ │ └── js/ │ └── SuspendExtensions.kt ├── nodejs/ │ ├── build.gradle.kts │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── com/ │ └── github/ │ └── burrunan/ │ └── wrappers/ │ └── nodejs/ │ ├── FsExtensions.kt │ └── StreamExtensions.kt ├── octokit-request-error/ │ ├── build.gradle.kts │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── octokit/ │ └── requesterror/ │ ├── index.module_@octokit_request-error.kt │ └── types.module_@octokit_request-error.kt ├── octokit-types/ │ ├── build.gradle.kts │ └── src/ │ └── jsMain/ │ └── kotlin/ │ └── octokit/ │ └── types/ │ ├── AuthInterface.module_@octokit_types.kt │ ├── EndpointDefaults.module_@octokit_types.kt │ ├── EndpointInterface.module_@octokit_types.kt │ ├── EndpointOptions.module_@octokit_types.kt │ ├── Fetch.module_@octokit_types.kt │ ├── GetResponseTypeFromEndpointMethod.module_@octokit_types.kt │ ├── OctokitResponse.module_@octokit_types.kt │ ├── RequestError.module_@octokit_types.kt │ ├── RequestHeaders.module_@octokit_types.kt │ ├── RequestInterface.module_@octokit_types.kt │ ├── RequestOptions.module_@octokit_types.kt │ ├── RequestParameters.module_@octokit_types.kt │ ├── RequestRequestOptions.module_@octokit_types.kt │ ├── ResponseHeaders.module_@octokit_types.kt │ ├── Route.module_@octokit_types.kt │ ├── Signal.module_@octokit_types.kt │ ├── StrategyInterface.module_@octokit_types.kt │ ├── Url.module_@octokit_types.kt │ └── VERSION.module_@octokit_types.kt └── octokit-webhooks/ ├── build.gradle.kts └── src/ └── jsMain/ └── kotlin/ └── octokit/ ├── ActionsTrigger.kt └── webhooks/ ├── ResponseHeaders.module_@octokit_types.kt ├── event-payloads.EventPayloads.module_@octokit_webhooks.kt ├── get-webhook-payload-type-from-event.module_@octokit_webhooks.kt ├── index.module_@octokit_webhooks.kt └── types.module_@octokit_webhooks.kt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] trim_trailing_whitespace = true insert_final_newline = true charset = utf-8 indent_style = space indent_size = 2 max_line_length = 140 [*.md] trim_trailing_whitespace = false [{*.sh,gradlew}] end_of_line = lf [{*.bat,*.cmd}] end_of_line = crlf [{*.kts,*.kt}] indent_size = 4 ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL ij_kotlin_allow_trailing_comma = true ij_kotlin_allow_trailing_comma_on_call_site = true ================================================ FILE: .gitattributes ================================================ * text=auto *.sh text eol=lf gradlew text eol=lf *.bat text eol=crlf *.jar binary ================================================ FILE: .github/workflows/check-action-typing.yml ================================================ name: Check Action Typing on: push: paths: - 'action.yml' - 'action-types.yml' pull_request: paths: - 'action.yml' - 'action-types.yml' jobs: check_action_typing: name: Check Action Typing runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Check Action Typing uses: krzema12/github-actions-typing@v0 ================================================ FILE: .github/workflows/gradle-wrapper-validation.yml ================================================ name: "Validate Gradle Wrapper" on: [push, pull_request] permissions: contents: read jobs: validation: name: "Validation" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: gradle/wrapper-validation-action@v3 ================================================ FILE: .github/workflows/main.yml ================================================ # The default workflow for GitHub Actions that is used for continuous # integration. A configuration file that is used to control when, where, # and how different CI jobs are executed. # For more information on how to modify this file check the following link: # https://help.github.com/en/actions/automating-your-workflow-with-github-actions name: CI on: push: branches: - '*' pull_request: branches: - '*' jobs: build: name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install Java 17 uses: actions/setup-java@v5 with: java-version: 17 distribution: liberica - uses: burrunan/gradle-cache-action@v3 name: Cache .gradle - name: Build run: ./gradlew --no-parallel --no-daemon --build-cache build - name: Publish release if: ${{ github.ref == 'refs/heads/main' }} env: PUSH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git fetch origin refs/heads/release:release git checkout release cp cache-action-entrypoint/build/dist/js/productionExecutable/cache-action-entrypoint.js* dist/ git checkout main action.yml git checkout main action-types.yml git add dist/ action.yml action-types.yml if git diff --staged --quiet; then echo Everything is UP-TO-DATE else echo Pushing the new release git fetch origin 'refs/tags/v*:refs/tags/v*' if [[ $(git tag --points-at HEAD) ]] || [[ "$(git log --format=%B -n 1 origin/release)" != 'Publish release from'* ]]; then AMEND= FORCE= else AMEND=--amend FORCE=--force fi git config --global user.email "sitnikov.vladimir@gmail.com" git config --global user.name "CI" git commit $AMEND -m "Publish release from $GITHUB_SHA" remote_repo="https://${GITHUB_ACTOR}:${PUSH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" git push $remote_repo $FORCE release fi ================================================ FILE: .gitignore ================================================ /.idea/ /.gradle/ /build/ /*/build/ /*/*/build/ /externals/ /*/externals/ /*/*/externals/ ================================================ FILE: CHANGELOG.md ================================================ ## 2024-05-03: v3 🚀 Move to actions/cache@v4 API, resolve "Cache service responded with 422" * Bump to actions/cache@v4 API * Bump Gradle to 8.14 * Bump to Kotlin 2.1.20 * Bump kotlinx-coroutines to 1.10.2 * Bump kotlin-serialization to 1.8.1 * Bump kotlin-wrappers to 2025.5.2 ## 2024-07-25: v2, v1.21 🚀 Move to node20 * Bump `node16` to `node20`. This resolves "node16 is deprecated" warning. * Bump Gradle to 8.8 * Bump kotlinx-coroutines to 1.8.1 * Bump kotlin-serialization to 1.7.1 * Bump wrapper-validation-action to v3 * Bump setup-java, checkout to v4 ## 2023-02-18: v1.21 🚑 bump dependencies * Add wrapper-validation-action * Bump checkout and setup-java to v3 ## 2023-02-18: v1.20 🚑 Avoid crash on missing layer-..json file * fix: avoid failure when index restore misses layer-..json file * Migrate to kotlin-wrappers:kotlin-actions-toolkit for better Kotlin wrappers https://github.com/burrunan/gradle-cache-action/pull/65 * Bump Gradle to 8.0.1 * Bump Kotlin to 1.8.10 ## 2023-02-03: v1.19 🚑 Support nested version catalogs * Also consider nested version catalogs in default dependency paths: https://github.com/burrunan/gradle-cache-action/issues/63 Thanks to [Vampire](https://github.com/Vampire) for the contribution. ## 2023-02-03: v1.18 🚑 fix crash when git log returns a string with a newline * Trim the resulting SHA to prevent failures like in https://github.com/burrunan/gradle-cache-action/issues/63 ## 2023-02-03: v1.17 🚀 better types for list arguments in github-workflows-kt * Mark list arguments better: https://github.com/burrunan/gradle-cache-action/pull/61 * Add gradle/libs.versions.toml to the default dependency paths: https://github.com/burrunan/gradle-cache-action/pull/62 Thanks to [Vampire](https://github.com/Vampire) for the contribution. ## 2023-01-23: v1.16 🚀 added types for github-workflows-kt See https://github.com/burrunan/gradle-cache-action/issues/58 Thanks to [Vampire](https://github.com/Vampire) for the contribution. ## 2022-11-27: v1.15 ⬆️ bump dependencies Includes all the fixes from 1.13 and 1.14. ## 2022-10-29: v1.14 ⬆️ bump dependencies *Unreleased*: the code was not compatible with `kotlin-wrappers/node`, so it did not work. Use 1.15 instead. * bump @actions/core: 1.9.1 -> 1.10.0 (fix set-state warning) * bump @actions/cache: 3.0.4 -> 3.0.6 ## 2022-08-24: v1.13 ⬆️ bump dependencies *Unreleased*: the code was not compatible with `kotlin-wrappers/node`, so it did not work. Use 1.15 instead. * bump @actions/core: 1.9.0 -> 1.9.1 * bump @actions/cache: 3.0.0 -> 3.0.4 * Move from kotlinx-node to kotlin-wrappers/node * Print stacktrace on cache proxy server failure * Bump Gradle to 7.5.1 ## 2022-07-15: v1.12 ⬆️ bump dependencies * Kotlin 1.4.31 legacy -> 1.7.10 IR * @actions/cache: 1.0.1 -> 3.0.0 * @actions/core: 1.2.4 -> 1.9.0 * @actions/exec 1.0.4 -> 1.1.1 * @actions/glob 0.1.0 -> 0.3.0 * @actions/http-client 1.0.8 -> 2.0.1 * @actions/io 1.0.2 -> 1.1.2 * @actions/tool-cache 1.6.0 -> 2.0.1 * @octokit/request-error 2.0.2 -> 3.0.0 * @octokit/types 5.4.0 -> 6.39.0 * @octokit/webhooks 7.9.3 -> 10.0.8 * nodejs: 12.18.3 -> 16.16.0 Thanks to https://github.com/infomiho for https://github.com/burrunan/gradle-cache-action/pull/49 ## 2022-07-06: v1.11 🚑 add home-directory property to override $HOME location When Docker executes under root user, it will use `/root` as home directory, so cache location would use locations like `/root/.gradle`. The new `home-directory: /path/to/proper/user/home` property can be used to override the location of `$HOME`. See https://github.com/burrunan/gradle-cache-action/issues/41 ## 2021-03-09: v1.10 🚀 optimize local build cache with push=false when read-only Local build cache won't help much in the read-only mode since workers are stateless anyway. Disabling the cache reduces the time it takes to pack cache entries. ## 2021-03-09: v1.9 🚀 optimize remote build cache with push=false when read-only Previously `read-only` was implemented as "skip saving the caches", however it makes sense to configure `push=false` so Gradle skips cache entry preparation as well. ## 2021-03-09: v1.8 🚑 support Gradle 5 Support Gradle 5 (and possibly even earlier versions). Previously the plugin added `init.gradle` script to activate remote build cache aggregator. However, `beforeSettings` is Gradle 6.0+ only, so in previous Gradle versions `gradle-cache-action` skips `com.github.burrunan.multi-cache` plugin. The impact is old Gradle versions would not be able to use both GitHub cache and custom remote build cache at the same time (only project-defined remote build cache would be used). ## 2021-03-08: v1.7 🚀 support read-only cache operation The following configuration would make all non-main branch builds to use read-only caching: `read-only: ${{ github.ref != 'refs/heads/main' }}`. It would save GitHub space usage for PR builds, however, they would still use caches from the main branch. Bump Gradle: 6.5.1 -> 6.8.3 Bump Kotlin: 1.4.0-rc -> 1.4.31 Bump kotlinx-serialization: 1.0-M1-1.4.0-rc -> 1.1.0 Bump kotlinx-serialization: 1.0-M1-1.4.0-rc -> 1.1.0 Bump kotlinx-coroutines: 1.3.8-1.4.0-rc -> 1.4.3 Bump kotlin-wrappers: 1.0.1-pre.110 -> 1.0.1-pre.148 ## 2020-12-09: v1.6 🚀 added option to silence warnings Add `gradle-distribution-sha-256-sum-warning` option to silence warning in case checksum is missing. Add `gradle-build-scan-report` to skip publishing Gradle Build Scan URL to the job report. Thanks to [Przemysław Jakubczyk](https://github.com/pjakubczyk) for the contributions. ## 2020-09-24: v1.5 🚑 support branch names with slashes `feature/branch` is a valid branch name in Git, so `gradle-cache-action` now escapes `/` when using branch name as a part of the cache key. ## 2020-08-20: v1.4 🚑 better exclusions for *.lock files It turns out caching action can't apply exclude if user included of the parent folders. See https://github.com/actions/cache/issues/364#issuecomment-678118231 It should fix errors like ``` C:\windows\System32\tar.exe -z -cf cache.tgz -P -C D:/a/... --files-from manifest.txt tar.exe: Couldn't open C:/Users/runneradmin/.gradle/caches/6.6/generated-gradle-jars/generated-gradle-jars.lock: Permission denied tar.exe: Error exit delayed from previous errors. ``` ## 2020-08-20 🙈 silence insecure protocols warning Modern Gradle versions issue a warning when users configure `http://` build cache. In practice, GitHub-based cache is located on localhost, so it is fine to use http. The plugin adds the relevant configuration to silence Gradle warning. ## 2020-08-19 🚀 Unlock Gradle remote build caching * Add HTTP caching proxy that implements Gradle HTTP cache API [effb04a](https://github.com/burrunan/gradle-cache-action/commit/effb04a) This enables Gradle to use GitHub cache API like a regular remote build cache service, so the caching is more efficient. Gradle fetches only the objects it needs, and it uploads only what was changed. Cache eviction is managed by GitHub. The remote build cache feature activates when you use `with: arguments: build ...` to launch the build. In other words, you need to launch Gradle via `gradle-cache-action` rather that regular `run:` or `gradle-command-action`. Here's how you can integrate build cache to existing projects: * Apache Calcite: https://github.com/apache/calcite/pull/2114 * Apache JMeter: https://github.com/apache/jmeter/pull/611 * pgjdbc: https://github.com/pgjdbc/pgjdbc/pull/1862 * junit-pioneer: https://github.com/junit-pioneer/junit-pioneer/pull/325 * opentelemetry-java-instrumentation: https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/1054 ### Fixes * Reduce verbosity of "cache already exists" warning to info [8ff7dd7](https://github.com/burrunan/gradle-cache-action/commit/8ff7dd7) * Gradle dependencies: treat *.gradle.kts as a part of the cache key [1c59269](https://github.com/burrunan/gradle-cache-action/commit/1c59269) * Parse properties from properties tag rather than from arguments [140d8d9](https://github.com/burrunan/gradle-cache-action/commit/140d8d9) * Add wrappers [597d5ea](https://github.com/burrunan/gradle-cache-action/commit/597d5ea) ## 2020-08-15 Support schedule and workflow_dispatch events * Treat workflow_dispatch event (manual launch) the same as "build from the default branch) [48e88ce](https://github.com/burrunan/gradle-cache-action/commit/48e88ce) * Use defaultbranch for schedule-based builds by default [8d3e9cc](https://github.com/burrunan/gradle-cache-action/commit/8d3e9cc) * 🐛 Avoid build failures on schedule events: use a fixed "defaultbranch" instead of computing the branch name [6957760](https://github.com/burrunan/gradle-cache-action/commit/6957760) ### Fixes * 🥅 Ignore unreadable files when hasing (and print warning) [01d4c8f](https://github.com/burrunan/gradle-cache-action/commit/01d4c8f) * 🐛 avoid adding /**/ mask to Gradle dependencies key [6d3e893](https://github.com/burrunan/gradle-cache-action/commit/6d3e893) * Add exception message to hashFiles(...) [d5f863c](https://github.com/burrunan/gradle-cache-action/commit/d5f863c) * Disable minification for better error reporting in GitHub [29d2590](https://github.com/burrunan/gradle-cache-action/commit/29d2590) * 🐛 Add missing await in mkdir(String) [e4a441d](https://github.com/burrunan/gradle-cache-action/commit/e4a441d) * 🐛 Add missing JsModule declaration [54f0245](https://github.com/burrunan/gradle-cache-action/commit/54f0245) * 🐛 Fix release publishing [b52aa2f](https://github.com/burrunan/gradle-cache-action/commit/b52aa2f) * Split modules [365cc69](https://github.com/burrunan/gradle-cache-action/commit/365cc69) * 🔨 Split modules [ec0c31a](https://github.com/burrunan/gradle-cache-action/commit/ec0c31a) * ✅ Add basic tests for cache store and restore, fix invalid "always partial restore" status [594213e](https://github.com/burrunan/gradle-cache-action/commit/594213e) * Add test for hashFilesDetailed [ce7fa0f](https://github.com/burrunan/gradle-cache-action/commit/ce7fa0f) ## 2020-07-27 ✨ add gradle-dependencies-cache-key property for configuring extra dependencies (e.g. versions declared in buildSrc/Versions.kt) [cc7a294](https://github.com/burrunan/gradle-cache-action/commit/cc7a294) ## 2020-07-27 🎉 Initial version The following caches are saved and restored: * Gradle dependencies (~/.gradle/caches/modules-2) * Gradle local build cache (~/.gradle/caches/build-cache-1) * Gradle generated jars (~/.gradle/caches/*.*/generated-gradle-jars) * Maven local repository (~/.m2/repository) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: NOTICE ================================================ Copyright 2020 Vladimir Sitnikov ================================================ FILE: README.md ================================================ # Gradle Cache Action [![CI Status](https://github.com/burrunan/gradle-cache-action/workflows/CI/badge.svg)](https://github.com/burrunan/gradle-cache-action/actions) ![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/burrunan/gradle-cache-action?label=release) This is a GitHub Action for caching Gradle caches. In other words, this is [@actions/cache](https://github.com/actions/cache) customized for Gradle. Key improvements over [@actions/cache](https://github.com/actions/cache) and [gradle-command-action](https://github.com/eskatos/gradle-command-action) are: - 🚀 Gradle remote build cache backend (pulls only the needed entries from GitHub cache) - 🎉 Support multiple remote caches via [gradle-multi-cache](https://github.com/burrunan/gradle-multi-cache) (e.g. GitHub Actions + S3) - 👋 Simplified configuration (action name + gradle command is enough for most case) - 👾 Less space usage (GitHub imposes overall 5GiB limit by default, so cache space matters) - 🔗 Link to Build Scan in build results - 💡 Gradle build failure markers added to the diff view (e.g. `compileJava` or `compileKotlin` markers right in the commit diff) ## Version notes `v1` uses `node16` which has been deprecated, so consider upgrading to `v2`. The upgrade requires only changing the version, however the newer `node20` might be missing if you use an old runner. ## Usage Add the following code to your workflow file in the `.github/workflows` directory. Note: Like with [gradle-command-action](https://github.com/eskatos/gradle-command-action), you can specify `gradle-version: release` to test with the current release version of Gradle, `gradle-version: nightly` for testing Gradle nightly builds, an so on (see `gradle-version` below). Note: For the [security reasons](https://julienrenaux.fr/2019/12/20/github-actions-security-risk/) you might want to use Git SHA rather than branch name or tag name. In other words, to avoid accidental tag update, you might want to use `burrunan/gradle-cache-action@03c71a8ba93d670980695505f48f49daf43704a6` rather than `burrunan/gradle-cache-action@v1`. Please see releases page to find out the commit ids: https://github.com/burrunan/gradle-cache-action/releases You might use the following references are: * `v1`, `v2` -- this is a *moving* qualifier. It points to the latest release among `v1.x` * `v1.0`, `v1.1`, ... -- those are fixed versions. They won't change over time For the best security you might want to use `burrunan/gradle-cache-action@v3` (see the ids at https://github.com/burrunan/gradle-cache-action/releases) ```yaml - uses: burrunan/gradle-cache-action@v3 name: Build PROJECT_NAME # Extra environment variables for Gradle execution (regular GitHub Actions feature) # Note: env must be outside of "with" env: VARIABLE: VALUE with: # If you have multiple jobs, use distinct job-id in in case you want to split caches # For instance, jobs with different JDK versions can't share caches # RUNNER_OS is added to job-id automatically job-id: jdk8 # Specifies arguments for Gradle execution # If arguments is missing or empty, then Gradle is not executed arguments: build # arguments can be multi-line for better readability # arguments: | # --no-paralell # build # -x test # Gradle version to use for execution: # wrapper (default), current, rc, nightly, release-nightly, or # versions like 6.6 (see https://services.gradle.org/versions/all) gradle-version: wrapper # Properties are passed as -Pname=value properties: | kotlin.js.compiler=ir kotlin.parallel.tasks.in.project=true ``` By default, the action enables the `local` build cache, and it adds a remote build cache that stores the data in GitHub Actions cache. However, you might want to enable the [Gradle Build Cache](https://docs.gradle.org/current/userguide/build_cache.html) for your local builds to make them faster, or even add a remote cache instance, so your local builds can reuse artifacts that are build on CI. This is how you can enable local build cache (don't forget to add `--build-cache` option or `org.gradle.caching=true` property): ```kotlin // settings.gradle.kts val isCiServer = System.getenv().containsKey("CI") // Cache build artifacts, so expensive operations do not need to be re-computed buildCache { local { isEnabled = !isCiServer } } ``` ## Sample integrations Here's how you can integrate build cache to existing projects: * Apache Calcite: https://github.com/apache/calcite/pull/2114 * Apache JMeter: https://github.com/apache/jmeter/pull/611 * pgjdbc: https://github.com/pgjdbc/pgjdbc/pull/1862 * junit-pioneer: https://github.com/junit-pioneer/junit-pioneer/pull/325 * opentelemetry-java-instrumentation: https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/1054 ## Configuration The default configuration should suit most of the cases, however, there are extra knobs: ```yaml - uses: burrunan/gradle-cache-action@v3 name: Cache .gradle # Extra environment variables for Gradle execution (regular GitHub Actions feature) env: VARIABLE: VALUE with: # If you have multiple jobs, use distinct job-id in case you want to split caches # For instance, jobs with different JDK versions can't share caches # RUNNER_OS is added to job-id automatically job-id: jdk8 # Overrides $HOME # home-directory: /home/user # Disable caching of $HOME/.gradle/caches/*.*/generated-gradle-jars save-generated-gradle-jars: false # Disable remote cache that proxies requests to GitHub Actions cache remote-build-cache-proxy-enabled: false # Set the cache key for Gradle version (e.g. in case multiple jobs use different versions) # By default the value is `wrapper`, so the version is determined from the gradle-wrapper.properties # Note: this argument specifies the version for Gradle execution (if `arguments` is present) # Supported values: # wrapper (default), current, rc, nightly, release-nightly, or # versions like 6.6 (see https://services.gradle.org/versions/all) gradle-version: 6.5.1-custom # Makes all non-main branch builds to use read-only caching read-only: ${{ github.ref != 'refs/heads/main' }} # Uncomment to keep Gradle Daemon after the build # daemon: true # Arguments for Gradle execution arguments: build jacocoReport # Properties are passed as -Pname=value properties: | kotlin.js.compiler=ir kotlin.parallel.tasks.in.project=true # Relative path under $GITHUB_WORKSPACE where Git repository is placed build-root-directory: sub/directory # Activates only the caches that are relevant for executing gradle command. # This is helpful when build job executes multiple gradle commands sequentially. # Then the caching is implemented in the very first one, and the subsequent should be marked # with execution-only-caches: true execution-only-caches: true # Disable caching of ~/.gradle/caches/build-cache-* save-local-build-cache: false # Disable caching of ~/.gradle/caches/modules-* save-gradle-dependencies-cache: false # Extra files to take into account for ~/.gradle/caches dependencies gradle-dependencies-cache-key: | gradle/dependencies.kt buildSrc/**/Version.kt # Disable caching of ~/.m2/repository/ save-maven-dependencies-cache: false # Ignore some of the paths when caching Maven Local repository maven-local-ignore-paths: | org/example/ com/example/ # Enable concurrent cache save and restore # Default is concurrent=false for better log readability concurrent: true # Disable publishing Gradle Build Scan URL to job report gradle-build-scan-report: false # Disable warning about missing distributionSha256Sum property in gradle-wrapper.properties gradle-distribution-sha-256-sum-warning: false ``` ## How does dependency caching work? The current GitHub Action's cache (both [actions/cache](https://github.com/actions/cache) action and [@actions/cache](https://github.com/actions/toolkit/tree/main/packages/cache) npm package) is immutable. The cache can't be updated, so it does not work very good for caches like "Gradle dependencies" or "Maven local repository". `gradle-cache-action` creates a layered cache, and it uses a small "index" cache to identify the required layers. If only a small fraction of files changes, then the action reuses the existing caches, and it adds a layer on top of it. That enables to save cache space (GitHub has a default limit of 5 GiB), and it reduces upload time as only the cache receives only the updated files. ## How does GitHub Actions-based Gradle remote build cache work? `gradle-cache-action` launches a small proxy server that listens for Gradle requests and then it redirects the requests to the `@actions/cache` API. That makes Gradle believe it is talking to a regular remote cache, and the cache receives only the relevant updates. The increased granularity enables GitHub to evict entries better (it removes unused entries automatically). The action configures the URL to the cache proxy via the `~/.gradle/init.gradle` script, and [Gradle picks it up automatically](https://docs.gradle.org/current/userguide/init_scripts.html) Note: Saving GitHub Actions caches might take noticeable time (e.g. 100 ms), so the cache uploads in the background. In other words, build scan would show virtually zero response times for cache save operations. If your build already has a remote cache declared (e.g. you are using your own cache), then `gradle-cache-action` would configure **both** remote caches. It would read from the GitHub cache first, and it would save data to both caches. The multi-cache feature can be disabled via `multi-cache-enabled: false`. ## How to enable build scans? 1. Read and agree to the terms of service: https://gradle.com/terms-of-service 1. Add `--scan` to `arguments:`, and add the following to `settings.gradle.kts` ```kotlin plugins { `gradle-enterprise` } val isCiServer = System.getenv().containsKey("CI") if (isCiServer) { gradleEnterprise { buildScan { termsOfServiceUrl = "https://gradle.com/terms-of-service" termsOfServiceAgree = "yes" tag("CI") } } } ``` ## Why another action instead of gradle-command-action? `gradle-command-action` was started as a Kotlin/JS experiment for making a customized [@actions/cache](https://github.com/actions/cache) that would make Gradle builds faster. Then it turned out there's a proxy remote cache requests to the `@actions/cache` API can be used when the caching action executes Gradle, so the `gradle-cache-action` got a Gradle execution feature. Of course, the same could have been made in [gradle-command-action](https://github.com/eskatos/gradle-command-action), however: - The author was not familiar with TypeScript ecosystem (stdlib, typical libraries, testing libraries, etc.) - Caching logic is collections-heavy, and Kotlin stdlib shines here. For instance, in Kotlin `list + list` adds lists, and `array.associateWith { valueFor(it) }` converts arrays to maps. This is easy to write without consulting StackOverflow, the code is readable, and it does not require you [to fight with the compiler](https://blog.johnnyreilly.com/2016/06/create-es2015-map-from-array-in-typescript.html). - A single language helps when building connected components. `gradle-cache-action` integrates with [gradle-multi-cache](https://github.com/burrunan/gradle-multi-cache) and [gradle-s3-build-cache](https://github.com/burrunan/gradle-s3-build-cache), and they all are Kotlin-based. ## Can I use the caching part of the action only? Yes, you can. If you omit `arguments:`, then the action runs in `cache-only` mode. It won't launch Gradle. ## Can I call multiple different Gradle builds in the same job? This might be complicated, see https://github.com/burrunan/gradle-cache-action/issues/15. Currently, the workaround is to configure `execution-only-caches: true` for all but one `gradle-cache-action` executions. Then one of the actions would do the cache save and restore, and the rest would use their own caches only. ## Contributing Contributions are always welcome! If you'd like to contribute (and we hope you do) please open a pull request. ## License Apache 2.0 ## Author Vladimir Sitnikov ================================================ FILE: action-types.yml ================================================ # See https://github.com/krzema12/github-actions-typing outputs: build-scan-url: type: string inputs: job-id: type: string build-root-directory: type: string home-directory: type: string gradle-version: type: string read-only: type: boolean save-generated-gradle-jars: type: boolean save-local-build-cache: type: boolean multi-cache-enabled: type: boolean multi-cache-version: type: string multi-cache-repository: type: string multi-cache-group-id-filter: type: string save-gradle-dependencies-cache: type: boolean execution-only-caches: type: boolean remote-build-cache-proxy-enabled: type: boolean gradle-dependencies-cache-key: type: list separator: "\n" list-item: type: string save-maven-dependencies-cache: type: boolean maven-local-ignore-paths: type: list separator: "\n" list-item: type: string debug: type: boolean concurrent: type: boolean arguments: type: list separator: "\n" list-item: type: string properties: type: list separator: "\n" list-item: type: string gradle-build-scan-report: type: boolean gradle-distribution-sha-256-sum-warning: type: boolean ================================================ FILE: action.yml ================================================ name: 'Gradle Cache' description: 'Caches .gradle folder (dependencies, local build cache, ...)' author: 'Vladimir Sitnikov' outputs: build-scan-url: description: Link to the build scan if any inputs: job-id: description: A job identifier to avoid cache pollution from different jobs required: false build-root-directory: description: Relative path under $GITHUB_WORKSPACE where Git repository is placed required: false home-directory: description: Overrides the location of $HOME (e.g. to avoid use of /root when running in Docker) required: false gradle-version: description: (wrapper | or explicit version) Caches often depend on the Gradle version, so this parameter sets the ID to use for cache keys. It does not affect the Gradle version used for build required: false default: wrapper read-only: description: Configures caches for read-only opreration (e.g. to save GitHub Actions storage limit) required: false default: false save-generated-gradle-jars: description: Enables caching of $HOME/.gradle/caches/*.*/generated-gradle-jars required: false default: 'true' save-local-build-cache: description: Enables caching of $HOME/.gradle/caches/build-cache-1 required: false default: 'true' multi-cache-enabled: description: Adds com.github.burrunan.multi-cache plugin to settings.gradle so GitHub Actions cache can be used in parallel with Gradle remote build cache required: false default: 'true' multi-cache-version: description: Configures com.github.burrunan.multi-cache version to use required: false default: '1.0' multi-cache-repository: description: Configures repository where com.github.burrunan.multi-cache can be located required: false default: '' multi-cache-group-id-filter: description: Configures group id for selecting only com.github.burrunan.multi-cache artifacts (it enables Gradle to use custom repository for multi-cache only) required: false default: 'com[.]github[.]burrunan[.]multi-?cache' save-gradle-dependencies-cache: description: Enables caching of ~/.gradle/caches/modules-* required: false default: 'true' execution-only-caches: description: | Activates only the caches that are relevant for executing gradle command. This is helpful when build job executes multiple gradle commands sequentially. Then the caching is implemented in the very first one, and the subsequent should be marked with execution-only-caches: true required: false default: 'false' remote-build-cache-proxy-enabled: description: Activates a remote cache that proxies requests to GitHub Actions cache required: false default: 'true' gradle-dependencies-cache-key: description: Extra files to take into account for ~/.gradle/caches dependencies required: false save-maven-dependencies-cache: description: Enables caching of ~/.m2/repository/ required: false default: 'true' maven-local-ignore-paths: description: Specifies ignored paths in the Maven Local repository (e.g. the artifacts of the current project) required: false default: '' debug: description: Shows extra logging to debug the action required: false default: 'true' daemon: description: Enables or disables Gradle Daemon (--no-daemon). Defaults to false as typically the build executes only once in CI, so there's no much value in keeping the daemon required: false default: 'false' concurrent: description: Enables concurent cache download and upload (disabled by default for better log output) required: false default: 'false' arguments: description: Gradle arguments to pass (optionally multiline) required: false properties: description: Extra Gradle properties (multiline) which would be passed as -Pname=value arguments required: false gradle-build-scan-report: description: Publishes Gradle Build Scan URL to job report. required: false default: 'true' gradle-distribution-sha-256-sum-warning: description: Enables warning when distributionSha256Sum property is missing in gradle-wrapper.properties required: false default: 'true' runs: using: node20 main: dist/cache-action-entrypoint.js post: dist/cache-action-entrypoint.js branding: icon: archive color: gray-dark ================================================ FILE: build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension plugins { kotlin("multiplatform") apply false } plugins.withType { configure { version = "22.0.0" } } subprojects { if (path != ":wrappers") { apply(plugin = "org.jetbrains.kotlin.multiplatform") } } allprojects { plugins.withId("org.jetbrains.kotlin.multiplatform") { configure { js { compilerOptions { target = "es2015" } } } tasks { withType().configureEach { testLogging { showStandardStreams = true } } } configure { js { if (project.name.endsWith("-entrypoint")) { browser { testTask { useMocha { timeout = "10000" } } } binaries.executable() } else { nodejs { testTask { useMocha { timeout = "10000" environment("RUNNER_TOOL_CACHE", layout.buildDirectory.dir("tool-cache").get().asFile.toString()) } } } } } } dependencies { "commonMainApi"(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.10.2")) "commonMainApi"(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.9.0")) "jsMainImplementation"(enforcedPlatform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:2025.12.7")) if (project.path != ":test-library") { "jsTestImplementation"(rootProject.projects.testLibrary) } } } } ================================================ FILE: cache-action-entrypoint/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { implementation(projects.cacheProxy) implementation(projects.gradleLauncher) implementation(projects.layeredCache) implementation(projects.wrappers.actionsToolkit) implementation(projects.wrappers.nodejs) implementation(projects.wrappers.octokitWebhooks) implementation("org.jetbrains.kotlin-wrappers:kotlin-actions-io") implementation(npm("string-argv", "0.3.1")) } } } } ================================================ FILE: cache-action-entrypoint/src/jsMain/kotlin/main.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import actions.core.* import actions.core.ext.getInput import actions.core.ext.getListInput import actions.io.mkdirP import com.github.burrunan.gradle.GradleCacheAction import com.github.burrunan.gradle.Parameters import com.github.burrunan.gradle.github.stateVariable import com.github.burrunan.gradle.proxy.CacheProxy import com.github.burrunan.launcher.LaunchParams import com.github.burrunan.launcher.install import com.github.burrunan.launcher.launchGradle import com.github.burrunan.launcher.resolveDistribution import com.github.burrunan.wrappers.nodejs.normalizedPath import js.globals.globalThis import node.buffer.BufferEncoding import node.fs.writeFile import node.path.path import node.process.process import octokit.currentTrigger fun String.splitLines() = split(Regex("\\s*[\r\n]+\\s*")) .filter { !it.startsWith("#") && it.contains("=") } .associate { val values = it.split(Regex("\\s*=\\s*"), limit = 2) values[0] to (values.getOrNull(1) ?: "") } fun isMochaRunning() = arrayOf("afterEach", "after", "beforeEach", "before", "describe", "it").all { globalThis[it] is Function<*> } suspend fun main() { if (isMochaRunning()) { // Ignore if called from tests return } val stageVar = stateVariable("stage") { "MAIN" } val stage = ActionStage.values().firstOrNull { it.name == stageVar.get() } // Set next stage stageVar.set( when (stage) { ActionStage.MAIN -> ActionStage.POST null -> { setFailed("Unable to find action stage: ${stageVar.get()}") return } else -> null }?.name ?: "FINAL", ) try { mainInternal(stage) } catch (e: ActionFailedException) { setFailed(e.message) } } suspend fun mainInternal(stage: ActionStage) { val homeDirectory = getInput("home-directory").trimEnd('/', '\\') if (homeDirectory != "") { info("Overriding home directory to $homeDirectory") process.env["HOME"] = homeDirectory } val gradleStartArguments = parseArgsStringToArgv(getInput("arguments")).toList() val cacheProxyEnabled = getInput("remote-build-cache-proxy-enabled").ifBlank { "true" }.toBoolean() val executionOnlyCaches = getInput("execution-only-caches").ifBlank { "false" }.toBoolean() val enableBuildScanReport = getInput("gradle-build-scan-report").ifBlank { "true" }.toBoolean() val buildRootDirectory = getInput("build-root-directory").trimEnd('/', '\\') if (buildRootDirectory != "") { info("changing working directory to $buildRootDirectory") process.chdir(buildRootDirectory) } val params = Parameters( jobId = ActionsEnvironment.RUNNER_OS + "-" + getInput("job-id"), path = ".", debug = getInput("debug").toBoolean(), generatedGradleJars = getInput("save-generated-gradle-jars").ifBlank { "true" }.toBoolean(), localBuildCache = (!cacheProxyEnabled || gradleStartArguments.isEmpty()) && getInput("save-local-build-cache").ifBlank { "true" } .toBoolean(), gradleDependenciesCache = !executionOnlyCaches && getInput("save-gradle-dependencies-cache").ifBlank { "true" }.toBoolean(), gradleDependenciesCacheKey = getListInput("gradle-dependencies-cache-key"), mavenDependenciesCache = !executionOnlyCaches && getInput("save-maven-dependencies-cache").ifBlank { "true" }.toBoolean(), mavenLocalIgnorePaths = getListInput("maven-local-ignore-paths"), concurrent = getInput("concurrent").ifBlank { "false" }.toBoolean(), readOnly = getInput("read-only").ifBlank { "false" }.toBoolean(), ) val gradleDistribution = resolveDistribution( versionSpec = getInput("gradle-version").ifBlank { "wrapper" }, projectPath = params.path, distributionUrl = getInput("gradle-distribution-url").ifBlank { null }, distributionSha256Sum = getInput("gradle-distribution-sha-256-sum").ifBlank { null }, enableDistributionSha256SumWarning = getInput("gradle-distribution-sha-256-sum-warning").ifBlank { "true" }.toBoolean(), ) if (stage == ActionStage.MAIN || stage == ActionStage.POST) { val cacheAction = GradleCacheAction(currentTrigger(), params, gradleDistribution) if (params.generatedGradleJars || params.localBuildCache || params.gradleDependenciesCache || params.mavenDependenciesCache ) { cacheAction.execute(stage) } } if (stage == ActionStage.MAIN && gradleStartArguments.isNotEmpty()) { val args = when (params.localBuildCache || cacheProxyEnabled) { true -> listOf("--build-cache") + gradleStartArguments else -> gradleStartArguments } val launchParams = LaunchParams( gradle = install(gradleDistribution), daemon = getInput("daemon").ifBlank { "false" }.toBoolean(), projectPath = params.path, arguments = args, properties = getInput("properties").splitLines(), ) val cacheProxy = CacheProxy() if (cacheProxyEnabled) { info("Starting remote cache proxy, adding it via ~/.gradle/init.gradle") cacheProxy.start() val gradleHome = path.join("~".normalizedPath, ".gradle") mkdirP(gradleHome) writeFile( path.join(gradleHome, "init.gradle"), cacheProxy.getMultiCacheConfiguration( multiCacheEnabled = getInput("multi-cache-enabled").ifBlank { "true" }.toBoolean(), multiCacheVersion = getInput("multi-cache-version").ifBlank { "1.0" }, multiCacheRepository = getInput("multi-cache-repository"), multiCacheGroupIdFilter = getInput("multi-cache-group-id-filter").ifBlank { "com[.]github[.]burrunan[.]multi-?cache" }, push = !params.readOnly, ), BufferEncoding.utf8, ) } try { val result = launchGradle(launchParams) if (enableBuildScanReport) { result.buildScanUrl?.let { warning("Gradle Build Scan: $it") setOutput("build-scan-url", it) } } } finally { if (cacheProxyEnabled) { cacheProxy.stop() } } } return } ================================================ FILE: cache-action-entrypoint/src/jsMain/kotlin/stringArgv.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:JsModule("string-argv") external fun parseArgsStringToArgv(value: String, env: String = definedExternally, file: String = definedExternally): Array ================================================ FILE: cache-action-entrypoint/src/jsTest/kotlin/com/github/burrunan/ArgumentsTest.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan import parseArgsStringToArgv import kotlin.test.Test import kotlin.test.assertEquals class ArgumentsTest { private fun parse(input: String, vararg output: String) { assertEquals(listOf(*output), parseArgsStringToArgv(input).toList(), input) } @Test fun simple() { parse("") parse("a b", "a", "b") parse("a 'b'", "a", "b") parse("a \"b\"", "a", "b") } @Test fun multiline() { parse("a\nb", "a", "b") parse("a\n b", "a", "b") parse("a\n b ", "a", "b") parse("a\n b \nc", "a", "b", "c") } @Test fun multilineWithQuotes() { parse("'a\nb'", "a\nb") parse("hello 'a\n b' world", "hello", "a\n b", "world") parse("hello \"a\n b\" world", "hello", "a\n b", "world") } @Test fun withDollars() { parse("\$HOME", "\$HOME") } @Test fun multilineWithComments() { // TODO: "# commented" should be ignored parse(""" build # commented test """.trimIndent(), "build", "#", "commented", "test") } } ================================================ FILE: cache-action-entrypoint/src/jsTest/kotlin/com/github/burrunan/SplitLinesTest.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan import splitLines import kotlin.test.Test import kotlin.test.assertEquals class SplitLinesTest { @Test fun empty() { assertEquals(mapOf("a" to "b", "c" to ""), "a=b\nc=".splitLines()) } @Test fun withoutEquals() { assertEquals(mapOf("a" to "b"), "a=b\nc".splitLines()) } } ================================================ FILE: cache-action-entrypoint/webpack.config.d/config.js ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ config.output = config.output || {} config.output.globalObject = "this" config.target = "node" config.resolve.modules.unshift("src/test/resources") const TerserPlugin = require('terser-webpack-plugin'); // keep_classnames is required to workaround node-fetch Expected signal to be an instanceof AbortSignal config.optimization = { minimizer: [ new TerserPlugin({ parallel: true, terserOptions: { // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions mangle: false, sourceMap: true, // compress: false, keep_classnames: /AbortSignal/, keep_fnames: /AbortSignal/, output: { beautify: true, indent_level: 1 } } }), ], }; ================================================ FILE: cache-proxy/build.gradle.kts ================================================ import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.js.testing.mocha.KotlinMocha /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { implementation(projects.cacheServiceMock) implementation(projects.wrappers.actionsCache) implementation(projects.wrappers.actionsToolkit) implementation(projects.wrappers.nodejs) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json") } } } } tasks.withType().configureEach { (testFramework as KotlinMocha).timeout = "60000" } ================================================ FILE: cache-proxy/src/jsMain/kotlin/com/github/burrunan/gradle/proxy/CacheProxy.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.proxy import actions.cache.RestoreType import actions.cache.restoreAndLog import actions.core.LogLevel import actions.glob.removeFiles import com.github.burrunan.gradle.cache.HttpException import com.github.burrunan.gradle.cache.handle import com.github.burrunan.wrappers.nodejs.mkdir import com.github.burrunan.wrappers.nodejs.pipeAndWait import js.objects.unsafeJso import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import node.fs.createReadStream import node.fs.createWriteStream import node.fs.stat import node.http.IncomingMessage import node.http.OutgoingHttpHeaders import node.http.ServerResponse import node.net.AddressInfo import node.path.path import node.process.process import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class CacheProxy { companion object { const val GHA_CACHE_URL = "GHA_CACHE_URL" private const val TEMP_DIR = ".cache-proxy" private val cacheVersion = "1-" } private var _cacheUrl: String? = null val cacheUrl: String? get() = _cacheUrl private val server = node.http.createServer> { req, res -> val query = node.url.parse(req.url!!, true) val path = query.pathname ?: "" res.handle { val id = path.removePrefix("/") when (req.method) { "GET" -> getEntry(id, res) "PUT" -> putEntry(id, req, res) else -> HttpException.notImplemented("Not implemented: ${req.method}") } } } // private val compression = jso { compressionMethod = CompressionMethod.Gzip } private suspend fun putEntry(id: String, req: IncomingMessage, res: ServerResponse<*>) { val fileName = path.join(TEMP_DIR, "bc-$id") try { req.pipeAndWait(createWriteStream(fileName)) res.writeHead(200, "OK", undefined.unsafeCast()) } finally { GlobalScope.launch { try { actions.cache.saveAndLog(listOf(fileName), id, cacheVersion, logLevel = LogLevel.DEBUG) } finally { removeFiles(listOf(fileName)) } } } } private suspend fun getEntry(id: String, res: ServerResponse<*>) { val fileName = path.join(TEMP_DIR, "bc-$id") val restoreType = restoreAndLog(listOf(fileName), id, restoreKeys = listOf(), version = cacheVersion, logLevel = LogLevel.DEBUG) if (restoreType == RestoreType.None) { throw HttpException.notFound("No cache entry found for $id") } res.writeHead( 200, "Ok", unsafeJso { contentLength = stat(fileName).size }, ) createReadStream(fileName).pipeAndWait(res) } private val pluginId = "com.github.burrunan.multi-cache" fun getMultiCacheConfiguration( multiCacheEnabled: Boolean = true, multiCacheVersion: String = "1.0", multiCacheRepository: String = "", multiCacheGroupIdFilter: String = "com[.]github[.]burrunan[.]multi-?cache", push: Boolean = true, ): String { val multiCacheGroupIdFilterEscaped = multiCacheGroupIdFilter.replace("\\", "\\\\") //language=Groovy return """ def pluginId = 'com.github.burrunan.multi-cache' def multiCacheVersion = '1.0' def multiCacheGroupIdFilter = 'com[.]github[.]burrunan[.]multi-?cache' boolean multiCacheEnabled = $multiCacheEnabled String multiCacheRepository = '$multiCacheRepository' boolean gradle6Plus = org.gradle.util.GradleVersion.current() >= org.gradle.util.GradleVersion.version('6.0') // beforeSettings is Gradle 6.0+ if (multiCacheEnabled && !gradle6Plus) { println("Multiple remote build caches ($pluginId) are supported in Gradle 6.0+ only") multiCacheEnabled = false } if (multiCacheEnabled) { beforeSettings { settings -> def repos = settings.buildscript.repositories if (multiCacheRepository != '') { repos.add( repos.maven { url = multiCacheRepository if ('$multiCacheGroupIdFilterEscaped' != '') { content { includeGroupByRegex('$multiCacheGroupIdFilterEscaped') } } } ) } else if (repos.isEmpty()) { repos.add(repos.gradlePluginPortal()) } settings.buildscript.dependencies { classpath("$pluginId:${pluginId}.gradle.plugin:$multiCacheVersion") } } } settingsEvaluated { settings -> settings.buildCache { boolean needMulticache = remote != null if (needMulticache && !multiCacheEnabled) { println("$pluginId is disabled") return } local { enabled = true push = $push } if (needMulticache) { settings.pluginManager.apply("$pluginId") settings.multicache.push('base') } remote(HttpBuildCache) { url = '$cacheUrl' push = $push // Build cache is located on localhost, so it is fine to use http protocol if (gradle6Plus) { allowInsecureProtocol = true } } if (needMulticache) { settings.multicache.pushAndConfigure('actions-cache') { loadSequentiallyWriteConcurrently('actions-cache', 'base') } } } } """.trimIndent() } suspend fun start() { suspendCoroutine { cont -> server.listen(0) { cont.resume(null) } } mkdir(TEMP_DIR) val url = "http://localhost:${(server.address().unsafeCast()).port}/" _cacheUrl = url process.env[GHA_CACHE_URL] = url } fun stop() { server.close() } suspend inline operator fun invoke(block: () -> T): T { start() try { return block() } finally { stop() } } } ================================================ FILE: cache-proxy/src/jsTest/kotlin/com/github/burrunan/gradle/proxy/CacheProxyTest.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.proxy import actions.exec.ExecOptions import actions.exec.exec import actions.glob.removeFiles import com.github.burrunan.gradle.cache.CacheService import com.github.burrunan.test.runTest import com.github.burrunan.wrappers.nodejs.mkdir import kotlinx.serialization.json.Json import kotlinx.serialization.json.encodeToDynamic import node.buffer.BufferEncoding import node.fs.copyFile import node.fs.writeFile import node.process.process import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.fail class CacheProxyTest { // Emulates Azure Cache Backend for @actions/cache val cacheService = CacheService() // Implements Gradle HTTP Build Cache via @actions/cache val cacheProxy = CacheProxy() @Test fun abc() = runTest { val z = mapOf("a" to 4, "b" to 6) println("json: " + JSON.stringify(Json.encodeToDynamic(z))) } @Test fun cacheProxyWorks() = runTest { val dir = "remote_cache_test" mkdir(dir) val root = process.cwd() + "/../../../.." console.log(root) cacheService { cacheProxy { val outputFile = "build/out.txt" removeFiles(listOf("$dir/$outputFile")) copyFile("$root/gradlew", dir + "/gradlew") mkdir("$dir/gradle") mkdir("$dir/gradle/wrapper") copyFile("$root/gradle/wrapper/gradle-wrapper.jar", "$dir/gradle/wrapper/gradle-wrapper.jar") copyFile("$root/gradle/wrapper/gradle-wrapper.properties", "$dir/gradle/wrapper/gradle-wrapper.properties") writeFile( "$dir/settings.gradle", """ rootProject.name = 'sample' boolean gradle6Plus = org.gradle.util.GradleVersion.current() >= org.gradle.util.GradleVersion.version('6.0') buildCache { local { // Only remote cache should be used enabled = false } remote(HttpBuildCache) { url = '${process.env["GHA_CACHE_URL"]}' push = true if (gradle6Plus) { allowInsecureProtocol = true } } } """.trimIndent(), BufferEncoding.utf8, ) writeFile( "$dir/build.gradle", """ tasks.create('props', WriteProperties) { outputFile = file("$outputFile") property("hello", "world") } tasks.create('props2', WriteProperties) { outputFile = file("${outputFile}2") property("hello", "world2") } """.trimIndent(), BufferEncoding.utf8, ) writeFile( "$dir/gradle.properties", """ org.gradle.caching=true #org.gradle.caching.debug=true org.gradle.configuration-cache=true """.trimIndent(), BufferEncoding.utf8, ) val out = exec("./gradlew", "props", "-i", "--build-cache", captureOutput = true) { ExecOptions.copy(it, cwd = dir, silent = true, ignoreReturnCode = true, ) } if (out.exitCode != 0) { fail("Unable to execute :props task: STDOUT: ${out.stdout}, STDERR: ${out.stderr}") } assertContains( out.stdout, "1 actionable task: 1 executed", ) removeFiles(listOf("$dir/$outputFile")) val out2 = exec("./gradlew", "props", "-i", "--build-cache", captureOutput = true) { ExecOptions.copy(it, cwd = dir, silent = true, ignoreReturnCode = true, ) } if (out.exitCode != 0) { fail("Unable to execute :props task: STDOUT: ${out.stdout}, STDERR: ${out.stderr}") } assertContains(out2.stdout, "1 actionable task: 1 from cache") } } } } ================================================ FILE: cache-service-mock/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { implementation(projects.wrappers.actionsCache) implementation(projects.wrappers.actionsToolkit) implementation(projects.wrappers.nodejs) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") } } } } ================================================ FILE: cache-service-mock/src/jsMain/kotlin/com/github/burrunan/gradle/cache/CacheService.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import actions.cache.internal.* import actions.core.debug import com.github.burrunan.wrappers.js.suspendWithCallback import com.github.burrunan.wrappers.nodejs.exists import com.github.burrunan.wrappers.nodejs.readJson import com.github.burrunan.wrappers.nodejs.readToBuffer import js.objects.unsafeJso import node.http.IncomingMessage import node.http.OutgoingHttpHeaders import node.http.ServerResponse import node.net.AddressInfo import node.process.process import node.querystring.ParsedUrlQuery import node.url.Url import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class CacheService { companion object { const val ARCHIVE_DOWNLOAD_URL = "_apis/artifactcache/get" } private val storage = CacheStorage() private val server = node.http.createServer> { req, res -> val query = node.url.parse(req.url!!, true) val path = query.pathname ?: "" res.handle { when { path == "/_apis/artifactcache/caches" && req.method == "POST" -> reserveCache(req, res) path == "/_apis/artifactcache/cache" && req.method == "GET" -> getCache(query, res) path.endsWith(ARCHIVE_DOWNLOAD_URL) && req.method == "GET" -> getContents(query, res) path.startsWith("/_apis/artifactcache/caches/") -> cacheOp(path.substringAfter("/_apis/artifactcache/caches/").toInt(), req, res) else -> HttpException.notImplemented("Path: $path") } } } private fun getContents(query: Url, res: ServerResponse<*>) { val key = query.query.unsafeCast()["key"] as String val entry = storage.getValue(key) res.writeHead( 200, "Ok", unsafeJso { contentLength = entry.value.length }, ) res.write(entry.value) } private suspend fun cacheOp(cacheId: Number, req: IncomingMessage, res: ServerResponse<*>) = when (req.method) { "PATCH" -> uploadCache(cacheId, req, res) "POST" -> commitCache(cacheId, req, res) else -> throw HttpException.notImplemented("Unknown method: ${req.method}") } private fun getCache(query: Url, res: ServerResponse<*>) { val request = query.query.unsafeCast() var resultKey: String? = null var resultEntry: CacheEntry? = null for ((index, key) in request.keys.split(',').withIndex()) { if (index == 0) { val entry = storage[key] ?: continue if (entry.version != request.version) { debug("Entry version differs for key $key. Requested: ${request.version}, actual: ${entry.version}") } resultKey = key resultEntry = entry break } val entry = storage.find(key, request.version) ?: continue resultKey = entry.key resultEntry = entry.value } if (resultKey == null) { throw HttpException.noContent("No entries found") } res.writeHead(200, "Ok", undefined.unsafeCast()) res.write( JSON.stringify( unsafeJso { cacheKey = resultKey scope = "refs/origin/main" creationTime = resultEntry?.creationTime?.toString() archiveLocation = "${process.env["ACTIONS_CACHE_URL"]}$ARCHIVE_DOWNLOAD_URL?key=$resultKey".takeIf { resultEntry != null } }, ), ) } private suspend fun uploadCache(cacheId: Number, req: IncomingMessage, res: ServerResponse<*>) { val contentRange = req.headers.asDynamic()["content-range"] as String val (_, start, end) = contentRange.match("bytes (\\d+)-(\\d+)") ?: arrayOf("", "", "") if (start.isEmpty()) { throw HttpException.notImplemented("Unknown content-range: $contentRange") } storage.update(cacheId, start.toInt(), end.toInt(), req.readToBuffer()) res.writeHead(200, "OK", undefined.unsafeCast()) } private suspend fun commitCache(cacheId: Number, req: IncomingMessage, res: ServerResponse<*>) { storage.commitCache(cacheId, req.readJson().size) res.writeHead(200, "OK", undefined.unsafeCast()) } private suspend fun reserveCache(req: IncomingMessage, res: ServerResponse<*>) { if (req.method != "POST") { throw HttpException.badRequest("Expecting POST method, got ${req.method}") } val request = req.readJson() val cacheId = storage.reserveCache(request.key, request.version!!) ?: throw HttpException.badRequest("Cache entry already exists") res.writeHead(200, "Reserve Cache OK", undefined.unsafeCast()) res.write( JSON.stringify( unsafeJso { this.cacheId = cacheId }, ), ) } suspend inline operator fun invoke(block: () -> T): T { start() try { return block() } finally { stop() } } suspend fun start() { suspendCoroutine { cont -> server.listen(0) { cont.resume(null) } } val runnerTemp = "runner_temp" if (!exists(runnerTemp)) { com.github.burrunan.wrappers.nodejs.mkdir(runnerTemp) } process.env["ACTIONS_RUNTIME_TOKEN"] = "42" process.env["RUNNER_TEMP"] = process.cwd() + "/" + runnerTemp process.env["ACTIONS_CACHE_URL"] = "http://localhost:${(server.address().unsafeCast()).port}/" } suspend fun stop() { suspendWithCallback { server.close(it) } } } ================================================ FILE: cache-service-mock/src/jsMain/kotlin/com/github/burrunan/gradle/cache/CacheStorage.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import node.buffer.Buffer class CacheStorage { private val storage = mutableMapOf() private val reservations = mutableMapOf() private val caches = mutableMapOf() private var nextId = 0 fun reserveCache(key: String, version: String): Number? { if (key in storage || key in reservations) { return null } if (reservations[key]?.version?.equals(version) == false) { return null } nextId += 1 reservations[key] = CacheReservation(nextId, version) caches[nextId] = TemporaryCache(key) return nextId } operator fun set(key: String, value: CacheEntry) { storage[key] = value } operator fun get(key: String) = storage[key] fun getValue(key: String) = storage.getValue(key) fun find(prefix: String, version: String) = storage.filterKeys { it.startsWith(prefix) } .filterValues { it.version == version } .maxByOrNull { it.value.creationTime.toDouble() } fun update(cacheId: Number, start: Int, end: Int, buffer: Buffer<*>) { caches.getValue(cacheId).parts.add(UploadPart(start, end, buffer)) } fun commitCache(cacheId: Number, size: Number) { val cache = caches.remove(cacheId) ?: throw HttpException.noContent("Cache $cacheId is not found") val reservation = reservations.remove(cache.key) ?: throw HttpException.noContent("Reservation ${cache.key} is not found for cache $cacheId") val parts = cache.parts val result = if (parts.size == 1 && parts[0].contents.length == size) { parts[0].contents } else { Buffer.alloc(size).also { for (part in parts) { part.contents.copy(it, part.start, 0, part.end) } } } set(cache.key, CacheEntry(reservation.version, result, cacheId)) } } class CacheEntry(val version: String, val value: Buffer<*>, val creationTime: Number) class CacheReservation(val number: Number, val version: String) class TemporaryCache(val key: String) { val parts = mutableListOf() } class UploadPart(val start: Int, val end: Int, val contents: Buffer<*>) ================================================ FILE: cache-service-mock/src/jsMain/kotlin/com/github/burrunan/gradle/cache/HttpException.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache class HttpException(val code: Int, message: String) : Throwable(message) { companion object { fun noContent(message: String) = HttpException(204, message) fun notImplemented(message: String) = HttpException(501, message) fun notFound(message: String) = HttpException(404, message) fun badRequest(message: String) = HttpException(400, message) } } ================================================ FILE: cache-service-mock/src/jsMain/kotlin/com/github/burrunan/gradle/cache/HttpExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import node.http.OutgoingHttpHeaders import node.http.ServerResponse fun ServerResponse<*>.handle(action: suspend CoroutineScope.() -> Unit) = GlobalScope.launch { try { supervisorScope { action() } } catch (e: HttpException) { writeHead(e.code, e.message ?: "no message", undefined.unsafeCast()) } catch (e: Throwable) { e.printStackTrace() writeHead(500, "Error processing ${e.message}", undefined.unsafeCast()) } finally { end() } } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle-launcher/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { implementation(projects.hashing) implementation(projects.wrappers.actionsToolkit) implementation(projects.wrappers.javaProperties) implementation(projects.wrappers.nodejs) implementation("org.jetbrains.kotlin-wrappers:kotlin-actions-http-client") implementation("org.jetbrains.kotlin-wrappers:kotlin-actions-tool-cache") implementation("org.jetbrains.kotlin-wrappers:kotlin-actions-io") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") } } } } ================================================ FILE: gradle-launcher/src/jsMain/kotlin/com/github/burrunan/launcher/GradleDistribution.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher data class GradleDistribution( val version: String, val distributionUrl: String, val distributionSha256Sum: String?, ) suspend fun resolveDistribution( versionSpec: String, projectPath: String, distributionUrl: String? = null, distributionSha256Sum: String? = null, enableDistributionSha256SumWarning: Boolean = true ): GradleDistribution { return if (distributionUrl == null) { when (val version = GradleVersion(versionSpec)) { is GradleVersion.Official -> version.findUrl() is GradleVersion.Dynamic -> version.findUrl() is GradleVersion.Wrapper -> findVersionFromWrapper(projectPath, enableDistributionSha256SumWarning) } } else { GradleDistribution( version = versionSpec, distributionUrl = distributionUrl, distributionSha256Sum = distributionSha256Sum ?: "$distributionUrl.sha256", ) } } ================================================ FILE: gradle-launcher/src/jsMain/kotlin/com/github/burrunan/launcher/GradleInstaller.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher import actions.core.ActionFailedException import actions.core.info import actions.core.warning import actions.http.client.HttpClient import actions.http.client.HttpCodes import actions.io.rmRF import actions.tool.cache.cacheDir import actions.tool.cache.downloadTool import actions.tool.cache.extractZip import com.github.burrunan.hashing.hashFiles import com.github.burrunan.wrappers.nodejs.exists import js.objects.unsafeJso import js.promise.await import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import node.buffer.BufferEncoding import node.fs.chmod import node.fs.readFile import node.http.OutgoingHttpHeaders import node.path.path import node.process.Platform suspend fun install(distribution: GradleDistribution): String { val cachedTool = actions.tool.cache.find("gradle", distribution.version) val gradleDir = if (cachedTool.isNotEmpty()) { info("Detected Gradle ${distribution.version} at $cachedTool") cachedTool } else { val gradleZip = downloadTool(distribution.distributionUrl) distribution.distributionSha256Sum?.let { expectedSha256 -> val hash = hashFiles(gradleZip, algorithm = "sha256", includeFileName = false).hash if (hash != expectedSha256) { throw ActionFailedException( "Checksum mismatch for Gradle ${distribution.version} (${distribution.distributionUrl}). " + "Expected: $expectedSha256, actual: $hash", ) } } val extractedGradleDir = extractZip(gradleZip) cacheDir(path.join(extractedGradleDir, "gradle-${distribution.version}"), "gradle", distribution.version).also { GlobalScope.launch { // Remove temporary files rmRF(gradleZip) rmRF(extractedGradleDir) } } } return path.join(gradleDir, "bin", if (node.os.platform() == Platform.win32) "gradle.bat" else "gradle").also { if (node.os.platform() != Platform.win32) { chmod(it, "755".toInt(8)) } } } private val HTTP_AGENT = unsafeJso { set("User-Agent", "burrunan/gradle-cache-action") } suspend fun GradleVersion.Official.findUrl(): GradleDistribution { val url = "https://services.gradle.org/versions/all" val response = HttpClient().getJson>(url, HTTP_AGENT).await() if (response.statusCode.unsafeCast() != HttpCodes.OK) { throw ActionFailedException("Unable to lookup $url Gradle version: ${response.statusCode}, ${JSON.stringify(response.result)}") } return response.result?.firstOrNull { it.version == name }?.resolveChecksum() ?: throw ActionFailedException("Unable to find Gradle version $name") } suspend fun GradleVersion.Dynamic.findUrl(): GradleDistribution { val url = "https://services.gradle.org/versions/$apiPath" val response = HttpClient().getJson(url, HTTP_AGENT).await() if (response.statusCode.unsafeCast() != HttpCodes.OK) { throw ActionFailedException("Unable to lookup $url Gradle version: ${response.statusCode}, ${JSON.stringify(response.result)}") } if (response.result?.version != null) { return response.result.unsafeCast().resolveChecksum() } if (this is GradleVersion.ReleaseCandidate) { return GradleVersion.Current.findUrl() } throw ActionFailedException("Empty result from $url: ${JSON.stringify(response.result)}") } suspend fun GradleVersionResponse.resolveChecksum() = GradleDistribution( version = version, distributionUrl = downloadUrl, distributionSha256Sum = HttpClient().get(checksumUrl, HTTP_AGENT).await().readBody().await().trim(), ) suspend fun findVersionFromWrapper(projectPath: String, enableDistributionSha256SumWarning: Boolean): GradleDistribution { val gradleWrapperProperties = path.join(projectPath, "gradle", "wrapper", "gradle-wrapper.properties") if (!exists(gradleWrapperProperties)) { warning("Gradle wrapper configuration is not found at ${path.resolve(gradleWrapperProperties)}.\nWill use the current release Gradle version") return GradleVersion.Current.findUrl() } val propString = readFile(gradleWrapperProperties, BufferEncoding.utf8) val props = javaproperties.parseString(propString).run { getKeys().associateWith { getFirst(it)!! } } val distributionUrl = props.getValue("distributionUrl") val distributionSha256Sum = props["distributionSha256Sum"] val version = distributionUrl.substringAfterLast("/") .substringAfter("gradle-") .removeSuffix("-all.zip") .removeSuffix("-bin.zip") .removeSuffix(".zip") if (enableDistributionSha256SumWarning && distributionSha256Sum == null) { warning( "distributionSha256Sum is not set in $gradleWrapperProperties.\n" + "Please consider adding the checksum, " + "see https://docs.gradle.org/current/userguide/gradle_wrapper.html#configuring_checksum_verification", ) } return if (distributionUrl.removePrefix("https").removePrefix("http") .startsWith("://services.gradle.org/") ) { // Official release, use shorter version // https://services.gradle.org/distributions-snapshots/gradle-6.7-20200730220045+0000-all.zip // https://services.gradle.org/distributions/gradle-6.6-rc-4-all.zip // https://services.gradle.org/distributions/gradle-6.5.1-all.zip if (distributionUrl.endsWith("-bin.zip") && distributionSha256Sum != null) { GradleDistribution(version, distributionUrl, distributionSha256Sum) } else { // Resolve checksum from the official site // This would switch to -bin distribution which is smaller GradleVersion.Official(version).findUrl() } } else { GradleDistribution(version, distributionUrl, distributionSha256Sum) } } ================================================ FILE: gradle-launcher/src/jsMain/kotlin/com/github/burrunan/launcher/GradleLauncher.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher import actions.core.AnnotationProperties import actions.core.ExitCode import actions.core.setFailed import actions.core.setOutput import actions.exec.ExecListeners import actions.exec.ExecOptions import actions.exec.exec import com.github.burrunan.launcher.internal.GradleErrorCollector import com.github.burrunan.launcher.internal.GradleOutErrorCollector import node.process.process class GradleResult( val buildScanUrl: String?, ) suspend fun launchGradle(params: LaunchParams): GradleResult { var buildScanUrl: String? = null // See https://youtrack.jetbrains.com/issue/KT-41107 var failureDetected = false val errorCollector = GradleErrorCollector() val outCollector = GradleOutErrorCollector() @Suppress("REDUNDANT_SPREAD_OPERATOR_IN_NAMED_FORM_IN_FUNCTION") val result = exec( params.gradle, args = *(if (params.daemon) emptyList() else listOf("--no-daemon") + params.properties.map { "-P${it.key}=${it.value}" } + params.arguments).toTypedArray(), ) { ExecOptions.copy( it, cwd = params.projectPath, ignoreReturnCode = true, listeners = ExecListeners( stdline = { val str = it.trimEnd() if (str.startsWith("https://gradle.com/s/")) { setOutput("build-scan-url", str) buildScanUrl = str } outCollector.process(str) }, errline = { errorCollector.process(it) outCollector.process(it) }, ) ) } errorCollector.done() outCollector.done() for (error in errorCollector.errors + outCollector.errors) { failureDetected = true val shortFile = error.file ?.removePrefix(process.cwd()) actions.core.error( error.message, AnnotationProperties( file = shortFile, startLine = error.line, startColumn = error.col, ), ) } if (failureDetected) { process.exitCode = ExitCode.Failure.unsafeCast() } if (!failureDetected && result.exitCode != 0) { setFailed("Gradle process finished with a non-zero exit code: ${result.exitCode}") } return GradleResult(buildScanUrl) } ================================================ FILE: gradle-launcher/src/jsMain/kotlin/com/github/burrunan/launcher/GradleVersion.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher sealed class GradleVersion(val name: String, unused: Int = 0) { companion object { val DYNAMIC_VERSIONS = listOf( Current, ReleaseCandidate, Nightly, ReleaseNightly, ) val FIXED_VERSIONS = DYNAMIC_VERSIONS + Wrapper } abstract class Dynamic(label: String, val apiPath: String) : GradleVersion(label) class Official(label: String) : GradleVersion(label) { override fun toString() = "Official($name)" } object Current : Dynamic("current", "current") { override fun toString() = "Current" } object ReleaseCandidate : Dynamic("rc", "release-candidate") { override fun toString() = "ReleaseCandidate" } object Nightly : Dynamic("nightly", "nightly") { override fun toString() = "Nightly" } object ReleaseNightly : Dynamic("release-nightly", "release-nightly") { override fun toString() = "ReleaseNightly" } object Wrapper : GradleVersion("wrapper") { override fun toString() = "Wrapper" } } fun GradleVersion(version: String) = GradleVersion.FIXED_VERSIONS.firstOrNull { it.name == version } ?: GradleVersion.Official(version) ================================================ FILE: gradle-launcher/src/jsMain/kotlin/com/github/burrunan/launcher/GradleVersionResponse.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher external interface GradleVersionResponse { val version: String val downloadUrl: String val checksumUrl: String } ================================================ FILE: gradle-launcher/src/jsMain/kotlin/com/github/burrunan/launcher/LaunchParams.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher class LaunchParams( val gradle: String, val daemon: Boolean, val projectPath: String, val arguments: List, val properties: Map, ) ================================================ FILE: gradle-launcher/src/jsMain/kotlin/com/github/burrunan/launcher/internal/GradleError.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher.internal class GradleError( val message: String, val file: String? = null, val line: Int? = null, val col: Int? = null, ) { override fun toString() = "GradleError(line=$line, col=$col, file=$file, message='$message')" } ================================================ FILE: gradle-launcher/src/jsMain/kotlin/com/github/burrunan/launcher/internal/GradleErrorCollector.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher.internal private enum class ErrorHeader(val message: String) { FAILURE("FAILURE: "), WHERE("* Where:"), WHAT_WENT_WRONG("* What went wrong:"), TRY("* Try:"), } private val errorHeaderValues = ErrorHeader.values() class GradleErrorCollector { private val _errors = mutableListOf() val errors: List = _errors private val sb = StringBuilder() private var nextKey: ErrorHeader? = null private val data = mutableMapOf() fun done() { if (data.isNotEmpty()) { val message = data[ErrorHeader.WHAT_WENT_WRONG] ?: "Unknown error" _errors += data[ErrorHeader.WHERE]?.let { location -> Regex("^Build file '(.+)' line: (\\d+)$").matchEntire(location)?.let { GradleError( message = message, file = it.groupValues[1], line = it.groupValues[2].toInt(), ) } } ?: GradleError(message) } data.clear() sb.clear() } fun process(line: String) { val str = line.trimEnd() if (str.startsWith(ErrorHeader.FAILURE.message)) { done() data[ErrorHeader.FAILURE] = str.removePrefix(ErrorHeader.FAILURE.message) return } if (str.startsWith("* Get more help") || str.startsWith("BUILD FAILED ") ) { done() nextKey = null return } errorHeaderValues.firstOrNull { str.startsWith(it.message) }?.let { nextKey?.let { data[it] = sb.toString().trimEnd() } sb.clear() nextKey = it return } if (nextKey != null) { sb.appendLine(line) } } } ================================================ FILE: gradle-launcher/src/jsMain/kotlin/com/github/burrunan/launcher/internal/GradleOutErrorCollector.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher.internal // e: /../build.gradle.kts:62:1: Unresolved reference: invalid private val KOTLIN_COMPILE_ERROR = Regex("^e: (\\S.+?):(\\d+):(?:(\\d+):)? (.+)$") // [ant:checkstyle] [ERROR] /.../SqlHopTableFunction.java:56:35: ',' is not followed by whitespace. [WhitespaceAfter] private val CHECKSTYLE_ERROR = Regex("^\\[ant:checkstyle\\] \\[ERROR\\] (\\S.+?):(\\d+):(?:(\\d+):)? (.+) \\[([^\\]]+)\\]\$") // /.../RelDataType.java:249: error: reference not found private val JAVA_ERROR = Regex("^(\\S.+?):(\\d+): error: (.+)$") class GradleOutErrorCollector { private val _errors = mutableListOf() val errors: List = _errors private var taskName: String = "Unknown task" private var javaError: MatchResult? = null private val javaErrorLines = mutableListOf() fun process(line: String) { if (line.startsWith("> Task ") || line.startsWith("> Configure") ) { taskName = line.removePrefix("> ").let { "[$it]" } } if (line.startsWith("e: ")) { // Looks like Kotlin error // e: /../build.gradle.kts:62:1: Unresolved reference: invalid KOTLIN_COMPILE_ERROR.matchEntire(line)?.let { _errors += GradleError( message = "$taskName ${it.groupValues[4]}", file = it.groupValues[1], line = it.groupValues[2].toInt(), col = it.groupValues[3].takeIf { it.isNotBlank() }?.toInt(), ) } return } // Checkstyle error: // [ant:checkstyle] [ERROR] /.../SqlHopTableFunction.java:56:35: ',' is not followed by whitespace. [WhitespaceAfter] CHECKSTYLE_ERROR.matchEntire(line)?.let { _errors += GradleError( message = "$taskName ${"[${it.groupValues[5]}] ".removePrefix("[] ")}${it.groupValues[4]}", file = it.groupValues[1], line = it.groupValues[2].toInt(), col = it.groupValues[3].takeIf { it.isNotBlank() }?.toInt(), ) } processJavaError(line) } private fun processJavaError(line: String) { // /.../RelDataType.java:249: error: reference not found JAVA_ERROR.matchEntire(line)?.let { done() javaError = it return } if (javaError != null) { val errorContinuation = line.startsWith(" ") if (errorContinuation) { javaErrorLines += line } if (!errorContinuation || javaErrorLines.size >= 3) { done() } } } fun done() { javaError?.let { _errors += GradleError( message = "$taskName ${it.groupValues[3]}\n${javaErrorLines.joinToString("\n")}", file = it.groupValues[1], line = it.groupValues[2].toInt(), ) } javaError = null javaErrorLines.clear() } } ================================================ FILE: gradle-launcher/src/jsTest/kotlin/com/github/burrunan/launcher/PropertiesParserTest.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher import kotlin.test.Test import kotlin.test.assertEquals class PropertiesParserTest { @Test fun simpleProperties() { assertParse( mapOf( "a-b" to "1", "c" to "34 34", "url" to "https://example.com", ), """ a-b=1 url=https\://example.com # asfd c = 34 34 """.trimIndent(), ) } private fun assertParse(expected: Map, value: String) { assertEquals( expected, javaproperties.parseString(value).run { getKeys().associateWith { get(it) } }, value, ) } } ================================================ FILE: gradle-launcher/src/jsTest/kotlin/com/github/burrunan/launcher/RetrieveGradleVersionTest.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher import actions.core.info import actions.tool.cache.findAllVersions import com.github.burrunan.test.runTest import kotlin.test.Test class RetrieveGradleVersionTest { @Test fun retrieve() = runTest { for (version in GradleVersion.DYNAMIC_VERSIONS) { val res = version.findUrl() println("$version => $res") } } @Test fun listTools() = runTest { listTool("gradle") listTool("Gradle") listTool("mvn") listTool("Maven") } private fun listTool(tool: String) { info("All $tool versions: ${findAllVersions(tool).joinToString(", ")}") } } ================================================ FILE: gradle-launcher/src/jsTest/kotlin/com/github/burrunan/launcher/internal/GradleErrorCollectorTest.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher.internal import kotlin.test.Test import kotlin.test.assertEquals class GradleErrorCollectorTest { @Test fun buildScriptFailure() { testCollector( """ GradleError(line=62, col=null, file=/home/runner/work/pgjdbc/pgjdbc/build.gradle.kts, message='Script compilation errors: Line 62: invalid code here ^ Unresolved reference: invalid Line 62: invalid code here ^ Unresolved reference: here 2 errors') """.trimIndent(), """ * Where: Build file '/home/runner/work/pgjdbc/pgjdbc/build.gradle.kts' line: 62 * What went wrong: Script compilation errors: Line 62: invalid code here ^ Unresolved reference: invalid Line 62: invalid code here ^ Unresolved reference: here 2 errors * Try: Run with --stacktrace option to get the stack trace. Run with --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 44s """.trimIndent(), ) } @Test fun noLocation() { testCollector( """ GradleError(line=null, col=null, file=null, message='Task 'asdfasdf' not found in root project 'pgjdbc'.') """.trimIndent(), """ See https://docs.gradle.org/6.3/userguide/command_line_interface.html#sec:command_line_warnings FAILURE: Build failed with an exception. * What went wrong: Task 'asdfasdf' not found in root project 'pgjdbc'. * Try: Run gradle tasks to get a list of available tasks. Run with --stacktrace option to get the stack trace. Run with --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 37s """.trimIndent(), ) } private fun testCollector(expected: String, input: String) { val collector = GradleErrorCollector() input.lines().forEach { collector.process(it) } collector.done() assertEquals(expected, collector.errors.joinToString("\n")) } } ================================================ FILE: gradle-launcher/src/jsTest/kotlin/com/github/burrunan/launcher/internal/GradleOutCollectorTest.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.launcher.internal import kotlin.test.Test import kotlin.test.assertEquals class GradleOutCollectorTest { @Test fun koltlinCompileErrors() { testCollector( """ GradleError(line=62, col=1, file=/home/runner/work/pgjdbc/pgjdbc/build.gradle.kts, message='[Configure project :] Unresolved reference: invalid') GradleError(line=62, col=14, file=/home/runner/work/pgjdbc/pgjdbc/build.gradle.kts, message='[Configure project :] Unresolved reference: here') """.trimIndent(), """ > Configure project : Evaluating root project 'pgjdbc' using build file '/home/runner/work/pgjdbc/pgjdbc/build.gradle.kts'. Loading cache entry 'cache/eila5i6e0q7sxpvg89w345ymz' from S3 bucket e: /home/runner/work/pgjdbc/pgjdbc/build.gradle.kts:62:1: Unresolved reference: invalid e: /home/runner/work/pgjdbc/pgjdbc/build.gradle.kts:62:14: Unresolved reference: here """.trimIndent(), ) } @Test fun checkstyleError() { testCollector( """ GradleError(line=56, col=35, file=/Users/runner/work/calcite/calcite/core/src/main/java/org/apache/calcite/sql/SqlHopTableFunction.java, message='[Task :core:checkstyleMain] [WhitespaceAfter] ',' is not followed by whitespace.') GradleError(line=32, col=null, file=/code/calcite/linq4j/src/main/java/org/apache/calcite/linq4j/AbstractEnumerable.java, message='[Task :core:checkstyleMain] [Indentation] 'method def modifier' has incorrect indentation level 0, expected level should be 2.') """.trimIndent(), """ > Task :core:checkstyleMain [ant:checkstyle] [ERROR] /Users/runner/work/calcite/calcite/core/src/main/java/org/apache/calcite/sql/SqlHopTableFunction.java:56:35: ',' is not followed by whitespace. [WhitespaceAfter] [ant:checkstyle] [ERROR] /code/calcite/linq4j/src/main/java/org/apache/calcite/linq4j/AbstractEnumerable.java:32: 'method def modifier' has incorrect indentation level 0, expected level should be 2. [Indentation] """.trimIndent(), ) } @Test fun javadocError() { testCollector( """ GradleError(line=249, col=null, file=/Users/../type/RelDataType.java, message='[Task :babel:javadoc] reference not found * {@link #equals(Object)}. ^') """.trimIndent(), """ > Task :babel:javadoc /Users/runner/runners/2.263.0/work/calcite/calcite/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java:632: warning: no @param for rel public List getAverageColumnSizesNotNull(RelNode rel) { ^ /Users/runner/runners/2.263.0/work/calcite/calcite/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java:632: warning: no @return public List getAverageColumnSizesNotNull(RelNode rel) { ^ /Users/../type/RelDataType.java:249: error: reference not found * {@link #equals(Object)}. ^ """.trimIndent(), ) } @Test fun javacError() { testCollector( """ GradleError(line=46, col=null, file=/home/runner/../ReaderInputStreamTest.java, message='[Task :compileJava] cannot find symbol Arrays.fill(acutal, (byte) 0x00); ^ symbol: variable acutal') """.trimIndent(), """ > Task :compileJava Compiling with JDK Java compiler API. /home/runner/../ReaderInputStreamTest.java:46: error: cannot find symbol Arrays.fill(acutal, (byte) 0x00); ^ symbol: variable acutal location: class ReaderInputStreamTest Note: Some input files use or override a deprecated API. Note: Recompile with -Xlint:deprecation for details. """.trimIndent(), ) } private fun testCollector(expected: String, input: String) { val collector = GradleOutErrorCollector() input.lines().forEach { collector.process(it) } collector.done() assertEquals(expected, collector.errors.joinToString("\n")) } } ================================================ FILE: gradle.properties ================================================ # # Copyright 2020 Vladimir Sitnikov # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # org.gradle.parallel=true kotlin.daemon.jvmargs=-Xmx1500m kotlin.parallel.tasks.in.project=true kotlin.code.style=official ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line set CLASSPATH= @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: hashing/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ plugins { kotlin("plugin.serialization") } kotlin { sourceSets { jsMain { dependencies { implementation(projects.wrappers.actionsToolkit) implementation(projects.wrappers.js) implementation(projects.wrappers.nodejs) implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") } } } } ================================================ FILE: hashing/src/jsMain/kotlin/com/github/burrunan/hashing/HashDetails.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.hashing import actions.core.ActionFailedException import actions.core.warning import com.github.burrunan.wrappers.nodejs.normalizedPath import com.github.burrunan.wrappers.nodejs.pipeAndWait import js.promise.await import kotlinx.serialization.Serializable import node.crypto.BinaryToTextEncoding import node.crypto.createHash import node.fs.createReadStream import node.fs.stat import node.process.process @Serializable class HashDetails( val info: HashInfo, val contents: HashContents, ) @Serializable class HashInfo( val totalBytes: Long, val hash: String, val totalFiles: Int, ) @Serializable class HashContents( val files: Map, ) @Serializable class FileDetails( val fileSize: Long, val hash: String, ) /** * Sample: ~/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.0.18/5f65affce1684999e2f4024983835efc3504012e/error_prone_annotations-2.0.18.jar */ private fun sha1FromModulesFileName(key: String): String { val lastSlash = key.lastIndexOf('/') val hashStart = key.lastIndexOf('/', startIndex = lastSlash - 1) + 1 return key.substring(hashStart, lastSlash).padStart(40, '0') } suspend fun hashFilesDetailed( vararg paths: String, algorithm: String = "sha1", includeFileName: Boolean = true, ): HashDetails = try { val globber = actions.glob.create(paths.joinToString("\n")) val fileNames = globber.glob().await() // Sorting is needed for stable overall hash fileNames.sort() val githubWorkspace = process.cwd() val homeDir = "~".normalizedPath var totalBytes = 0L val files = mutableMapOf() val overallHash = createHash(algorithm) for (name in fileNames) { val statSync = stat(name) if (statSync.isDirectory()) { continue } val fileSize = statSync.size.toLong() totalBytes += fileSize val key = when { name.startsWith(githubWorkspace) -> "ws://" + name.substring(githubWorkspace.length) name.startsWith(homeDir) -> "~" + name.substring(homeDir.length) else -> name }.replace('\\', '/') // Avoid hashing the contents when we know the hash from the file path val digest = when { algorithm == "sha1" && key.startsWith("~/.gradle/caches/modules-2/files-2.1/") -> sha1FromModulesFileName(key) key.startsWith("~/.gradle/caches/build-cache-1/") -> key.substringAfterLast('/') else -> { val hash = createHash(algorithm) try { createReadStream(name).pipeAndWait(hash, end = true) } catch (e: Throwable) { warning("Unable to hash $name, will ignore the file: ${e.stackTraceToString()}") continue } hash.digest(BinaryToTextEncoding.hex) } } files[key] = FileDetails(fileSize, digest) // Add filename if (includeFileName) { overallHash.update(key) } overallHash.update(digest) } HashDetails( HashInfo(totalBytes, overallHash.digest(BinaryToTextEncoding.hex), files.size), HashContents(files), ) } catch (e: Throwable) { throw ActionFailedException("Unable to hash ${paths.joinToString(", ")}: $e", e) } ================================================ FILE: hashing/src/jsMain/kotlin/com/github/burrunan/hashing/diff.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.hashing import com.github.burrunan.formatBytes class Diff( val newFiles: Int, val totalUpdated: Long, val totalDeleted: Long, val messages: List, val updatedFiles: List, val deletedFiles: List, ) { val summary: String get() = (if (updatedFiles.isNotEmpty()) "${updatedFiles.size} updates (${totalUpdated.formatBytes()})" else "") + (if (deletedFiles.isNotEmpty()) (if (updatedFiles.isNotEmpty()) ", " else "") + "${deletedFiles.size} deletes (${totalDeleted.formatBytes()})" else "") + "\n " + messages.joinToString("\n ") } fun diff( oldContents: HashContents, newContents: HashContents, maxUpdatesToPrint: Int = 50, ): Diff { val messages = mutableListOf() val updatedFiles = mutableListOf() val deletedFiles = mutableListOf() var newFiles = 0 var totalUpdated = 0L var totalDeleted = 0L for ((file, hash) in newContents.files) { val oldHash = oldContents.files[file] if (oldHash?.hash == hash.hash) { continue } updatedFiles.add(file) if (messages.size >= maxUpdatesToPrint) { continue } messages.add( if (oldHash == null) { newFiles += 1 totalUpdated += hash.fileSize "N ${hash.fileSize} $file ${hash.hash}" } else { totalUpdated += hash.fileSize "U ${hash.fileSize} $file ${oldHash.fileSize} ${oldHash.hash} => ${hash.hash}" }, ) } if (oldContents.files.size + newFiles != newContents.files.size) { for ((file, hash) in oldContents.files) { if (file !in newContents.files) { deletedFiles.add(file) totalDeleted += hash.fileSize if (messages.size < maxUpdatesToPrint) { messages.add("D $file $hash") } } } } return Diff( newFiles = newFiles, totalUpdated = totalUpdated, totalDeleted = totalDeleted, messages = messages, updatedFiles = updatedFiles, deletedFiles = deletedFiles, ) } ================================================ FILE: hashing/src/jsMain/kotlin/com/github/burrunan/hashing/hashFiles.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.hashing import actions.core.ActionFailedException import actions.core.warning import com.github.burrunan.wrappers.nodejs.normalizedPath import com.github.burrunan.wrappers.nodejs.pipeAndWait import js.promise.await import node.WritableStream import node.buffer.BufferEncoding import node.crypto.BinaryToTextEncoding import node.crypto.createHash import node.fs.createReadStream import node.fs.stat import node.process.process data class HashResult( val hash: String, val numFiles: Int, val totalBytes: Int, ) suspend fun hashFiles( vararg paths: String, algorithm: String = "sha1", includeFileName: Boolean = true, ): HashResult = try { val globber = actions.glob.create(paths.joinToString("\n")) val fileNames = globber.glob().await() fileNames.sort() val githubWorkspace = process.cwd() val homeDir = "~".normalizedPath val hash = createHash(algorithm) var totalBytes = 0 var numFiles = 0 for (name in fileNames) { val statSync = stat(name) if (statSync.isDirectory()) { continue } val key = when { name.startsWith(githubWorkspace) -> "ws://" + name.substring(githubWorkspace.length) name.startsWith(homeDir) -> "~" + name.substring(homeDir.length) else -> name }.replace('\\', '/') numFiles += 1 totalBytes += statSync.size.toInt() // Add filename try { createReadStream(name).pipeAndWait(hash, end = false) } catch (e: Throwable) { warning("Unable to hash $name, will ignore the file: ${e.stackTraceToString()}") continue } if (includeFileName) { hash.update(key, BufferEncoding.utf8) } } hash.unsafeCast().end() HashResult( hash = hash.digest(BinaryToTextEncoding.hex), numFiles = numFiles, totalBytes = totalBytes, ) } catch (e: Throwable) { throw ActionFailedException("Unable to hash ${paths.joinToString(", ")}: $e", e) } ================================================ FILE: layered-cache/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ plugins { kotlin("plugin.serialization") } kotlin { sourceSets { jsMain { dependencies { implementation(projects.gradleLauncher) implementation(projects.hashing) implementation(projects.wrappers.actionsCache) implementation(projects.wrappers.actionsToolkit) implementation(projects.wrappers.nodejs) implementation(projects.wrappers.octokitWebhooks) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json") } } jsTest { dependencies { implementation(projects.cacheServiceMock) } } } } ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/GradleCacheAction.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle import actions.core.ActionFailedException import actions.core.ActionStage import actions.core.info import actions.exec.exec import com.github.burrunan.gradle.cache.* import com.github.burrunan.gradle.github.suspendingStateVariable import com.github.burrunan.launcher.GradleDistribution import octokit.ActionsTrigger import kotlin.js.Date import kotlin.math.roundToInt class GradleCacheAction( val trigger: ActionsTrigger, val params: Parameters, val gradleDistribution: GradleDistribution, ) { companion object { const val DEFAULT_BRANCH_VAR = "defaultbranch" } private val treeId = suspendingStateVariable("tree_id") { // Sometimes the output might include a newline, so trim it // See https://github.com/burrunan/gradle-cache-action/issues/63 exec("git", "log", "-1", "--quiet", "--format=%T", captureOutput = true).stdout.trim() } suspend fun execute(stage: ActionStage) { val gradleVersion = gradleDistribution.version val caches = mutableListOf() if (params.generatedGradleJars) { caches.add(gradleGeneratedJarsCache(gradleVersion)) } if (params.localBuildCache) { caches.add(localBuildCache(params.jobId, trigger, gradleVersion, treeId.get())) } if (params.gradleDependenciesCache) { caches.add(gradleDependenciesCache(trigger, params.path, params.gradleDependenciesCacheKey)) } if (params.mavenDependenciesCache) { caches.add(mavenDependenciesCache(trigger, params.path, params.mavenLocalIgnorePaths)) } val cache = CompositeCache("all-caches", caches, concurrent = params.concurrent) when (stage) { ActionStage.MAIN -> { val started = Date.now() val restore = cache.restore() val elapsed = Date.now() - started info("Cache restore took ${(elapsed / 1000).roundToInt()} seconds") } ActionStage.POST -> { if (params.readOnly) { info("read-only == true, so will skip cache upload") } else { cache.save() } } else -> throw ActionFailedException("Cache action should be called in PRE or POST stages only. " + "Current stage is $stage") } } } ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/Parameters.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle data class Parameters( val jobId: String, val path: String, val debug: Boolean, val generatedGradleJars: Boolean, val localBuildCache: Boolean, val gradleDependenciesCache: Boolean, val gradleDependenciesCacheKey: List, val mavenDependenciesCache: Boolean, val mavenLocalIgnorePaths: List, val concurrent: Boolean, val readOnly: Boolean, ) ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/cache/ActionsTriggerExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import actions.core.ActionsEnvironment import com.github.burrunan.gradle.GradleCacheAction import octokit.ActionsTrigger val ActionsTrigger.cacheKey: String get() = when (this) { is ActionsTrigger.PullRequest -> "PR${event.pull_request.number}" is ActionsTrigger.BranchPush -> when (val ref = event.ref.removePrefix("refs/heads/")) { event.repository.default_branch.removePrefix("refs/heads/") -> GradleCacheAction.DEFAULT_BRANCH_VAR else -> ref } is ActionsTrigger.Schedule, is ActionsTrigger.WorkflowDispatch -> GradleCacheAction.DEFAULT_BRANCH_VAR is ActionsTrigger.Other -> "$name-${ActionsEnvironment.GITHUB_WORKFLOW}-${ActionsEnvironment.GITHUB_SHA}" } ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/cache/Cache.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import actions.cache.RestoreType interface Cache { val name: String suspend fun save() suspend fun restore(): RestoreType } ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/cache/CompositeCache.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import actions.cache.RestoreType import actions.core.ext.group import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope class CompositeCache( override val name: String, private val caches: List, private val concurrent: Boolean, ) : Cache { override suspend fun save() { if (!concurrent) { for (cache in caches) { group("Save ${cache.name}") { cache.save() } } return } supervisorScope { for (cache in caches) { launch { cache.save() } } } } override suspend fun restore(): RestoreType { if (!concurrent) { for (cache in caches) { group("Restore ${cache.name}") { cache.restore() } } return RestoreType.Unknown } supervisorScope { for (cache in caches) { launch { cache.restore() } } } return RestoreType.Unknown } } ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/cache/DefaultCache.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import actions.cache.RestoreType import actions.cache.restoreAndLog import actions.cache.saveAndLog import actions.core.debug import actions.core.info import actions.glob.removeFiles import com.github.burrunan.formatBytes import com.github.burrunan.gradle.github.stateVariable import com.github.burrunan.gradle.github.toBoolean import com.github.burrunan.gradle.github.toInt import com.github.burrunan.hashing.HashContents import com.github.burrunan.hashing.HashDetails import com.github.burrunan.hashing.HashInfo import com.github.burrunan.hashing.hashFilesDetailed import com.github.burrunan.wrappers.nodejs.exists import kotlin.math.absoluteValue class DefaultCache( name: String, val primaryKey: String, private val restoreKeys: List = listOf(), private val paths: List, private val readOnlyMessage: String? = null, stateKey: String = "", private val skipRestoreIfPathExists: String? = null ) : Cache { @Suppress("CanBePrimaryConstructorProperty") override val name: String = name private val version = "1-" private val cacheInfo = MetadataFile("$name-info", HashInfo.serializer()) private val cacheContents = MetadataFile("$name-contents", HashContents.serializer()) private val saveRestorePaths = paths + cacheInfo.cachedName + cacheContents.cachedName private val isExactMatch = stateVariable("${name}_${stateKey}_exact").toBoolean() private val isSkipped = stateVariable("${name}_${stateKey}_skip").toBoolean() private val restoredKeyIndex = stateVariable("${name}_${stateKey}_key").toInt(-1) private val restoredKey: String? get() = when { isExactMatch.get() -> primaryKey restoredKeyIndex.get() >= 0 -> restoreKeys[restoredKeyIndex.get()] else -> null } private var details: HashDetails? = null suspend fun info(): HashInfo? { details?.info?.let { return it } restoredKey?.let { cacheContents.prepare(it) } if (!isExactMatch.get() && restoredKeyIndex.get() == -1) { // Cache was not restored => no information known return null } return cacheInfo.decode() } suspend fun contents(): HashContents? { details?.contents?.let { return it } restoredKey?.let { cacheContents.prepare(it) } if (!isExactMatch.get() && restoredKeyIndex.get() == -1) { // Cache was not restored => no information known return null } return cacheContents.decode() } override suspend fun restore(): RestoreType { skipRestoreIfPathExists?.let { if (exists(it)) { debug { "$name: $it already exists, so the cache restore and upload will be skipped" } isSkipped.set(true) } } debug { "$name: restoring $primaryKey, $restoreKeys, $saveRestorePaths" } return restoreAndLog(saveRestorePaths, primaryKey, restoreKeys, version = version).also { isExactMatch.set(it is RestoreType.Exact) restoredKeyIndex.set( when (it) { is RestoreType.Partial -> restoreKeys.indexOfFirst { key -> it.path.startsWith(key) } else -> -1 }, ) debug { "$name: restore type $it, ${isExactMatch.get()}, ${restoredKeyIndex.get()}" } restoredKey?.let { key -> cacheInfo.restore(key) cacheContents.restore(key) } } } override suspend fun save() { debug { "$name: saving ${isExactMatch.get()} ${restoredKeyIndex.get()} $primaryKey, $restoreKeys, $saveRestorePaths" } if (isSkipped.get()) { debug { "$name: cache save skipped" } return } if (isExactMatch.get()) { info("$name loaded from exact match, no need to update the cache entry") return } readOnlyMessage?.let { info("$name is configured as read-only: $it") return } restoredKey?.let { key -> cacheInfo.prepare(key) cacheContents.prepare(key) } val oldHash = info() val newHash = hashFilesDetailed(*paths.toTypedArray()) details = newHash if (newHash.contents.files.isEmpty()) { info("$name: no files to cache => won't upload empty cache") return } if (oldHash != null) { info("$name: comparing modifications of the cache contents") if (newHash.info.hash == oldHash.hash) { info("$name: contents did not change => no need to upload it") return } val delta = newHash.info.totalBytes - oldHash.totalBytes info("$name: hash content differs (${delta.absoluteValue} bytes ${if (delta >= 0) "increase" else "decrease"})") } info("$name: uploading ${newHash.info.totalBytes.formatBytes()}, ${newHash.contents.files.size} files as $primaryKey") cacheInfo.encode(newHash.info) cacheContents.encode(newHash.contents) saveAndLog(saveRestorePaths, primaryKey, version = version) } } ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/cache/GradleGeneratedJarsCache.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache fun gradleGeneratedJarsCache(gradleVersion: String): Cache = DefaultCache( name = "gradle-generated-jars", primaryKey = "generated-gradle-jars-gradle-$gradleVersion", paths = listOf( "~/.gradle/caches/$gradleVersion/generated-gradle-jars/*", "!~/.gradle/caches/$gradleVersion/generated-gradle-jars/*.lock", ), skipRestoreIfPathExists = "~/.gradle/caches/$gradleVersion/generated-gradle-jars", ) ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/cache/LayeredCache.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import actions.cache.RestoreType import actions.core.ActionFailedException import actions.core.debug import actions.core.info import actions.core.warning import actions.glob.removeFiles import com.github.burrunan.formatBytes import com.github.burrunan.gradle.github.stateVariable import com.github.burrunan.gradle.github.toBoolean import com.github.burrunan.hashing.* import kotlinx.serialization.Serializable @Serializable class CacheLayers( val layers: List, val deletedFiles: List, ) @Serializable class CacheLayer( // format is $version-... val primaryKey: String, val paths: List, ) class LayeredCache( override val name: String, private val baseline: String, val maxLayers: Int = 5, private val primaryKey: String, private val restoreKeys: List = listOf(), private val paths: List, ) : Cache { private val version = "1" private val layers = MetadataFile("layer-$name", CacheLayers.serializer()) private val isExactMatch = stateVariable("${name}_exact").toBoolean() private val index = DefaultCache( name = "$version-index-$name", primaryKey = "$version-index-$primaryKey", restoreKeys = restoreKeys.map { "$version-index-$it" }, paths = listOf(layers.cachedName), ) override fun toString(): String = "Cache $name, primaryKey=$primaryKey, restoreKeys=$restoreKeys, " private fun CacheLayer.toCache(stateKey: String) = DefaultCache( name = name, stateKey = stateKey, primaryKey = primaryKey, restoreKeys = if (paths.isNotEmpty()) listOf() else restoreKeys.map { "$version-$it" }, paths = this@toCache.paths.ifEmpty { this@LayeredCache.paths }, ) private fun Diff.toLayer(): CacheLayer { // @actions/cache treats "paths" as a part of the cache key, so "delta-" is not important here for correctness // delta- is here for readability return CacheLayer( primaryKey = "$version-delta-$primaryKey", paths = updatedFiles, ) } override suspend fun restore(): RestoreType { val indexRestoreType = index.restore() if (indexRestoreType == RestoreType.None) { return RestoreType.None } val cacheIndex = layers.decode() ?: run { warning("Unable to restore cache $this") return RestoreType.Unknown } var restoreType: RestoreType = when (indexRestoreType) { is RestoreType.Exact -> RestoreType.Exact(indexRestoreType.path.removePrefix("$version-index-")) is RestoreType.Partial -> RestoreType.Partial(indexRestoreType.path.removePrefix("$version-index-")) else -> indexRestoreType } info( cacheIndex.layers.joinToString(", ", "$name: ${cacheIndex.layers.size} layers. ") { if (it.paths.isEmpty()) it.primaryKey else "${it.primaryKey} (${it.paths.size} files)" }, ) // Restore layers one by one, so newer layers can overwrite the old files for ((index, layer) in cacheIndex.layers.withIndex()) { val cache = layer.toCache(index.toString()) val restore = cache.restore() if (restore !is RestoreType.Exact) { restoreType = RestoreType.Unknown } debug { "$name: layer $index, restore=$restore" } } removeFiles(cacheIndex.deletedFiles) isExactMatch.set(restoreType is RestoreType.Exact) return restoreType } override suspend fun save() { if (isExactMatch.get()) { info("$name loaded from exact match, no need to update the cache entry") return } val cacheIndex = layers.decode(warnOnMissing = false) val isBaseline = primaryKey.startsWith(baseline) if (cacheIndex == null) { if (!isBaseline) { info("$name: old contents is not found, and the current cache $primaryKey does not start with $baseline, so cache saving can't be done") return } saveSingleLayerCache() return } // PR: keep all baseline layers, add one new for PR // branch: val caches = cacheIndex.layers .mapIndexed { index, cacheLayer -> cacheLayer.toCache(index.toString()) } val oldContents = caches.associateWith { it.contents() } if (isBaseline) { val missing = oldContents.mapNotNull { (cache, contents) -> if (contents == null) cache.primaryKey else null } if (missing.isNotEmpty()) { info("$name: there are missing layers: $missing") saveSingleLayerCache() return } if (cacheIndex.layers.size > maxLayers) { info("$name: ${cacheIndex.layers.size} layers reached, will create new snapshot") saveSingleLayerCache() return } } // non-baseline if (!isBaseline) { val firstLayer = cacheIndex.layers.firstOrNull() val firstBaseline = caches.firstOrNull { it.name.startsWith(baseline) }?.name if (firstLayer?.primaryKey?.startsWith("$version-$baseline") != true) { info("$name: the first baseline is not found, and the current cache $primaryKey does not start with $version-$baseline, so cache saving can't be done") return } if (oldContents.values.firstOrNull() == null) { info("$name: the first baseline $firstBaseline was not received, and the current cache $primaryKey does not start with $baseline, so cache saving can't be done") return } } val newContents = hashFilesDetailed(*paths.toTypedArray()) val oldFiles = mutableMapOf() val reusedFiles = mutableMapOf() val deletedFiles = mutableSetOf() val newLayers = mutableListOf() val layerInfo = mutableListOf() for ((layer, contents) in cacheIndex.layers.zip(oldContents.values)) { if (contents == null) { info("$name: unknown contents for layer ${layer.primaryKey}") continue } var helpfulBytes = 0L var wastedBytes = 0L val helpfulLayerFiles = mutableMapOf() val deletedLayerFiles = mutableListOf() for ((file, details) in contents.files) { val newDetails = newContents.contents.files[file] if (details.hash == newDetails?.hash && file !in reusedFiles) { // same file => previous layer is helpful helpfulBytes += details.fileSize helpfulLayerFiles[file] = details } else { // file is different or removed => previous layer is not useful if (newDetails == null) { deletedLayerFiles.add(file) } wastedBytes += details.fileSize } } if (wastedBytes >= helpfulBytes) { // Too much waste => remove the layer info("$name: layer ${layer.primaryKey} has too much waste (${wastedBytes.formatBytes()} > ${helpfulBytes.formatBytes()}), so the layer will be skipped") continue } oldFiles += contents.files reusedFiles += helpfulLayerFiles deletedFiles += deletedLayerFiles newLayers += layer layerInfo += "${layer.primaryKey} ${(helpfulBytes + wastedBytes).formatBytes()} total (${contents.files.size} files), ${wastedBytes.formatBytes()} outdated" } if (!isBaseline && newLayers.isEmpty()) { info("$name: at least one layer from the default branch is needed. The new contents is ${newContents.info.totalBytes.formatBytes()} (${newContents.info.totalFiles} files)") return } val diff = diff(HashContents(oldFiles), newContents.contents) if (diff.messages.isNotEmpty()) { info("$name: cache contents is changed: ${diff.summary}") } val layer = diff.toLayer() val cache = layer.toCache("newlayer") // TODO: reuse HashDetails when saving cache cache.save() newLayers += layer layerInfo += "${layer.primaryKey} ${diff.totalUpdated.formatBytes()} total (${diff.updatedFiles.size} files), ${diff.totalDeleted.formatBytes()} deleted (${diff.deletedFiles.size} files)" info(layerInfo.joinToString("; ", "$name: ${newLayers.size} layers. ")) layers.encode(CacheLayers(newLayers, deletedFiles = deletedFiles.toList())) // Save the index index.save() } private suspend fun saveSingleLayerCache() { info("$name: creating single-layer cache image") val layer = CacheLayer( primaryKey = "$version-$primaryKey", paths = listOf(), ) val cache = layer.toCache("single-layer") cache.save() if (cache.info()?.totalFiles == 0) { // cache is empty => skip creating empty file return } layers.encode(CacheLayers(listOf(layer), deletedFiles = listOf())) index.save() } } ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/cache/MetadataFile.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import actions.core.warning import com.github.burrunan.wrappers.nodejs.exists import com.github.burrunan.wrappers.nodejs.normalizedPath import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import node.buffer.BufferEncoding import node.fs.* class MetadataFile(name: String, private val serializer: KSerializer, private val extension: String = ".json") { companion object { const val ROOT_FOLDER = "~/.gradle-cache-action" val SPECIAL_CHARS = Regex("""[!@#$%^&*:;'"{}\r\n\[\]\\]""") init { val path = ROOT_FOLDER.normalizedPath if (!existsSync(path)) { try { mkdirSync(path) } catch (ignored: Throwable) { } } } } val cachedName = "$ROOT_FOLDER/$name$extension" private var uniqueName = cachedName.normalizedPath fun prepare(key: String) { uniqueName = "${cachedName.normalizedPath}.${key.replace('/', '-')}" } suspend fun restore(key: String) { val path = cachedName.normalizedPath if (exists(path)) { prepare(key) rename(path, uniqueName) } else { warning("$cachedName: $path does not exist") } } suspend fun decode(warnOnMissing: Boolean = true): T? { if (!exists(uniqueName)) { if (warnOnMissing) { warning("$cachedName: $uniqueName does not exist") } return null } return try { Json.decodeFromString( serializer, readFile(uniqueName, BufferEncoding.utf8) ) } catch (e: SerializationException) { warning("$cachedName: error deserializing $uniqueName with ${serializer.descriptor.serialName}, message: $e") return null } } suspend fun encode(value: T) { writeFile( cachedName.normalizedPath, Json.encodeToString(serializer, value), BufferEncoding.utf8, ) } } ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/cache/dependenciesCache.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import actions.core.ActionsEnvironment import actions.core.debug import com.github.burrunan.gradle.GradleCacheAction import com.github.burrunan.gradle.github.suspendingStateVariable import com.github.burrunan.hashing.hashFiles import octokit.ActionsTrigger /** * Populate cache only when building a default branch, otherwise treat the cache as read-only. */ suspend fun dependenciesCache( name: String, trigger: ActionsTrigger, cacheLocation: List, pathDependencies: List, ): Cache { val defaultBranch = GradleCacheAction.DEFAULT_BRANCH_VAR val pkPrefix = trigger.cacheKey val cacheName = "dependencies-$name" // Avoid re-computing the hash for saving the cache val dependencyDeclarationHash = suspendingStateVariable(cacheName) { hashFiles(*pathDependencies.toTypedArray()).hash } debug { "$cacheName: dependencyDeclarationHash=${dependencyDeclarationHash.get()}" } val prefix = "dependencies-$name-${ActionsEnvironment.RUNNER_OS}" return LayeredCache( name = cacheName, baseline = prefix, primaryKey = "$prefix-$pkPrefix-${dependencyDeclarationHash.get()}", restoreKeys = listOf( "$prefix-$pkPrefix", "$prefix-$defaultBranch", "$prefix-master", "$prefix-main", ), paths = cacheLocation, ) } suspend fun gradleDependenciesCache(trigger: ActionsTrigger, path: String, gradleDependenciesCacheKey: List): Cache = dependenciesCache( "gradle", trigger, cacheLocation = listOf( "~/.gradle/caches/modules-2/*", "!~/.gradle/caches/modules-2/gc.properties", "!~/.gradle/caches/modules-2/modules-2.lock", ), pathDependencies = listOf( "$path/**/*.gradle", "$path/**/*.gradle.kts", "$path/**/gradle/dependency-locking/**", "$path/**/*.properties", "$path/**/gradle/libs.versions.toml", ) + gradleDependenciesCacheKey.map { (if (it.startsWith("!")) "!" else "") + "$path/**/" + it.trim().trimStart('!') } + // Excludes must go the last so they win listOf("!$path/**/.gradle/"), ) suspend fun mavenDependenciesCache(trigger: ActionsTrigger, path: String, mavenLocalIgnorePaths: List): Cache = dependenciesCache( "maven", trigger, cacheLocation = listOf("~/.m2/repository") + mavenLocalIgnorePaths.map { "!~/.m2/repository/$it" }, pathDependencies = listOf( "$path/**/pom.xml", ), ) ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/cache/localBuildCache.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.cache import com.github.burrunan.gradle.GradleCacheAction import octokit.ActionsTrigger fun localBuildCache(jobId: String, trigger: ActionsTrigger, gradleVersion: String, treeId: String): Cache { val buildCacheLocation = "~/.gradle/caches/build-cache-1" val defaultBranch = GradleCacheAction.DEFAULT_BRANCH_VAR val pkPrefix = trigger.cacheKey val restoreKeys = when (trigger) { is ActionsTrigger.PullRequest -> arrayOf( pkPrefix, trigger.event.pull_request.base.ref.removePrefix("refs/heads/"), ) is ActionsTrigger.BranchPush -> arrayOf( pkPrefix, ) else -> arrayOf() } + arrayOf( defaultBranch, "master", "main", ) val prefix = "gradle-build-cache-$jobId-gradle-$gradleVersion" return LayeredCache( name = "local-build-cache", baseline = "$prefix-$defaultBranch", primaryKey = "$prefix-$pkPrefix-$treeId", restoreKeys = restoreKeys.map { "$prefix-$it" }, paths = listOf( "$buildCacheLocation/*", "!$buildCacheLocation/gc.properties", "!$buildCacheLocation/build-cache-1.lock", ), ) } ================================================ FILE: layered-cache/src/jsMain/kotlin/com/github/burrunan/gradle/github/StateExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.gradle.github import actions.core.getState import actions.core.saveState interface MutableStateVariable { fun set(value: T) } open class BaseStateVariable(protected val name: String) : MutableStateVariable { protected var value: T? = null override fun set(value: T) { this.value = value saveState(name, value) } } interface SuspendingStateVariable : MutableStateVariable { suspend fun get(): T } class DefaultSuspendingStateVariable(name: String, val default: suspend () -> String) : BaseStateVariable(name), SuspendingStateVariable { override suspend fun get(): String = value ?: getState(name).ifBlank { default() }.also { set(it) } } interface StateVariable : MutableStateVariable { fun get(): T } class DefaultStateVariable(name: String, private val default: () -> String) : BaseStateVariable(name), StateVariable { override fun get(): String = value ?: getState(name).ifBlank { default() }.also { set(it) } } fun suspendingStateVariable(name: String, default: suspend () -> String): SuspendingStateVariable = DefaultSuspendingStateVariable(name, default) fun stateVariable(name: String, default: () -> String = { "" }): StateVariable = DefaultStateVariable(name, default) fun StateVariable.transform(decode: (T) -> R, encode: (R) -> T) = object : StateVariable { override fun get(): R = decode(this@transform.get()) override fun set(value: R) { this@transform.set(encode(value)) } } fun StateVariable.toBoolean() = transform({ it == "Y" }, { if (it) "Y" else "N" }) fun StateVariable.toInt(default: Int) = transform({ if (it.isBlank()) default else it.toInt() }, { it.toString() }) fun StateVariable.toLong(default: Long) = transform({ if (it.isBlank()) default else it.toLong() }, { it.toString() }) ================================================ FILE: layered-cache/src/jsTest/kotlin/com/github/burrunan/gradle/CacheServerTest.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.github.burrunan.gradle import actions.cache.RestoreType import actions.cache.restoreAndLog import actions.cache.saveAndLog import com.github.burrunan.gradle.cache.CacheService import com.github.burrunan.gradle.cache.LayeredCache import com.github.burrunan.test.runTest import com.github.burrunan.wrappers.nodejs.mkdir import node.buffer.BufferEncoding import node.fs.readFile import node.fs.unlink import node.fs.writeFile import kotlin.test.Test import kotlin.test.assertEquals class CacheServerTest { val cacheService = CacheService() @Test fun saveCache() = runTest { val dir = "saveCache" mkdir(dir) val file = "$dir/cached.txt" val contents = "hello, world" writeFile(file, contents, BufferEncoding.utf8) val patterns = listOf("$dir/**") val primaryKey = "linux-gradle-feature/123123" cacheService { saveAndLog(patterns, primaryKey, "1-") unlink(file) assertEquals( RestoreType.Exact(primaryKey), restoreAndLog( patterns, primaryKey, restoreKeys = listOf("linux-gradle-", "linux-"), version = "1-", ), "Cache restored from exact match", ) assertEquals( readFile(file, BufferEncoding.utf8), contents, "Contents after restore should match", ) assertEquals( RestoreType.Partial(primaryKey), restoreAndLog( patterns, "asdf$primaryKey", restoreKeys = listOf("linux-gradle-", "linux-"), version = "1-", ), "PK not found => restored from restoreKeys", ) assertEquals( readFile(file, BufferEncoding.utf8), contents, "Contents after restore should match", ) } } @Test fun layeredCacheTest() = runTest { val dir = "saveCache" mkdir(dir) val file = "$dir/cached.txt" val contents = "hello, world" writeFile(file, contents, BufferEncoding.utf8) val patterns = listOf("$dir/**") val primaryKey = "prefix-gradle-features/cool/123123" val cache = LayeredCache( "test-cache", "prefix-", primaryKey = primaryKey, restoreKeys = listOf( "prefix-gradle-", "prefix-", ), paths = patterns ) cacheService { assertEquals( RestoreType.None, cache.restore(), "No data -> RestoreType.None" ) cache.save() assertEquals( RestoreType.Exact(primaryKey), cache.restore(), "Restore after saving exact cache -> RestoreType.Exact" ) } } } ================================================ FILE: layered-cache/src/jsTest/kotlin/com/github/burrunan/gradle/GlobTest.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.github.burrunan.gradle import com.github.burrunan.formatBytes import com.github.burrunan.hashing.hashFilesDetailed import com.github.burrunan.test.runTest import com.github.burrunan.wrappers.nodejs.mkdir import node.buffer.BufferEncoding import node.fs.writeFile import node.path.path import kotlin.test.Test import kotlin.test.assertEquals class GlobTest { @Test fun glob() = runTest { val dirName = "globTest" val dotGradle = path.join(dirName, ".gradle") mkdir(dirName) mkdir(dotGradle) writeFile(path.join(dirName, "settings.gradle"), "a", BufferEncoding.utf8) writeFile(path.join(dirName, "good.txt"), "a", BufferEncoding.utf8) writeFile(path.join(dirName, "bad.txt"), "a", BufferEncoding.utf8) writeFile(path.join(dotGradle, "extra.txt"), "a", BufferEncoding.utf8) val hash = hashFilesDetailed( "$dirName/**/*.gradle", "$dirName/**/*.txt", "!$dirName/**/.gradle/", "!$dirName/**/*bad**", ) val actual = hash.contents.files.entries.joinToString("\n") { (file, details) -> "${details.fileSize.formatBytes()} ${details.hash} $file" } assertEquals( """ 1 B 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 ws:///globTest/good.txt 1 B 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 ws:///globTest/settings.gradle """.trimIndent(), actual, ) } } ================================================ FILE: layered-cache/src/jsTest/resources/readme.txt ================================================ ================================================ FILE: renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:best-practices" ], "automerge": true, "platformAutomerge": true, "labels": ["dependencies"], "schedule": ["every 1 weeks on Monday"] } ================================================ FILE: settings.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ pluginManagement { plugins { kotlin("multiplatform") version "2.3.0" kotlin("plugin.serialization") version "2.3.0" } } dependencyResolutionManagement { repositories { mavenCentral() } } rootProject.name = "gradle-cache-action" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") include( "cache-service-mock", "cache-proxy", "gradle-launcher", "hashing", "layered-cache", "cache-action-entrypoint", "test-library", "wrappers:js", "wrappers:nodejs", "wrappers:actions-toolkit", "wrappers:actions-cache", "wrappers:java-properties", "wrappers:octokit-request-error", "wrappers:octokit-types", "wrappers:octokit-webhooks" ) ================================================ FILE: test-library/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { api(kotlin("test-js")) implementation(projects.wrappers.nodejs) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") } } } } ================================================ FILE: test-library/src/jsMain/kotlin/com/github/burrunan/test/testExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.test import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.promise import node.process.process fun runTest(block: suspend () -> Unit): dynamic = GlobalScope.promise { process.env["RUNNER_OS"] = "macos" block() } ================================================ FILE: wrappers/actions-cache/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { api("org.jetbrains.kotlin-wrappers:kotlin-actions-cache") implementation(projects.wrappers.actionsToolkit) } } } } ================================================ FILE: wrappers/actions-cache/src/jsMain/kotlin/actions/cache/CacheExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.cache import actions.core.LogLevel import actions.core.info import actions.core.log import actions.core.warning suspend fun restoreAndLog( paths: List, primaryKey: String, restoreKeys: List = listOf(), version: String, logLevel: LogLevel = LogLevel.INFO, ): RestoreType { val result = try { when { restoreKeys.isEmpty() -> restoreCache(paths.toTypedArray(), version + primaryKey) else -> restoreCache( paths.toTypedArray(), version + primaryKey, restoreKeys.map { version + it }.toTypedArray(), ) } } catch (t: Throwable) { when (t.asDynamic().name) { "ValidationError" -> throw t else -> { warning("Error while loading $primaryKey: ${t.message}") return RestoreType.None } } } result?.removePrefix(version)?.let { return if (it.endsWith(primaryKey)) RestoreType.Exact(it) else RestoreType.Partial(it) } log(logLevel) { "Cache was not found for version=$version, primaryKey=$primaryKey, restore keys=${restoreKeys.joinToString(", ")}" } return RestoreType.None } suspend fun saveAndLog( paths: List, key: String, version: String, logLevel: LogLevel = LogLevel.INFO, ) { try { saveCache(paths.toTypedArray(), version + key) } catch (t: Throwable) { // TODO: propagate error when (t.asDynamic().name) { "ValidationError" -> throw t "ReserveCacheError" -> info(t.message ?: "Unknown ReserveCacheError") else -> when { t.message?.contains("Cache already exists") == true -> log(logLevel) { "Error while uploading $key: ${t.message}" } else -> warning("Error while uploading $key: ${t.message}") } } } } ================================================ FILE: wrappers/actions-cache/src/jsMain/kotlin/actions/cache/RestoreType.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.cache sealed class RestoreType { data class Exact(val path: String) : RestoreType() data class Partial(val path: String) : RestoreType() object None : RestoreType() { override fun toString() = "None" } object Unknown : RestoreType() { override fun toString() = "Unknown" } } ================================================ FILE: wrappers/actions-cache/src/jsMain/kotlin/actions/cache/internal/CacheContract.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:JsModule("@actions/cache/lib/internal/constants") package actions.cache.internal import actions.cache.HttpClientError external interface GetCacheParams { val keys: String val version: String } external interface ReserveCacheRequest { val key: String val version: String? var cacheSize: Number? } external interface ReserveCacheResponse { var cacheId: Number } external interface ArtifactCacheEntry { var cacheKey: String? var scope: String? var creationTime: String? var archiveLocation: String? } external interface CommitCacheRequest { val size: Number } external interface InternalCacheOptions { var compressionMethod: CompressionMethod? var cacheSize: Number? } external enum class CompressionMethod { Gzip, // Long range mode was added to zstd in v1.3.2. // This enum is for earlier version of zstd that does not have --long support ZstdWithoutLong, Zstd } external interface ITypedResponse { var statusCode: Number var result: T? var headers: Any } external interface ITypedResponseWithError: ITypedResponse { val error: HttpClientError? } ================================================ FILE: wrappers/actions-cache/src/jsMain/kotlin/actions/cache/internal/httpclient.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:JsModule("@actions/cache/lib/internal/cacheHttpClient") @file:JsNonModule package actions.cache.internal import actions.cache.DownloadOptions import actions.cache.UploadOptions import actions.cache.internal.ArtifactCacheEntry import actions.cache.internal.InternalCacheOptions import kotlin.js.Promise external fun getCacheEntry( keys: Array, paths: Array, options: InternalCacheOptions = definedExternally ): Promise external fun reserveCache( key: String, paths: Array, options: InternalCacheOptions = definedExternally ): Promise> external fun saveCache( cacheId: Number, archivePath: String, options: UploadOptions? = definedExternally, ): Promise external fun downloadCache( archiveLocation: String, archivePath: String, options: DownloadOptions? = definedExternally, ): Promise ================================================ FILE: wrappers/actions-cache/src/jsMain/kotlin/actions/cache/types.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.cache typealias ValidationError = Error typealias ReserveCacheError = Error typealias HttpClientError = Error ================================================ FILE: wrappers/actions-toolkit/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { api(projects.wrappers.nodejs) api("org.jetbrains.kotlin-wrappers:kotlin-actions-core") api("org.jetbrains.kotlin-wrappers:kotlin-actions-exec") api("org.jetbrains.kotlin-wrappers:kotlin-actions-glob") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") } } } } ================================================ FILE: wrappers/actions-toolkit/src/jsMain/kotlin/actions/core/ActionFailedException.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.core class ActionFailedException(override val message: String, cause: Throwable? = null) : Throwable(message, cause) ================================================ FILE: wrappers/actions-toolkit/src/jsMain/kotlin/actions/core/ActionStage.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.core enum class ActionStage { PRE, MAIN, POST } ================================================ FILE: wrappers/actions-toolkit/src/jsMain/kotlin/actions/core/ActionsEnvironment.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.core import node.process.process import kotlin.reflect.KProperty /** * See https://docs.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables */ object ActionsEnvironment { val HOME by Environment val GITHUB_WORKFLOW by Environment val GITHUB_RUN_ID by Environment val GITHUB_RUN_NUMBER by Environment val GITHUB_ACTION by Environment val GITHUB_ACTOR by Environment val GITHUB_REPOSITORY by Environment val GITHUB_EVENT_NAME by Environment val GITHUB_EVENT_PATH by Environment val GITHUB_WORKSPACE by Environment val GITHUB_SHA by Environment val GITHUB_REF by Environment val GITHUB_HEAD_REF by Environment val GITHUB_BASE_REF by Environment val GITHUB_SERVER_URL by Environment val GITHUB_API_URL by Environment val GITHUB_GRAPHQL_URL by Environment val RUNNER_OS by Environment } private object Environment { operator fun getValue(environment: Any, property: KProperty<*>): String = process.env[property.name] ?: throw ActionFailedException("${property.name} is not found in process.env") } ================================================ FILE: wrappers/actions-toolkit/src/jsMain/kotlin/actions/core/LogLevel.kt ================================================ package actions.core enum class LogLevel { DEBUG, INFO, NONE, } ================================================ FILE: wrappers/actions-toolkit/src/jsMain/kotlin/actions/core/LoggingExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.core inline fun debug(message: () -> String) { if (isDebug()) { debug(message()) } } inline fun log(logLevel: LogLevel, message: () -> String) { when(logLevel) { LogLevel.DEBUG -> debug(message) LogLevel.INFO -> info(message()) LogLevel.NONE -> Unit } } ================================================ FILE: wrappers/actions-toolkit/src/jsMain/kotlin/actions/core/ext/Group.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.core.ext import actions.core.endGroup import actions.core.startGroup inline fun group(name: String, action: () -> T): T { startGroup(name) try { return action() } finally { endGroup() } } ================================================ FILE: wrappers/actions-toolkit/src/jsMain/kotlin/actions/core/ext/InputExtensions.kt ================================================ package actions.core.ext import actions.core.InputOptions fun getInput(name: String, required: Boolean = false): String = actions.core.getInput(name, InputOptions(required = required)) private val LINE_SEPARATOR = Regex("[\r\n]+") fun getListInput(name: String, required: Boolean = false): List = actions.core.getInput(name, InputOptions(required = required)) .split(LINE_SEPARATOR) .map { it.trim() } .filterNot { it.isEmpty() } ================================================ FILE: wrappers/actions-toolkit/src/jsMain/kotlin/actions/exec/ExecExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.exec class ExecResult( val exitCode: Int, val stdout: String, val stderr: String, ) suspend fun exec( commandLine: String, vararg args: String, captureOutput: Boolean = false, options: (ExecOptions) -> ExecOptions = { it } ): ExecResult { val stdout = mutableListOf() val stderr = mutableListOf() val execOptions = if (!captureOutput) { ExecOptions() } else { ExecOptions( listeners = ExecListeners( stdout = { // it.toString() results in [...] for unknown reason stdout.add("" + it.unsafeCast()) }, stderr = { // it.toString() results in [...] for unknown reason stderr.add("" + it.unsafeCast()) } ) ) } val exitCode = exec( commandLine, args.copyOf(), options(execOptions), ) return ExecResult( exitCode = exitCode.toInt(), stdout = stdout.joinToString("\n"), stderr = stderr.joinToString("\n") ) } ================================================ FILE: wrappers/actions-toolkit/src/jsMain/kotlin/actions/glob/removeFiles.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package actions.glob import js.promise.await import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import node.fs.unlink suspend fun removeFiles(files: List) { if (files.isEmpty()) { return } val globber = create(files.joinToString("\n")) val fileNames = globber.glob().await() supervisorScope { for (file in fileNames) { launch { unlink(file) } } } } ================================================ FILE: wrappers/java-properties/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { api(npm("java-properties", "1.0.2")) } } } } ================================================ FILE: wrappers/java-properties/src/jsMain/kotlin/javaproperties/index.module_java-properties.kt ================================================ @file:JsModule("java-properties") @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package javaproperties import kotlin.js.Json open external class PropertiesFile(vararg args: String) { open var objs: Json open fun makeKeys(line: String) open fun addFile(file: String) open fun of(vararg args: String) open fun get(key: String, defaultValue: String = definedExternally): dynamic /* String? | Array? */ open fun getLast(key: String, defaultValue: String = definedExternally): String? open fun getFirst(key: String, defaultValue: String = definedExternally): String? open fun getInt(key: String, defaultIntValue: Number = definedExternally): Number? open fun getFloat(key: String, defaultFloatValue: Number = definedExternally): Number? open fun getBoolean(key: String, defaultBooleanValue: Boolean = definedExternally): Boolean open fun set(key: String, value: String) open fun interpolate(s: String): String open fun getKeys(): Array open fun getMatchingKeys(matchstr: String): Array open fun reset() } external fun of(args: Any): PropertiesFile ================================================ FILE: wrappers/java-properties/src/jsMain/kotlin/javaproperties/parseString.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package javaproperties private val NEWLINE = Regex("\\s*[\r\n]+\\s*") fun parseString(text: String) = PropertiesFile().apply { for(line in text.split(NEWLINE)) { makeKeys(line) } } ================================================ FILE: wrappers/js/src/jsMain/kotlin/com/github/burrunan/formatBytes.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan fun Long.formatBytes() = when { this < 5 * 1024 -> "${this} B" this < 5 * 1024 * 1204 -> "${(this + 512L) / (1024L)} KiB" this < 5L * 1024 * 1204 * 1024 -> "${(this + 512L * 1024) / (1024L * 1024)} MiB" else -> "${(this + 512L * 1024 * 1024) / (1024L * 1024 * 1024)} GiB" } ================================================ FILE: wrappers/js/src/jsMain/kotlin/com/github/burrunan/wrappers/js/SuspendExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.wrappers.js import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine suspend inline fun suspendWithCallback(crossinline block: ((Throwable?) -> Unit) -> Unit) = suspendCoroutine { cont -> block.invoke { error -> when (error) { null -> cont.resume(null) else -> cont.resumeWithException(error) } } } suspend inline fun suspendWithCallback(crossinline block: ((Throwable?, result: T) -> Unit) -> Unit) = suspendCoroutine { cont -> block.invoke { error, result -> when (error) { null -> cont.resume(result) else -> cont.resumeWithException(error) } } } ================================================ FILE: wrappers/nodejs/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { api(projects.wrappers.js) api("org.jetbrains.kotlin-wrappers:kotlin-node") } } } } ================================================ FILE: wrappers/nodejs/src/jsMain/kotlin/com/github/burrunan/wrappers/nodejs/FsExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.wrappers.nodejs import js.objects.unsafeJso import node.fs.MakeDirectoryOptions import node.fs.existsSync import node.fs.mkdir suspend fun mkdir(path: String) { if (!exists(path)) { mkdir(path, unsafeJso { recursive = true }) } } @Deprecated(message = "catch errors instead", level = DeprecationLevel.WARNING) fun exists(path: String) = existsSync(path.normalizedPath) val String.normalizedPath: String get() = when { startsWith("~") -> node.os.homedir() + substring(1) else -> this } ================================================ FILE: wrappers/nodejs/src/jsMain/kotlin/com/github/burrunan/wrappers/nodejs/StreamExtensions.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.burrunan.wrappers.nodejs import js.objects.unsafeJso import node.ReadableStream import node.WritableStream import node.buffer.Buffer import node.stream.Readable import node.stream.consumers.buffer import node.stream.consumers.json import node.stream.finished suspend fun Readable.readJson(): T = json(this) as T suspend fun Readable.readToBuffer(): Buffer<*> = buffer(this) suspend fun T.pipeAndWait(destination: D, end : Boolean = false) { pipe(destination = destination, options = unsafeJso { this.end = end }) finished(this) } ================================================ FILE: wrappers/octokit-request-error/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { api(projects.wrappers.octokitTypes) } } } } ================================================ FILE: wrappers/octokit-request-error/src/jsMain/kotlin/octokit/requesterror/index.module_@octokit_request-error.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.requesterror typealias RequestError = Error ================================================ FILE: wrappers/octokit-request-error/src/jsMain/kotlin/octokit/requesterror/types.module_@octokit_request-error.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.requesterror import octokit.types.RequestOptions import octokit.types.ResponseHeaders external interface RequestErrorOptions { var headers: ResponseHeaders? var request: RequestOptions } ================================================ FILE: wrappers/octokit-types/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { api(projects.wrappers.nodejs) } } } } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/AuthInterface.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "DEPRECATION") package octokit.types import kotlin.js.Promise external interface AuthInterfaceHook { @nativeInvoke operator fun invoke(request: RequestInterface__0, options: RequestParameters /* RequestParameters & `T$6` */): Promise> @nativeInvoke operator fun invoke(request: RequestInterface__0, route: Route, parameters: RequestParameters = definedExternally): Promise> } external interface AuthInterface, Authentication : Any?> { @nativeInvoke operator fun invoke(vararg args: AuthOptions): Promise var hook: AuthInterfaceHook } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/EndpointDefaults.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types external interface EndpointDefaultsRequestHeader: RequestHeaders { } external interface EndpointDefaultsMediaType { var format: String var previews: Array } external interface EndpointDefaults { var baseUrl: Url var method: String /* "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT" */ var url: Url? var headers: EndpointDefaultsRequestHeader var mediaType: EndpointDefaultsMediaType } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/EndpointInterface.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "DEPRECATION") package octokit.types external interface `T$4` { var method: String? } external interface MergeFunction { @nativeInvoke operator fun

invoke(route: Any, parameters: P = definedExternally): D /* D & Any & P */ @nativeInvoke operator fun <@Suppress("FINAL_UPPER_BOUND") R : Route, P : RequestParameters> invoke(route: R, parameters: P = definedExternally): D /* D & Any & P */ @nativeInvoke operator fun

invoke(options: P): RequestParameters /* RequestParameters & `T$3` */ @nativeInvoke operator fun invoke(): D /* D & RequestParameters */ } external interface EndpointInterface { @nativeInvoke operator fun invoke(options: O /* O & `T$4` & Any */): RequestOptions /* RequestOptions & Pick */ @nativeInvoke operator fun

invoke(route: Any, parameters: P = definedExternally): Any /* Any & Any */ @nativeInvoke operator fun <@Suppress("FINAL_UPPER_BOUND") R : Route, P : RequestParameters> invoke(route: R, parameters: P = definedExternally): Any /* Any & Any */ var DEFAULTS: D /* D & RequestParameters */ // var defaults: (newDefaults: O) -> EndpointInterface var defaults: EndpointInterfaceDefaults var merge: MergeFunction var parse: EndpointInterfaceOptionParser } external interface EndpointInterfaceDefaults { @nativeInvoke operator fun invoke(newDefaults: O): EndpointInterface } external interface EndpointInterfaceOptionParser { @nativeInvoke operator fun invoke(options: O): RequestOptions /*& Pick*/ } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/EndpointOptions.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types external interface `T$6` { var method: String /* "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT" */ var url: Url } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/Fetch.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types typealias Fetch = Any ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/GetResponseTypeFromEndpointMethod.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types typealias Unwrap<@Suppress("UNUSED_TYPEALIAS_PARAMETER") T> = Any typealias AnyFunction = (args: Any) -> Any typealias GetResponseTypeFromEndpointMethod<@Suppress("UNUSED_TYPEALIAS_PARAMETER") T> = Unwrap*/> typealias GetResponseDataTypeFromEndpointMethod<@Suppress("UNUSED_TYPEALIAS_PARAMETER") T> = Any ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/OctokitResponse.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types external interface OctokitResponse { var headers: ResponseHeaders var status: Number var url: Url var data: T } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/RequestError.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types external interface `T$7` { var resource: String var code: String var field: String var message: String? } external interface RequestError { var name: String var status: Number var documentation_url: String var errors: Array<`T$7`>? } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/RequestHeaders.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "DEPRECATION") package octokit.types external interface RequestHeaders { var accept: String? var authorization: String? @nativeGetter operator fun get(header: String): Any? /* String? | Number? */ @nativeSetter operator fun set(header: String, value: String?) @nativeSetter operator fun set(header: String, value: Number?) } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/RequestInterface.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "DEPRECATION") package octokit.types import kotlin.js.Promise external interface RequestInterface { @nativeInvoke operator fun invoke(options: O /* O & `T$4` & Any */): Promise> @nativeInvoke operator fun invoke(route: Any, options: Any = definedExternally): Any @nativeInvoke operator fun <@Suppress("FINAL_UPPER_BOUND") R : Route> invoke(route: R, options: Any = definedExternally): Any // var defaults: (newDefaults: O) -> RequestInterface var defaults: RequestInterfaceDefaults var endpoint: EndpointInterface } external interface RequestInterface__0 : RequestInterface external interface RequestInterfaceDefaults { @nativeInvoke operator fun invoke(newDefaults: O): RequestInterface } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/RequestOptions.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types external interface RequestOptions { var method: String /* "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT" */ var url: Url var headers: RequestHeaders var body: Any? var request: RequestRequestOptions? } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/RequestParameters.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "DEPRECATION") package octokit.types external interface `T$8` { var format: String? var previews: Array? } external interface RequestParameters { var baseUrl: Url? var headers: RequestHeaders? var mediaType: `T$8`? var request: RequestRequestOptions? @nativeGetter operator fun get(parameter: String): Any? @nativeSetter operator fun set(parameter: String, value: Any) } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/RequestRequestOptions.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "DEPRECATION") package octokit.types external interface RequestRequestOptions { var agent: Any? var fetch: Fetch? var signal: Signal? var timeout: Number? @nativeGetter operator fun get(option: String): Any? @nativeSetter operator fun set(option: String, value: Any) } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/ResponseHeaders.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "DEPRECATION") package octokit.types external interface ResponseHeaders { var date: String? var etag: String? var link: String? var location: String? var server: String? var status: String? var vary: String? @nativeGetter operator fun get(header: String): Any? /* String? | Number? */ @nativeSetter operator fun set(header: String, value: String?) @nativeSetter operator fun set(header: String, value: Number?) } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/Route.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types typealias Route = String ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/Signal.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types typealias Signal = Any ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/StrategyInterface.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "DEPRECATION") package octokit.types external interface StrategyInterface, AuthOptions : Array, Authentication : Any?> { @nativeInvoke operator fun invoke(vararg args: StrategyOptions): AuthInterface } ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/Url.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types typealias Url = String ================================================ FILE: wrappers/octokit-types/src/jsMain/kotlin/octokit/types/VERSION.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.types external var VERSION: Any ================================================ FILE: wrappers/octokit-webhooks/build.gradle.kts ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ kotlin { sourceSets { jsMain { dependencies { api(projects.wrappers.octokitRequestError) api(npm("@octokit/webhooks", "13.8.2")) implementation(projects.wrappers.actionsToolkit) } } } } ================================================ FILE: wrappers/octokit-webhooks/src/jsMain/kotlin/octokit/ActionsTrigger.kt ================================================ /* * Copyright 2020 Vladimir Sitnikov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package octokit import actions.core.ActionsEnvironment import node.buffer.BufferEncoding import node.fs.readFile import octokit.webhooks.WebhookPayloadPullRequest import octokit.webhooks.WebhookPayloadPush import octokit.webhooks.WebhookPayloadWorkflowDispatch sealed class ActionsTrigger(val name: String, open val event: Any) { class PullRequest(override val event: WebhookPayloadPullRequest) : ActionsTrigger("pull_request", event) class BranchPush(override val event: WebhookPayloadPush) : ActionsTrigger("push", event) class WorkflowDispatch(override val event: WebhookPayloadWorkflowDispatch) : ActionsTrigger("workflow_dispatch", event) class Schedule(name: String, event: Any) : ActionsTrigger(name, event) class Other(name: String, event: Any) : ActionsTrigger(name, event) } suspend fun currentTrigger(): ActionsTrigger { val eventString = readFile(ActionsEnvironment.GITHUB_EVENT_PATH, BufferEncoding.utf8) val event = JSON.parse(eventString) @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") return when (val eventName = ActionsEnvironment.GITHUB_EVENT_NAME) { "pull_request" -> ActionsTrigger.PullRequest(event as WebhookPayloadPullRequest) "push" -> ActionsTrigger.BranchPush(event as WebhookPayloadPush) "workflow_dispatch" -> ActionsTrigger.WorkflowDispatch(event as WebhookPayloadWorkflowDispatch) "schedule" -> ActionsTrigger.Schedule(eventName, event) else -> ActionsTrigger.Other(eventName, event) } } ================================================ FILE: wrappers/octokit-webhooks/src/jsMain/kotlin/octokit/webhooks/ResponseHeaders.module_@octokit_types.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "DEPRECATION") external interface ResponseHeaders { var date: String? var etag: String? var link: String? var location: String? var server: String? var status: String? var vary: String? @nativeGetter operator fun get(header: String): Any? /* String? | Number? */ @nativeSetter operator fun set(header: String, value: String?) @nativeSetter operator fun set(header: String, value: Number?) } ================================================ FILE: wrappers/octokit-webhooks/src/jsMain/kotlin/octokit/webhooks/event-payloads.EventPayloads.module_@octokit_webhooks.kt ================================================ @file:JsModule("@octokit/webhooks") @file:JsQualifier("EventPayloads") @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") package octokit.webhooks external interface WebhookPayloadWorkflowRunSender { var avatar_url: String var events_url: String var followers_url: String var following_url: String var gists_url: String var gravatar_id: String var html_url: String var id: Number var login: String var node_id: String var organizations_url: String var received_events_url: String var repos_url: String var site_admin: Boolean var starred_url: String var subscriptions_url: String var type: String var url: String } external interface WebhookPayloadWorkflowRunOrganization { var avatar_url: String var description: String var events_url: String var hooks_url: String var id: Number var issues_url: String var login: String var members_url: String var node_id: String var public_members_url: String var repos_url: String var url: String } external interface WebhookPayloadWorkflowRun { var action: String var organization: WebhookPayloadWorkflowRunOrganization var repository: PayloadRepository var sender: WebhookPayloadWorkflowRunSender } external interface WebhookPayloadWorkflowDispatchSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadWorkflowDispatchOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface WebhookPayloadWorkflowDispatchInputs external interface WebhookPayloadWorkflowDispatch { var inputs: WebhookPayloadWorkflowDispatchInputs var ref: String var repository: PayloadRepository var organization: WebhookPayloadWorkflowDispatchOrganization var sender: WebhookPayloadWorkflowDispatchSender var workflow: String } external interface WebhookPayloadWatchSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadWatch { var action: String var repository: PayloadRepository var sender: WebhookPayloadWatchSender } external interface WebhookPayloadTeamAddSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadTeamAddOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface WebhookPayloadTeamAddTeam { var name: String var id: Number var node_id: String var slug: String var description: String var privacy: String var url: String var html_url: String var members_url: String var repositories_url: String var permission: String } external interface WebhookPayloadTeamAdd { var team: WebhookPayloadTeamAddTeam var repository: PayloadRepository var organization: WebhookPayloadTeamAddOrganization var sender: WebhookPayloadTeamAddSender } external interface WebhookPayloadTeamChanges external interface WebhookPayloadTeamSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadTeamOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface PayloadRepositoryPermissions { var pull: Boolean var push: Boolean var admin: Boolean } external interface WebhookPayloadTeamTeam { var name: String var id: Number var node_id: String var slug: String var description: String? var privacy: String var url: String var html_url: String var members_url: String var repositories_url: String var permission: String } external interface WebhookPayloadTeam { var action: String var team: WebhookPayloadTeamTeam var repository: PayloadRepository? var organization: WebhookPayloadTeamOrganization var sender: WebhookPayloadTeamSender var changes: WebhookPayloadTeamChanges? } external interface WebhookPayloadStatusSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadStatusBranchesItemCommit { var sha: String var url: String } external interface WebhookPayloadStatusBranchesItem { var name: String var commit: WebhookPayloadStatusBranchesItemCommit var protected: Boolean } external interface WebhookPayloadStatusCommitCommitter { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadStatusCommitAuthor { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadStatusCommitCommitVerification { var verified: Boolean var reason: String var signature: String var payload: String } external interface WebhookPayloadStatusCommitCommitTree { var sha: String var url: String } external interface WebhookPayloadStatusCommitCommitCommitter { var name: String var email: String var date: String } external interface WebhookPayloadStatusCommitCommitAuthor { var name: String var email: String var date: String } external interface WebhookPayloadStatusCommitCommit { var author: WebhookPayloadStatusCommitCommitAuthor var committer: WebhookPayloadStatusCommitCommitCommitter var message: String var tree: WebhookPayloadStatusCommitCommitTree var url: String var comment_count: Number var verification: WebhookPayloadStatusCommitCommitVerification } external interface WebhookPayloadStatusCommit { var sha: String var node_id: String var commit: WebhookPayloadStatusCommitCommit var url: String var html_url: String var comments_url: String var author: WebhookPayloadStatusCommitAuthor var committer: WebhookPayloadStatusCommitCommitter var parents: Array } external interface WebhookPayloadStatus { var id: Number var sha: String var name: String var target_url: Any? var context: String var description: Any? var state: String var commit: WebhookPayloadStatusCommit var branches: Array var created_at: String var updated_at: String var repository: PayloadRepository var sender: WebhookPayloadStatusSender } external interface WebhookPayloadStarSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadStar { var action: String var starred_at: String? var repository: PayloadRepository var sender: WebhookPayloadStarSender } external interface WebhookPayloadSponsorshipChangesTierFrom { var node_id: String var created_at: String var description: String var monthly_price_in_cents: Number var monthly_price_in_dollars: Number var name: String } external interface WebhookPayloadSponsorshipChangesTier { var from: WebhookPayloadSponsorshipChangesTierFrom } external interface WebhookPayloadSponsorshipChanges { var tier: WebhookPayloadSponsorshipChangesTier } external interface WebhookPayloadSponsorshipSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadSponsorshipSponsorshipTier { var node_id: String var created_at: String var description: String var monthly_price_in_cents: Number var monthly_price_in_dollars: Number var name: String } external interface WebhookPayloadSponsorshipSponsorshipSponsor { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadSponsorshipSponsorshipSponsorable { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadSponsorshipSponsorship { var node_id: String var created_at: String var sponsorable: WebhookPayloadSponsorshipSponsorshipSponsorable var sponsor: WebhookPayloadSponsorshipSponsorshipSponsor var privacy_level: String var tier: WebhookPayloadSponsorshipSponsorshipTier } external interface WebhookPayloadSponsorship { var action: String var sponsorship: WebhookPayloadSponsorshipSponsorship var sender: WebhookPayloadSponsorshipSender var changes: WebhookPayloadSponsorshipChanges? var effective_date: String? } external interface WebhookPayloadSecurityAdvisorySecurityAdvisoryVulnerabilitiesItemFirstPatchedVersion { var identifier: String } external interface WebhookPayloadSecurityAdvisorySecurityAdvisoryVulnerabilitiesItemPackage { var ecosystem: String var name: String } external interface WebhookPayloadSecurityAdvisorySecurityAdvisoryVulnerabilitiesItem { var `package`: WebhookPayloadSecurityAdvisorySecurityAdvisoryVulnerabilitiesItemPackage var severity: String var vulnerable_version_range: String var first_patched_version: WebhookPayloadSecurityAdvisorySecurityAdvisoryVulnerabilitiesItemFirstPatchedVersion } external interface WebhookPayloadSecurityAdvisorySecurityAdvisoryReferencesItem { var url: String } external interface WebhookPayloadSecurityAdvisorySecurityAdvisoryIdentifiersItem { var value: String var type: String } external interface WebhookPayloadSecurityAdvisorySecurityAdvisory { var ghsa_id: String var summary: String var description: String var severity: String var identifiers: Array var references: Array var published_at: String var updated_at: String var withdrawn_at: Any? var vulnerabilities: Array } external interface WebhookPayloadSecurityAdvisory { var action: String var security_advisory: WebhookPayloadSecurityAdvisorySecurityAdvisory } external interface WebhookPayloadRepositoryVulnerabilityAlertAlertDismisser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadRepositoryVulnerabilityAlertSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadRepositoryVulnerabilityAlertAlert { var id: Number var affected_range: String var affected_package_name: String var external_reference: String var external_identifier: String var fixed_in: String var dismisser: WebhookPayloadRepositoryVulnerabilityAlertAlertDismisser? var dismiss_reason: String? var dismissed_at: String? } external interface WebhookPayloadRepositoryVulnerabilityAlert { var action: String var alert: WebhookPayloadRepositoryVulnerabilityAlertAlert var repository: PayloadRepository? var sender: WebhookPayloadRepositoryVulnerabilityAlertSender? } external interface WebhookPayloadRepositoryImportSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadRepositoryImportOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface WebhookPayloadRepositoryImport { var status: String var repository: PayloadRepository var organization: WebhookPayloadRepositoryImportOrganization var sender: WebhookPayloadRepositoryImportSender } external interface WebhookPayloadRepositoryOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface WebhookPayloadRepositorySender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadRepository { var action: String var repository: PayloadRepository var sender: WebhookPayloadRepositorySender var organization: WebhookPayloadRepositoryOrganization? } external interface WebhookPayloadRepositoryDispatchInstallation { var id: Number var node_id: String } external interface WebhookPayloadRepositoryDispatchSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadRepositoryDispatchOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface WebhookPayloadRepositoryDispatchClientPayload external interface WebhookPayloadRepositoryDispatch { var action: String var branch: String var client_payload: WebhookPayloadRepositoryDispatchClientPayload var repository: PayloadRepository var organization: WebhookPayloadRepositoryDispatchOrganization var sender: WebhookPayloadRepositoryDispatchSender var installation: WebhookPayloadRepositoryDispatchInstallation } external interface WebhookPayloadReleaseSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadReleaseReleaseAuthor { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadReleaseRelease { var url: String var assets_url: String var upload_url: String var html_url: String var id: Number var node_id: String var tag_name: String var target_commitish: String var name: Any? var draft: Boolean var author: WebhookPayloadReleaseReleaseAuthor var prerelease: Boolean var created_at: String var published_at: String var assets: Array var tarball_url: String var zipball_url: String var body: Any? } external interface WebhookPayloadRelease { var action: String var release: WebhookPayloadReleaseRelease var repository: PayloadRepository var sender: WebhookPayloadReleaseSender } external interface WebhookPayloadPushSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPushPusher { var name: String var email: String } external interface WebhookPayloadPush { var ref: String var before: String var after: String var created: Boolean var deleted: Boolean var forced: Boolean var base_ref: Any? var compare: String var commits: Array var head_commit: Any? var repository: PayloadRepository var pusher: WebhookPayloadPushPusher var sender: WebhookPayloadPushSender } external interface WebhookPayloadPullRequestReviewCommentSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewCommentPullRequestLinksStatuses { var href: String } external interface WebhookPayloadPullRequestReviewCommentPullRequestLinksCommits { var href: String } external interface WebhookPayloadPullRequestReviewCommentPullRequestLinksReviewComment { var href: String } external interface WebhookPayloadPullRequestReviewCommentPullRequestLinksReviewComments { var href: String } external interface WebhookPayloadPullRequestReviewCommentPullRequestLinksComments { var href: String } external interface WebhookPayloadPullRequestReviewCommentPullRequestLinksIssue { var href: String } external interface WebhookPayloadPullRequestReviewCommentPullRequestLinksHtml { var href: String } external interface WebhookPayloadPullRequestReviewCommentPullRequestLinksSelf { var href: String } external interface WebhookPayloadPullRequestReviewCommentPullRequestLinks { var self: WebhookPayloadPullRequestReviewCommentPullRequestLinksSelf var html: WebhookPayloadPullRequestReviewCommentPullRequestLinksHtml var issue: WebhookPayloadPullRequestReviewCommentPullRequestLinksIssue var comments: WebhookPayloadPullRequestReviewCommentPullRequestLinksComments var review_comments: WebhookPayloadPullRequestReviewCommentPullRequestLinksReviewComments var review_comment: WebhookPayloadPullRequestReviewCommentPullRequestLinksReviewComment var commits: WebhookPayloadPullRequestReviewCommentPullRequestLinksCommits var statuses: WebhookPayloadPullRequestReviewCommentPullRequestLinksStatuses } external interface WebhookPayloadPullRequestReviewCommentPullRequestBaseRepoOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewCommentPullRequestBaseRepo { var id: Number var node_id: String var name: String var full_name: String var private: Boolean var owner: WebhookPayloadPullRequestReviewCommentPullRequestBaseRepoOwner var html_url: String var description: Any? var fork: Boolean var url: String var forks_url: String var keys_url: String var collaborators_url: String var teams_url: String var hooks_url: String var issue_events_url: String var events_url: String var assignees_url: String var branches_url: String var tags_url: String var blobs_url: String var git_tags_url: String var git_refs_url: String var trees_url: String var statuses_url: String var languages_url: String var stargazers_url: String var contributors_url: String var subscribers_url: String var subscription_url: String var commits_url: String var git_commits_url: String var comments_url: String var issue_comment_url: String var contents_url: String var compare_url: String var merges_url: String var archive_url: String var downloads_url: String var issues_url: String var pulls_url: String var milestones_url: String var notifications_url: String var labels_url: String var releases_url: String var deployments_url: String var created_at: String var updated_at: String var pushed_at: String var git_url: String var ssh_url: String var clone_url: String var svn_url: String var homepage: Any? var size: Number var stargazers_count: Number var watchers_count: Number var language: String var has_issues: Boolean var has_projects: Boolean var has_downloads: Boolean var has_wiki: Boolean var has_pages: Boolean var forks_count: Number var mirror_url: Any? var archived: Boolean var disabled: Boolean var open_issues_count: Number var license: Any? var forks: Number var open_issues: Number var watchers: Number var default_branch: String var allow_squash_merge: Boolean? var allow_merge_commit: Boolean? var allow_rebase_merge: Boolean? var delete_branch_on_merge: Boolean? } external interface WebhookPayloadPullRequestReviewCommentPullRequestBaseUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewCommentPullRequestBase { var label: String var ref: String var sha: String var user: WebhookPayloadPullRequestReviewCommentPullRequestBaseUser var repo: WebhookPayloadPullRequestReviewCommentPullRequestBaseRepo } external interface WebhookPayloadPullRequestReviewCommentPullRequestHeadRepoOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewCommentPullRequestHeadRepo { var id: Number var node_id: String var name: String var full_name: String var private: Boolean var owner: WebhookPayloadPullRequestReviewCommentPullRequestHeadRepoOwner var html_url: String var description: Any? var fork: Boolean var url: String var forks_url: String var keys_url: String var collaborators_url: String var teams_url: String var hooks_url: String var issue_events_url: String var events_url: String var assignees_url: String var branches_url: String var tags_url: String var blobs_url: String var git_tags_url: String var git_refs_url: String var trees_url: String var statuses_url: String var languages_url: String var stargazers_url: String var contributors_url: String var subscribers_url: String var subscription_url: String var commits_url: String var git_commits_url: String var comments_url: String var issue_comment_url: String var contents_url: String var compare_url: String var merges_url: String var archive_url: String var downloads_url: String var issues_url: String var pulls_url: String var milestones_url: String var notifications_url: String var labels_url: String var releases_url: String var deployments_url: String var created_at: String var updated_at: String var pushed_at: String var git_url: String var ssh_url: String var clone_url: String var svn_url: String var homepage: Any? var size: Number var stargazers_count: Number var watchers_count: Number var language: String var has_issues: Boolean var has_projects: Boolean var has_downloads: Boolean var has_wiki: Boolean var has_pages: Boolean var forks_count: Number var mirror_url: Any? var archived: Boolean var disabled: Boolean var open_issues_count: Number var license: Any? var forks: Number var open_issues: Number var watchers: Number var default_branch: String var allow_squash_merge: Boolean? var allow_merge_commit: Boolean? var allow_rebase_merge: Boolean? var delete_branch_on_merge: Boolean? } external interface WebhookPayloadPullRequestReviewCommentPullRequestHeadUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewCommentPullRequestHead { var label: String var ref: String var sha: String var user: WebhookPayloadPullRequestReviewCommentPullRequestHeadUser var repo: WebhookPayloadPullRequestReviewCommentPullRequestHeadRepo } external interface WebhookPayloadPullRequestReviewCommentPullRequestUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewCommentPullRequest { var url: String var id: Number var node_id: String var html_url: String var diff_url: String var patch_url: String var issue_url: String var number: Number var state: String var locked: Boolean var title: String var user: WebhookPayloadPullRequestReviewCommentPullRequestUser var body: String var created_at: String var updated_at: String var closed_at: Any? var merged_at: Any? var merge_commit_sha: String var assignee: Any? var assignees: Array var requested_reviewers: Array var requested_teams: Array var labels: Array var milestone: Any? var commits_url: String var review_comments_url: String var review_comment_url: String var comments_url: String var statuses_url: String var head: WebhookPayloadPullRequestReviewCommentPullRequestHead var base: WebhookPayloadPullRequestReviewCommentPullRequestBase var _links: WebhookPayloadPullRequestReviewCommentPullRequestLinks var author_association: String } external interface WebhookPayloadPullRequestReviewCommentCommentLinksPullRequest { var href: String } external interface WebhookPayloadPullRequestReviewCommentCommentLinksHtml { var href: String } external interface WebhookPayloadPullRequestReviewCommentCommentLinksSelf { var href: String } external interface WebhookPayloadPullRequestReviewCommentCommentLinks { var self: WebhookPayloadPullRequestReviewCommentCommentLinksSelf var html: WebhookPayloadPullRequestReviewCommentCommentLinksHtml var pull_request: WebhookPayloadPullRequestReviewCommentCommentLinksPullRequest } external interface WebhookPayloadPullRequestReviewCommentCommentUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewCommentComment { var url: String var pull_request_review_id: Number var id: Number var node_id: String var diff_hunk: String var path: String var position: Number var original_position: Number var commit_id: String var original_commit_id: String var user: WebhookPayloadPullRequestReviewCommentCommentUser var body: String var created_at: String var updated_at: String var html_url: String var pull_request_url: String var author_association: String var _links: WebhookPayloadPullRequestReviewCommentCommentLinks } external interface WebhookPayloadPullRequestReviewComment { var action: String var comment: WebhookPayloadPullRequestReviewCommentComment var pull_request: WebhookPayloadPullRequestReviewCommentPullRequest var repository: PayloadRepository var sender: WebhookPayloadPullRequestReviewCommentSender } external interface WebhookPayloadPullRequestReviewSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewPullRequestLinksStatuses { var href: String } external interface WebhookPayloadPullRequestReviewPullRequestLinksCommits { var href: String } external interface WebhookPayloadPullRequestReviewPullRequestLinksReviewComment { var href: String } external interface WebhookPayloadPullRequestReviewPullRequestLinksReviewComments { var href: String } external interface WebhookPayloadPullRequestReviewPullRequestLinksComments { var href: String } external interface WebhookPayloadPullRequestReviewPullRequestLinksIssue { var href: String } external interface WebhookPayloadPullRequestReviewPullRequestLinksHtml { var href: String } external interface WebhookPayloadPullRequestReviewPullRequestLinksSelf { var href: String } external interface WebhookPayloadPullRequestReviewPullRequestLinks { var self: WebhookPayloadPullRequestReviewPullRequestLinksSelf var html: WebhookPayloadPullRequestReviewPullRequestLinksHtml var issue: WebhookPayloadPullRequestReviewPullRequestLinksIssue var comments: WebhookPayloadPullRequestReviewPullRequestLinksComments var review_comments: WebhookPayloadPullRequestReviewPullRequestLinksReviewComments var review_comment: WebhookPayloadPullRequestReviewPullRequestLinksReviewComment var commits: WebhookPayloadPullRequestReviewPullRequestLinksCommits var statuses: WebhookPayloadPullRequestReviewPullRequestLinksStatuses } external interface WebhookPayloadPullRequestReviewPullRequestBaseRepoOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewPullRequestBaseRepo { var id: Number var node_id: String var name: String var full_name: String var private: Boolean var owner: WebhookPayloadPullRequestReviewPullRequestBaseRepoOwner var html_url: String var description: Any? var fork: Boolean var url: String var forks_url: String var keys_url: String var collaborators_url: String var teams_url: String var hooks_url: String var issue_events_url: String var events_url: String var assignees_url: String var branches_url: String var tags_url: String var blobs_url: String var git_tags_url: String var git_refs_url: String var trees_url: String var statuses_url: String var languages_url: String var stargazers_url: String var contributors_url: String var subscribers_url: String var subscription_url: String var commits_url: String var git_commits_url: String var comments_url: String var issue_comment_url: String var contents_url: String var compare_url: String var merges_url: String var archive_url: String var downloads_url: String var issues_url: String var pulls_url: String var milestones_url: String var notifications_url: String var labels_url: String var releases_url: String var deployments_url: String var created_at: String var updated_at: String var pushed_at: String var git_url: String var ssh_url: String var clone_url: String var svn_url: String var homepage: Any? var size: Number var stargazers_count: Number var watchers_count: Number var language: String var has_issues: Boolean var has_projects: Boolean var has_downloads: Boolean var has_wiki: Boolean var has_pages: Boolean var forks_count: Number var mirror_url: Any? var archived: Boolean var disabled: Boolean var open_issues_count: Number var license: Any? var forks: Number var open_issues: Number var watchers: Number var default_branch: String var allow_squash_merge: Boolean? var allow_merge_commit: Boolean? var allow_rebase_merge: Boolean? var delete_branch_on_merge: Boolean? } external interface WebhookPayloadPullRequestReviewPullRequestBaseUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewPullRequestBase { var label: String var ref: String var sha: String var user: WebhookPayloadPullRequestReviewPullRequestBaseUser var repo: WebhookPayloadPullRequestReviewPullRequestBaseRepo } external interface WebhookPayloadPullRequestReviewPullRequestHeadRepoOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewPullRequestHeadRepo { var id: Number var node_id: String var name: String var full_name: String var private: Boolean var owner: WebhookPayloadPullRequestReviewPullRequestHeadRepoOwner var html_url: String var description: Any? var fork: Boolean var url: String var forks_url: String var keys_url: String var collaborators_url: String var teams_url: String var hooks_url: String var issue_events_url: String var events_url: String var assignees_url: String var branches_url: String var tags_url: String var blobs_url: String var git_tags_url: String var git_refs_url: String var trees_url: String var statuses_url: String var languages_url: String var stargazers_url: String var contributors_url: String var subscribers_url: String var subscription_url: String var commits_url: String var git_commits_url: String var comments_url: String var issue_comment_url: String var contents_url: String var compare_url: String var merges_url: String var archive_url: String var downloads_url: String var issues_url: String var pulls_url: String var milestones_url: String var notifications_url: String var labels_url: String var releases_url: String var deployments_url: String var created_at: String var updated_at: String var pushed_at: String var git_url: String var ssh_url: String var clone_url: String var svn_url: String var homepage: Any? var size: Number var stargazers_count: Number var watchers_count: Number var language: String var has_issues: Boolean var has_projects: Boolean var has_downloads: Boolean var has_wiki: Boolean var has_pages: Boolean var forks_count: Number var mirror_url: Any? var archived: Boolean var disabled: Boolean var open_issues_count: Number var license: Any? var forks: Number var open_issues: Number var watchers: Number var default_branch: String var allow_squash_merge: Boolean? var allow_merge_commit: Boolean? var allow_rebase_merge: Boolean? var delete_branch_on_merge: Boolean? } external interface WebhookPayloadPullRequestReviewPullRequestHeadUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewPullRequestHead { var label: String var ref: String var sha: String var user: WebhookPayloadPullRequestReviewPullRequestHeadUser var repo: WebhookPayloadPullRequestReviewPullRequestHeadRepo } external interface WebhookPayloadPullRequestReviewPullRequestUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewPullRequest { var url: String var id: Number var node_id: String var html_url: String var diff_url: String var patch_url: String var issue_url: String var number: Number var state: String var locked: Boolean var title: String var user: WebhookPayloadPullRequestReviewPullRequestUser var body: String var created_at: String var updated_at: String var closed_at: Any? var merged_at: Any? var merge_commit_sha: String var assignee: Any? var assignees: Array var requested_reviewers: Array var requested_teams: Array var labels: Array var milestone: Any? var commits_url: String var review_comments_url: String var review_comment_url: String var comments_url: String var statuses_url: String var head: WebhookPayloadPullRequestReviewPullRequestHead var base: WebhookPayloadPullRequestReviewPullRequestBase var _links: WebhookPayloadPullRequestReviewPullRequestLinks var author_association: String } external interface WebhookPayloadPullRequestReviewReviewLinksPullRequest { var href: String } external interface WebhookPayloadPullRequestReviewReviewLinksHtml { var href: String } external interface WebhookPayloadPullRequestReviewReviewLinks { var html: WebhookPayloadPullRequestReviewReviewLinksHtml var pull_request: WebhookPayloadPullRequestReviewReviewLinksPullRequest } external interface WebhookPayloadPullRequestReviewReviewUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestReviewReview { var id: Number var node_id: String var user: WebhookPayloadPullRequestReviewReviewUser var body: Any? var commit_id: String var submitted_at: String var state: String var html_url: String var pull_request_url: String var author_association: String var _links: WebhookPayloadPullRequestReviewReviewLinks } external interface WebhookPayloadPullRequestReview { var action: String var review: WebhookPayloadPullRequestReviewReview var pull_request: WebhookPayloadPullRequestReviewPullRequest var repository: PayloadRepository var sender: WebhookPayloadPullRequestReviewSender } external interface WebhookPayloadPullRequestLabel { var id: Number var node_id: String var url: String var name: String var color: String var default: Boolean } external interface WebhookPayloadPullRequestPullRequestMilestoneCreator { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface `T$75` { var url: String var html_url: String var labels_url: String var id: Number var node_id: String var number: Number var title: String var description: String var creator: WebhookPayloadPullRequestPullRequestMilestoneCreator var open_issues: Number var closed_issues: Number var state: String var created_at: String var updated_at: String var due_on: String var closed_at: String } external interface WebhookPayloadPullRequestPullRequestLabelsItem { var id: Number var node_id: String var url: String var name: String var color: String var default: Boolean } external interface `T$76` { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestInstallation { var id: Number var node_id: String } external interface WebhookPayloadPullRequestAssignee { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestPullRequestAssigneesItem { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestPullRequestLinksStatuses { var href: String } external interface WebhookPayloadPullRequestPullRequestLinksCommits { var href: String } external interface WebhookPayloadPullRequestPullRequestLinksReviewComment { var href: String } external interface WebhookPayloadPullRequestPullRequestLinksReviewComments { var href: String } external interface WebhookPayloadPullRequestPullRequestLinksComments { var href: String } external interface WebhookPayloadPullRequestPullRequestLinksIssue { var href: String } external interface WebhookPayloadPullRequestPullRequestLinksHtml { var href: String } external interface WebhookPayloadPullRequestPullRequestLinksSelf { var href: String } external interface WebhookPayloadPullRequestPullRequestLinks { var self: WebhookPayloadPullRequestPullRequestLinksSelf var html: WebhookPayloadPullRequestPullRequestLinksHtml var issue: WebhookPayloadPullRequestPullRequestLinksIssue var comments: WebhookPayloadPullRequestPullRequestLinksComments var review_comments: WebhookPayloadPullRequestPullRequestLinksReviewComments var review_comment: WebhookPayloadPullRequestPullRequestLinksReviewComment var commits: WebhookPayloadPullRequestPullRequestLinksCommits var statuses: WebhookPayloadPullRequestPullRequestLinksStatuses } external interface WebhookPayloadPullRequestPullRequestBaseRepoOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestPullRequestBaseRepo { var id: Number var node_id: String var name: String var full_name: String var private: Boolean var owner: WebhookPayloadPullRequestPullRequestBaseRepoOwner var html_url: String var description: Any? var fork: Boolean var url: String var forks_url: String var keys_url: String var collaborators_url: String var teams_url: String var hooks_url: String var issue_events_url: String var events_url: String var assignees_url: String var branches_url: String var tags_url: String var blobs_url: String var git_tags_url: String var git_refs_url: String var trees_url: String var statuses_url: String var languages_url: String var stargazers_url: String var contributors_url: String var subscribers_url: String var subscription_url: String var commits_url: String var git_commits_url: String var comments_url: String var issue_comment_url: String var contents_url: String var compare_url: String var merges_url: String var archive_url: String var downloads_url: String var issues_url: String var pulls_url: String var milestones_url: String var notifications_url: String var labels_url: String var releases_url: String var deployments_url: String var created_at: String var updated_at: String var pushed_at: String var git_url: String var ssh_url: String var clone_url: String var svn_url: String var homepage: Any? var size: Number var stargazers_count: Number var watchers_count: Number var language: String? var has_issues: Boolean var has_projects: Boolean var has_downloads: Boolean var has_wiki: Boolean var has_pages: Boolean var forks_count: Number var mirror_url: Any? var archived: Boolean var disabled: Boolean var open_issues_count: Number var license: Any? var forks: Number var open_issues: Number var watchers: Number var default_branch: String var allow_squash_merge: Boolean? var allow_merge_commit: Boolean? var allow_rebase_merge: Boolean? var delete_branch_on_merge: Boolean? } external interface WebhookPayloadPullRequestPullRequestBaseUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestPullRequestBase { var label: String var ref: String var sha: String var user: WebhookPayloadPullRequestPullRequestBaseUser var repo: WebhookPayloadPullRequestPullRequestBaseRepo } external interface WebhookPayloadPullRequestPullRequestHeadRepoOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestPullRequestHeadRepo { var id: Number var node_id: String var name: String var full_name: String var private: Boolean var owner: WebhookPayloadPullRequestPullRequestHeadRepoOwner var html_url: String var description: Any? var fork: Boolean var url: String var forks_url: String var keys_url: String var collaborators_url: String var teams_url: String var hooks_url: String var issue_events_url: String var events_url: String var assignees_url: String var branches_url: String var tags_url: String var blobs_url: String var git_tags_url: String var git_refs_url: String var trees_url: String var statuses_url: String var languages_url: String var stargazers_url: String var contributors_url: String var subscribers_url: String var subscription_url: String var commits_url: String var git_commits_url: String var comments_url: String var issue_comment_url: String var contents_url: String var compare_url: String var merges_url: String var archive_url: String var downloads_url: String var issues_url: String var pulls_url: String var milestones_url: String var notifications_url: String var labels_url: String var releases_url: String var deployments_url: String var created_at: String var updated_at: String var pushed_at: String var git_url: String var ssh_url: String var clone_url: String var svn_url: String var homepage: Any? var size: Number var stargazers_count: Number var watchers_count: Number var language: String? var has_issues: Boolean var has_projects: Boolean var has_downloads: Boolean var has_wiki: Boolean var has_pages: Boolean var forks_count: Number var mirror_url: Any? var archived: Boolean var disabled: Boolean var open_issues_count: Number var license: Any? var forks: Number var open_issues: Number var watchers: Number var default_branch: String var allow_squash_merge: Boolean? var allow_merge_commit: Boolean? var allow_rebase_merge: Boolean? var delete_branch_on_merge: Boolean? } external interface WebhookPayloadPullRequestPullRequestHeadUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestPullRequestHead { var label: String var ref: String var sha: String var user: WebhookPayloadPullRequestPullRequestHeadUser var repo: WebhookPayloadPullRequestPullRequestHeadRepo } external interface WebhookPayloadPullRequestPullRequestUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPullRequestPullRequest { var url: String var id: Number var node_id: String var html_url: String var diff_url: String var patch_url: String var issue_url: String var number: Number var state: String var locked: Boolean var title: String var user: WebhookPayloadPullRequestPullRequestUser var body: String var created_at: String var updated_at: String var closed_at: String? var merged_at: Any? var merge_commit_sha: String? var assignee: `T$76`? var assignees: Array var requested_reviewers: Array var requested_teams: Array var labels: Array var milestone: `T$75`? var commits_url: String var review_comments_url: String var review_comment_url: String var comments_url: String var statuses_url: String var head: WebhookPayloadPullRequestPullRequestHead var base: WebhookPayloadPullRequestPullRequestBase var _links: WebhookPayloadPullRequestPullRequestLinks var author_association: String var draft: Boolean var merged: Boolean var mergeable: Boolean? var rebaseable: Boolean? var mergeable_state: String var merged_by: Any? var comments: Number var review_comments: Number var maintainer_can_modify: Boolean var commits: Number var additions: Number var deletions: Number var changed_files: Number } external interface WebhookPayloadPullRequest { var action: String var number: Number var pull_request: WebhookPayloadPullRequestPullRequest var repository: PayloadRepository var sender: WebhookPayloadPullRequestSender var assignee: WebhookPayloadPullRequestAssignee? var installation: WebhookPayloadPullRequestInstallation? var label: WebhookPayloadPullRequestLabel? } external interface WebhookPayloadPublicSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPublic { var repository: PayloadRepository var sender: WebhookPayloadPublicSender } external interface WebhookPayloadProjectSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadProjectProjectCreator { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadProjectProject { var owner_url: String var url: String var html_url: String var columns_url: String var id: Number var node_id: String var name: String var body: String var number: Number var state: String var creator: WebhookPayloadProjectProjectCreator var created_at: String var updated_at: String } external interface WebhookPayloadProject { var action: String var project: WebhookPayloadProjectProject var repository: PayloadRepository var sender: WebhookPayloadProjectSender } external interface WebhookPayloadProjectColumnSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadProjectColumnProjectColumn { var url: String var project_url: String var cards_url: String var id: Number var node_id: String var name: String var created_at: String var updated_at: String } external interface WebhookPayloadProjectColumn { var action: String var project_column: WebhookPayloadProjectColumnProjectColumn var repository: PayloadRepository var sender: WebhookPayloadProjectColumnSender } external interface WebhookPayloadProjectCardSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadProjectCardProjectCardCreator { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadProjectCardProjectCard { var url: String var project_url: String var column_url: String var column_id: Number var id: Number var node_id: String var note: String var archived: Boolean var creator: WebhookPayloadProjectCardProjectCardCreator var created_at: String var updated_at: String } external interface WebhookPayloadProjectCard { var action: String var project_card: WebhookPayloadProjectCardProjectCard var repository: PayloadRepository var sender: WebhookPayloadProjectCardSender } external interface WebhookPayloadPingSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPingHookLastResponse { var code: Any? var status: String var message: Any? } external interface WebhookPayloadPingHookConfig { var content_type: String var url: String var insecure_ssl: String } external interface WebhookPayloadPingHook { var type: String var id: Number var name: String var active: Boolean var events: Array var config: WebhookPayloadPingHookConfig var updated_at: String var created_at: String var url: String var test_url: String var ping_url: String var last_response: WebhookPayloadPingHookLastResponse } external interface WebhookPayloadPing { var zen: String var hook_id: Number var hook: WebhookPayloadPingHook var repository: PayloadRepository var sender: WebhookPayloadPingSender } external interface WebhookPayloadPageBuildSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPageBuildBuildPusher { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPageBuildBuildError { var message: Any? } external interface WebhookPayloadPageBuildBuild { var url: String var status: String var error: WebhookPayloadPageBuildBuildError var pusher: WebhookPayloadPageBuildBuildPusher var commit: String var duration: Number var created_at: String var updated_at: String } external interface WebhookPayloadPageBuild { var id: Number var build: WebhookPayloadPageBuildBuild var repository: PayloadRepository var sender: WebhookPayloadPageBuildSender } external interface WebhookPayloadPackageSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPackagePackageRegistry { var about_url: String var name: String var type: String var url: String var vendor: String } external interface WebhookPayloadPackagePackagePackageVersionAuthor { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPackagePackagePackageVersionPackageFilesItem { var download_url: String var id: Number var name: String var sha256: String var sha1: String var md5: String var content_type: String var state: String var size: Number var created_at: String var updated_at: String } external interface WebhookPayloadPackagePackagePackageVersionReleaseAuthor { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPackagePackagePackageVersionRelease { var url: String var html_url: String var id: Number var tag_name: String var target_commitish: String var name: String var draft: Boolean var author: WebhookPayloadPackagePackagePackageVersionReleaseAuthor var prerelease: Boolean var created_at: String var published_at: String } external interface WebhookPayloadPackagePackagePackageVersion { var id: Number var version: String var summary: String var body: String var body_html: String var release: WebhookPayloadPackagePackagePackageVersionRelease var manifest: String var html_url: String var tag_name: String var target_commitish: String var target_oid: String var draft: Boolean var prerelease: Boolean var created_at: String var updated_at: String var metadata: Array var package_files: Array var author: WebhookPayloadPackagePackagePackageVersionAuthor var installation_command: String } external interface WebhookPayloadPackagePackageOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadPackagePackage { var id: Number var name: String var package_type: String var html_url: String var created_at: String var updated_at: String var owner: WebhookPayloadPackagePackageOwner var package_version: WebhookPayloadPackagePackagePackageVersion var registry: WebhookPayloadPackagePackageRegistry } external interface WebhookPayloadPackage { var action: String var `package`: WebhookPayloadPackagePackage var repository: PayloadRepository var sender: WebhookPayloadPackageSender } external interface WebhookPayloadOrgBlockSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadOrgBlockOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface WebhookPayloadOrgBlockBlockedUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadOrgBlock { var action: String var blocked_user: WebhookPayloadOrgBlockBlockedUser var organization: WebhookPayloadOrgBlockOrganization var sender: WebhookPayloadOrgBlockSender } external interface WebhookPayloadOrganizationSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadOrganizationOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface WebhookPayloadOrganizationMembershipUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadOrganizationMembership { var url: String var state: String var role: String var organization_url: String var user: WebhookPayloadOrganizationMembershipUser } external interface WebhookPayloadOrganization { var action: String var membership: WebhookPayloadOrganizationMembership var organization: WebhookPayloadOrganizationOrganization var sender: WebhookPayloadOrganizationSender } external interface WebhookPayloadMilestoneSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadMilestoneMilestoneCreator { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadMilestoneMilestone { var url: String var html_url: String var labels_url: String var id: Number var node_id: String var number: Number var title: String var description: String var creator: WebhookPayloadMilestoneMilestoneCreator var open_issues: Number var closed_issues: Number var state: String var created_at: String var updated_at: String var due_on: String var closed_at: String? } external interface WebhookPayloadMilestone { var action: String var milestone: WebhookPayloadMilestoneMilestone var repository: PayloadRepository var sender: WebhookPayloadMilestoneSender } external interface WebhookPayloadMetaSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadMetaHookConfig { var content_type: String var insecure_ssl: String var url: String } external interface WebhookPayloadMetaHook { var type: String var id: Number var name: String var active: Boolean var events: Array var config: WebhookPayloadMetaHookConfig var updated_at: String var created_at: String } external interface WebhookPayloadMeta { var action: String var hook_id: Number var hook: WebhookPayloadMetaHook var repository: PayloadRepository var sender: WebhookPayloadMetaSender } external interface WebhookPayloadMembershipOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface WebhookPayloadMembershipTeam { var name: String var id: Number var node_id: String var slug: String var description: String var privacy: String var url: String var html_url: String var members_url: String var repositories_url: String var permission: String } external interface WebhookPayloadMembershipSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadMembershipMember { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadMembership { var action: String var scope: String var member: WebhookPayloadMembershipMember var sender: WebhookPayloadMembershipSender var team: WebhookPayloadMembershipTeam var organization: WebhookPayloadMembershipOrganization } external interface WebhookPayloadMemberChangesPermission { var from: String } external interface WebhookPayloadMemberChanges { var permission: WebhookPayloadMemberChangesPermission } external interface WebhookPayloadMemberSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadMemberMember { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadMember { var action: String var member: WebhookPayloadMemberMember var repository: PayloadRepository var sender: WebhookPayloadMemberSender var changes: WebhookPayloadMemberChanges? } external interface WebhookPayloadMarketplacePurchasePreviousMarketplacePurchasePlan { var id: Number var name: String var description: String var monthly_price_in_cents: Number var yearly_price_in_cents: Number var price_model: String var has_free_trial: Boolean var unit_name: String var bullets: Array } external interface WebhookPayloadMarketplacePurchasePreviousMarketplacePurchaseAccount { var type: String var id: Number var login: String var organization_billing_email: String } external interface WebhookPayloadMarketplacePurchasePreviousMarketplacePurchase { var account: WebhookPayloadMarketplacePurchasePreviousMarketplacePurchaseAccount var billing_cycle: String var on_free_trial: Boolean var free_trial_ends_on: Any? var unit_count: Number var plan: WebhookPayloadMarketplacePurchasePreviousMarketplacePurchasePlan } external interface WebhookPayloadMarketplacePurchaseMarketplacePurchasePlan { var id: Number var name: String var description: String var monthly_price_in_cents: Number var yearly_price_in_cents: Number var price_model: String var has_free_trial: Boolean var unit_name: String? var bullets: Array } external interface WebhookPayloadMarketplacePurchaseMarketplacePurchaseAccount { var type: String var id: Number var login: String var organization_billing_email: String } external interface WebhookPayloadMarketplacePurchaseMarketplacePurchase { var account: WebhookPayloadMarketplacePurchaseMarketplacePurchaseAccount var billing_cycle: String var unit_count: Number var on_free_trial: Boolean var free_trial_ends_on: Any? var next_billing_date: String var plan: WebhookPayloadMarketplacePurchaseMarketplacePurchasePlan } external interface WebhookPayloadMarketplacePurchaseSender { var login: String var id: Number var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean var email: String } external interface WebhookPayloadMarketplacePurchase { var action: String var effective_date: String var sender: WebhookPayloadMarketplacePurchaseSender var marketplace_purchase: WebhookPayloadMarketplacePurchaseMarketplacePurchase var previous_marketplace_purchase: WebhookPayloadMarketplacePurchasePreviousMarketplacePurchase? } external interface WebhookPayloadLabelChangesColor { var from: String } external interface WebhookPayloadLabelChanges { var color: WebhookPayloadLabelChangesColor } external interface WebhookPayloadLabelSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadLabelLabel { var id: Number var node_id: String var url: String var name: String var color: String var default: Boolean } external interface WebhookPayloadLabel { var action: String var label: WebhookPayloadLabelLabel var repository: PayloadRepository var sender: WebhookPayloadLabelSender var changes: WebhookPayloadLabelChanges? } external interface WebhookPayloadIssuesLabel { var id: Number var node_id: String var url: String var name: String var color: String var default: Boolean } external interface WebhookPayloadIssuesIssuePullRequest { var url: String var html_url: String var diff_url: String var patch_url: String } external interface WebhookPayloadIssuesAssignee { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssuesSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssuesChanges external interface WebhookPayloadIssuesIssueMilestoneCreator { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface `T$77` { var url: String var html_url: String var labels_url: String var id: Number var node_id: String var number: Number var title: String var description: String var creator: WebhookPayloadIssuesIssueMilestoneCreator var open_issues: Number var closed_issues: Number var state: String var created_at: String var updated_at: String var due_on: String var closed_at: String } external interface WebhookPayloadIssuesIssueAssigneesItem { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssuesIssueLabelsItem { var id: Number var node_id: String var url: String var name: String var color: String var default: Boolean } external interface WebhookPayloadIssuesIssueUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssuesIssue { var url: String var repository_url: String var labels_url: String var comments_url: String var events_url: String var html_url: String var id: Number var node_id: String var number: Number var title: String var user: WebhookPayloadIssuesIssueUser var labels: Array var state: String var locked: Boolean var assignee: `T$76`? var assignees: Array var milestone: `T$77`? var comments: Number var created_at: String var updated_at: String var closed_at: Any? var author_association: String var body: String var pull_request: WebhookPayloadIssuesIssuePullRequest? } external interface WebhookPayloadIssues { var action: String var issue: WebhookPayloadIssuesIssue var changes: WebhookPayloadIssuesChanges? var repository: PayloadRepository var sender: WebhookPayloadIssuesSender var assignee: WebhookPayloadIssuesAssignee? var label: WebhookPayloadIssuesLabel? } external interface WebhookPayloadIssueCommentChangesBody { var from: String } external interface WebhookPayloadIssueCommentChanges { var body: WebhookPayloadIssueCommentChangesBody } external interface WebhookPayloadIssueCommentSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssueCommentCommentUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssueCommentComment { var url: String var html_url: String var issue_url: String var id: Number var node_id: String var user: WebhookPayloadIssueCommentCommentUser var created_at: String var updated_at: String var author_association: String var body: String } external interface WebhookPayloadIssueCommentIssueMilestoneCreator { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssueCommentIssueMilestone { var url: String var html_url: String var labels_url: String var id: Number var node_id: String var number: Number var title: String var description: String var creator: WebhookPayloadIssueCommentIssueMilestoneCreator var open_issues: Number var closed_issues: Number var state: String var created_at: String var updated_at: String var due_on: String var closed_at: String } external interface WebhookPayloadIssueCommentIssueAssigneesItem { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssueCommentIssueAssignee { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssueCommentIssueLabelsItem { var id: Number var node_id: String var url: String var name: String var color: String var default: Boolean } external interface WebhookPayloadIssueCommentIssueUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadIssueCommentIssue { var url: String var repository_url: String var labels_url: String var comments_url: String var events_url: String var html_url: String var id: Number var node_id: String var number: Number var title: String var user: WebhookPayloadIssueCommentIssueUser var labels: Array var state: String var locked: Boolean var assignee: WebhookPayloadIssueCommentIssueAssignee var assignees: Array var milestone: WebhookPayloadIssueCommentIssueMilestone var comments: Number var created_at: String var updated_at: String var closed_at: Any? var author_association: String var body: String } external interface WebhookPayloadIssueComment { var action: String var issue: WebhookPayloadIssueCommentIssue var comment: WebhookPayloadIssueCommentComment var repository: PayloadRepository var sender: WebhookPayloadIssueCommentSender var changes: WebhookPayloadIssueCommentChanges? } external interface WebhookPayloadInstallationRepositoriesRepositoriesRemovedItem { var id: Number var name: String var full_name: String var private: Boolean } external interface WebhookPayloadInstallationRepositoriesSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadInstallationRepositoriesRepositoriesAddedItem { var id: Number var node_id: String var name: String var full_name: String var private: Boolean } external interface WebhookPayloadInstallationRepositoriesInstallationPermissions { var administration: String? var statuses: String? var repository_projects: String? var repository_hooks: String? var pull_requests: String? var pages: String? var issues: String var deployments: String? var contents: String var checks: String? var metadata: String var vulnerability_alerts: String? } external interface WebhookPayloadInstallationRepositoriesInstallationAccount { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadInstallationRepositoriesInstallation { var id: Number var account: WebhookPayloadInstallationRepositoriesInstallationAccount var repository_selection: String var access_tokens_url: String var repositories_url: String var html_url: String var app_id: Number var target_id: Number var target_type: String var permissions: WebhookPayloadInstallationRepositoriesInstallationPermissions var events: Array var created_at: Number var updated_at: Number var single_file_name: String? } external interface WebhookPayloadInstallationRepositories { var action: String var installation: WebhookPayloadInstallationRepositoriesInstallation var repository_selection: String var repositories_added: Array var repositories_removed: Array var sender: WebhookPayloadInstallationRepositoriesSender } external interface WebhookPayloadInstallationSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadInstallationRepositoriesItem { var id: Number var node_id: String var name: String var full_name: String var private: Boolean } external interface WebhookPayloadInstallationInstallationPermissions { var metadata: String var contents: String var issues: String var administration: String? var checks: String? var deployments: String? var pages: String? var pull_requests: String? var repository_hooks: String? var repository_projects: String? var statuses: String? var vulnerability_alerts: String? } external interface WebhookPayloadInstallationInstallationAccount { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadInstallationInstallation { var id: Number var account: WebhookPayloadInstallationInstallationAccount var repository_selection: String var access_tokens_url: String var repositories_url: String var html_url: String var app_id: Number var target_id: Number var target_type: String var permissions: WebhookPayloadInstallationInstallationPermissions var events: Array var created_at: Number var updated_at: Number var single_file_name: String? } external interface WebhookPayloadInstallation { var action: String var installation: WebhookPayloadInstallationInstallation var repositories: Array var sender: WebhookPayloadInstallationSender } external interface WebhookPayloadGollumSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadGollumPagesItem { var page_name: String var title: String var summary: Any? var action: String var sha: String var html_url: String } external interface WebhookPayloadGollum { var pages: Array var repository: PayloadRepository var sender: WebhookPayloadGollumSender } external interface WebhookPayloadGithubAppAuthorizationSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadGithubAppAuthorization { var action: String var sender: WebhookPayloadGithubAppAuthorizationSender } external interface WebhookPayloadForkSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadForkForkeeOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadForkForkee { var id: Number var node_id: String var name: String var full_name: String var private: Boolean var owner: WebhookPayloadForkForkeeOwner var html_url: String var description: Any? var fork: Boolean var url: String var forks_url: String var keys_url: String var collaborators_url: String var teams_url: String var hooks_url: String var issue_events_url: String var events_url: String var assignees_url: String var branches_url: String var tags_url: String var blobs_url: String var git_tags_url: String var git_refs_url: String var trees_url: String var statuses_url: String var languages_url: String var stargazers_url: String var contributors_url: String var subscribers_url: String var subscription_url: String var commits_url: String var git_commits_url: String var comments_url: String var issue_comment_url: String var contents_url: String var compare_url: String var merges_url: String var archive_url: String var downloads_url: String var issues_url: String var pulls_url: String var milestones_url: String var notifications_url: String var labels_url: String var releases_url: String var deployments_url: String var created_at: String var updated_at: String var pushed_at: String var git_url: String var ssh_url: String var clone_url: String var svn_url: String var homepage: Any? var size: Number var stargazers_count: Number var watchers_count: Number var language: Any? var has_issues: Boolean var has_projects: Boolean var has_downloads: Boolean var has_wiki: Boolean var has_pages: Boolean var forks_count: Number var mirror_url: Any? var archived: Boolean var disabled: Boolean var open_issues_count: Number var license: Any? var forks: Number var open_issues: Number var watchers: Number var default_branch: String var public: Boolean } external interface WebhookPayloadFork { var forkee: WebhookPayloadForkForkee var repository: PayloadRepository var sender: WebhookPayloadForkSender } external interface WebhookPayloadDeploymentStatusSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadDeploymentStatusDeploymentCreator { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadDeploymentStatusDeploymentPayload external interface WebhookPayloadDeploymentStatusDeployment { var url: String var id: Number var node_id: String var sha: String var ref: String var task: String var payload: WebhookPayloadDeploymentStatusDeploymentPayload var original_environment: String var environment: String var description: Any? var creator: WebhookPayloadDeploymentStatusDeploymentCreator var created_at: String var updated_at: String var statuses_url: String var repository_url: String } external interface WebhookPayloadDeploymentStatusDeploymentStatusCreator { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadDeploymentStatusDeploymentStatus { var url: String var id: Number var node_id: String var state: String var creator: WebhookPayloadDeploymentStatusDeploymentStatusCreator var description: String var environment: String var target_url: String var created_at: String var updated_at: String var deployment_url: String var repository_url: String } external interface WebhookPayloadDeploymentStatus { var action: String var deployment_status: WebhookPayloadDeploymentStatusDeploymentStatus var deployment: WebhookPayloadDeploymentStatusDeployment var repository: PayloadRepository var sender: WebhookPayloadDeploymentStatusSender } external interface WebhookPayloadDeploymentSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadDeploymentDeploymentCreator { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadDeploymentDeploymentPayload external interface WebhookPayloadDeploymentDeployment { var url: String var id: Number var node_id: String var sha: String var ref: String var task: String var payload: WebhookPayloadDeploymentDeploymentPayload var original_environment: String var environment: String var description: Any? var creator: WebhookPayloadDeploymentDeploymentCreator var created_at: String var updated_at: String var statuses_url: String var repository_url: String } external interface WebhookPayloadDeployment { var action: String var deployment: WebhookPayloadDeploymentDeployment var repository: PayloadRepository var sender: WebhookPayloadDeploymentSender } external interface WebhookPayloadDeployKeySender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadDeployKeyKey { var id: Number var key: String var url: String var title: String var verified: Boolean var created_at: String var read_only: Boolean } external interface WebhookPayloadDeployKey { var action: String var key: WebhookPayloadDeployKeyKey var repository: PayloadRepository var sender: WebhookPayloadDeployKeySender } external interface WebhookPayloadDeleteSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadDelete { var ref: String var ref_type: String var pusher_type: String var repository: PayloadRepository var sender: WebhookPayloadDeleteSender } external interface WebhookPayloadCreateSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadCreate { var ref: String var ref_type: String var master_branch: String var description: Any? var pusher_type: String var repository: PayloadRepository var sender: WebhookPayloadCreateSender } external interface WebhookPayloadContentReferenceInstallation { var id: Number var node_id: String } external interface WebhookPayloadContentReferenceSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadContentReferenceContentReference { var id: Number var node_id: String var reference: String } external interface WebhookPayloadContentReference { var action: String var content_reference: WebhookPayloadContentReferenceContentReference var repository: PayloadRepository var sender: WebhookPayloadContentReferenceSender var installation: WebhookPayloadContentReferenceInstallation } external interface WebhookPayloadCommitCommentSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadCommitCommentCommentUser { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadCommitCommentComment { var url: String var html_url: String var id: Number var node_id: String var user: WebhookPayloadCommitCommentCommentUser var position: Any? var line: Any? var path: Any? var commit_id: String var created_at: String var updated_at: String var author_association: String var body: String } external interface WebhookPayloadCommitComment { var action: String var comment: WebhookPayloadCommitCommentComment var repository: PayloadRepository var sender: WebhookPayloadCommitCommentSender } external interface WebhookPayloadCheckSuiteInstallation { var id: Number var node_id: String } external interface WebhookPayloadCheckSuiteSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadCheckSuiteCheckSuiteHeadCommitCommitter { var name: String var email: String } external interface WebhookPayloadCheckSuiteCheckSuiteHeadCommitAuthor { var name: String var email: String } external interface WebhookPayloadCheckSuiteCheckSuiteHeadCommit { var id: String var tree_id: String var message: String var timestamp: String var author: WebhookPayloadCheckSuiteCheckSuiteHeadCommitAuthor var committer: WebhookPayloadCheckSuiteCheckSuiteHeadCommitCommitter } external interface WebhookPayloadCheckSuiteCheckSuiteAppPermissions { var administration: String var checks: String var contents: String var deployments: String var issues: String var members: String var metadata: String var organization_administration: String var organization_hooks: String var organization_plan: String var organization_projects: String var organization_user_blocking: String var pages: String var pull_requests: String var repository_hooks: String var repository_projects: String var statuses: String var team_discussions: String var vulnerability_alerts: String } external interface WebhookPayloadCheckSuiteCheckSuiteAppOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadCheckSuiteCheckSuiteApp { var id: Number var node_id: String var owner: WebhookPayloadCheckSuiteCheckSuiteAppOwner var name: String var description: String var external_url: String var html_url: String var created_at: String var updated_at: String var permissions: WebhookPayloadCheckSuiteCheckSuiteAppPermissions var events: Array } external interface WebhookPayloadCheckSuiteCheckSuitePullRequestsItemBaseRepo { var id: Number var url: String var name: String } external interface WebhookPayloadCheckSuiteCheckSuitePullRequestsItemBase { var ref: String var sha: String var repo: WebhookPayloadCheckSuiteCheckSuitePullRequestsItemBaseRepo } external interface WebhookPayloadCheckSuiteCheckSuitePullRequestsItemHeadRepo { var id: Number var url: String var name: String } external interface WebhookPayloadCheckSuiteCheckSuitePullRequestsItemHead { var ref: String var sha: String var repo: WebhookPayloadCheckSuiteCheckSuitePullRequestsItemHeadRepo } external interface WebhookPayloadCheckSuiteCheckSuitePullRequestsItem { var url: String var id: Number var number: Number var head: WebhookPayloadCheckSuiteCheckSuitePullRequestsItemHead var base: WebhookPayloadCheckSuiteCheckSuitePullRequestsItemBase } external interface WebhookPayloadCheckSuiteCheckSuite { var id: Number var node_id: String var head_branch: String var head_sha: String var status: String var conclusion: String? var url: String var before: String var after: String var pull_requests: Array var app: WebhookPayloadCheckSuiteCheckSuiteApp var created_at: String var updated_at: String var latest_check_runs_count: Number var check_runs_url: String var head_commit: WebhookPayloadCheckSuiteCheckSuiteHeadCommit } external interface WebhookPayloadCheckSuite { var action: String var check_suite: WebhookPayloadCheckSuiteCheckSuite var repository: PayloadRepository var sender: WebhookPayloadCheckSuiteSender var installation: WebhookPayloadCheckSuiteInstallation? } external interface WebhookPayloadCheckRunInstallation { var id: Number } external interface WebhookPayloadCheckRunOrganization { var login: String var id: Number var node_id: String var url: String var repos_url: String var events_url: String var hooks_url: String var issues_url: String var members_url: String var public_members_url: String var avatar_url: String var description: String } external interface WebhookPayloadCheckRunSender { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface PayloadRepositoryOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean var name: String? var email: String? } external interface PayloadRepository { var id: Number var node_id: String var name: String var full_name: String var private: Boolean var owner: PayloadRepositoryOwner var html_url: String var description: String? var fork: Boolean var url: String var forks_url: String var keys_url: String var collaborators_url: String var teams_url: String var hooks_url: String var issue_events_url: String var events_url: String var assignees_url: String var branches_url: String var tags_url: String var blobs_url: String var git_tags_url: String var git_refs_url: String var trees_url: String var statuses_url: String var languages_url: String var stargazers_url: String var contributors_url: String var subscribers_url: String var subscription_url: String var commits_url: String var git_commits_url: String var comments_url: String var issue_comment_url: String var contents_url: String var compare_url: String var merges_url: String var archive_url: String var downloads_url: String var issues_url: String var pulls_url: String var milestones_url: String var notifications_url: String var labels_url: String var releases_url: String var deployments_url: String var created_at: dynamic /* String | Number */ var updated_at: String var pushed_at: dynamic /* String | Number */ var git_url: String var ssh_url: String var clone_url: String var svn_url: String var homepage: String? var size: Number var stargazers_count: Number var watchers_count: Number var language: String? var has_issues: Boolean var has_projects: Boolean var has_downloads: Boolean var has_wiki: Boolean var has_pages: Boolean var forks_count: Number var mirror_url: Any? var archived: Boolean var disabled: Boolean? var open_issues_count: Number var license: Any? var forks: Number var open_issues: Number var watchers: Number var default_branch: String var stargazers: Number? var master_branch: String? var permissions: PayloadRepositoryPermissions? } external interface WebhookPayloadCheckRunCheckRunPullRequestsItemBaseRepo { var id: Number var url: String var name: String } external interface WebhookPayloadCheckRunCheckRunPullRequestsItemBase { var ref: String var sha: String var repo: WebhookPayloadCheckRunCheckRunPullRequestsItemBaseRepo } external interface WebhookPayloadCheckRunCheckRunPullRequestsItemHeadRepo { var id: Number var url: String var name: String } external interface WebhookPayloadCheckRunCheckRunPullRequestsItemHead { var ref: String var sha: String var repo: WebhookPayloadCheckRunCheckRunPullRequestsItemHeadRepo } external interface WebhookPayloadCheckRunCheckRunPullRequestsItem { var url: String var id: Number var number: Number var head: WebhookPayloadCheckRunCheckRunPullRequestsItemHead var base: WebhookPayloadCheckRunCheckRunPullRequestsItemBase } external interface WebhookPayloadCheckRunCheckRunAppPermissions { var administration: String var checks: String var contents: String var deployments: String var issues: String var members: String var metadata: String var organization_administration: String var organization_hooks: String var organization_plan: String var organization_projects: String var organization_user_blocking: String var pages: String var pull_requests: String var repository_hooks: String var repository_projects: String var statuses: String var team_discussions: String var vulnerability_alerts: String } external interface WebhookPayloadCheckRunCheckRunAppOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadCheckRunCheckRunApp { var id: Number var node_id: String var owner: WebhookPayloadCheckRunCheckRunAppOwner var name: String var description: String? var external_url: String var html_url: String var created_at: String var updated_at: String var permissions: WebhookPayloadCheckRunCheckRunAppPermissions? var events: Array? } external interface WebhookPayloadCheckRunCheckRunCheckSuiteAppPermissions { var administration: String var checks: String var contents: String var deployments: String var issues: String var members: String var metadata: String var organization_administration: String var organization_hooks: String var organization_plan: String var organization_projects: String var organization_user_blocking: String var pages: String var pull_requests: String var repository_hooks: String var repository_projects: String var statuses: String var team_discussions: String var vulnerability_alerts: String } external interface WebhookPayloadCheckRunCheckRunCheckSuiteAppOwner { var login: String var id: Number var node_id: String var avatar_url: String var gravatar_id: String var url: String var html_url: String var followers_url: String var following_url: String var gists_url: String var starred_url: String var subscriptions_url: String var organizations_url: String var repos_url: String var events_url: String var received_events_url: String var type: String var site_admin: Boolean } external interface WebhookPayloadCheckRunCheckRunCheckSuiteApp { var id: Number var node_id: String var owner: WebhookPayloadCheckRunCheckRunCheckSuiteAppOwner var name: String var description: String? var external_url: String var html_url: String var created_at: String var updated_at: String var permissions: WebhookPayloadCheckRunCheckRunCheckSuiteAppPermissions? var events: Array? } external interface WebhookPayloadCheckRunCheckRunCheckSuitePullRequestsItemBaseRepo { var id: Number var url: String var name: String } external interface WebhookPayloadCheckRunCheckRunCheckSuitePullRequestsItemBase { var ref: String var sha: String var repo: WebhookPayloadCheckRunCheckRunCheckSuitePullRequestsItemBaseRepo } external interface WebhookPayloadCheckRunCheckRunCheckSuitePullRequestsItemHeadRepo { var id: Number var url: String var name: String } external interface WebhookPayloadCheckRunCheckRunCheckSuitePullRequestsItemHead { var ref: String var sha: String var repo: WebhookPayloadCheckRunCheckRunCheckSuitePullRequestsItemHeadRepo } external interface WebhookPayloadCheckRunCheckRunCheckSuitePullRequestsItem { var url: String var id: Number var number: Number var head: WebhookPayloadCheckRunCheckRunCheckSuitePullRequestsItemHead var base: WebhookPayloadCheckRunCheckRunCheckSuitePullRequestsItemBase } external interface WebhookPayloadCheckRunCheckRunCheckSuite { var id: Number var node_id: String? var head_branch: String var head_sha: String var status: String var conclusion: String? var url: String var before: String var after: String var pull_requests: Array var app: WebhookPayloadCheckRunCheckRunCheckSuiteApp var created_at: String var updated_at: String } external interface WebhookPayloadCheckRunCheckRunOutput { var title: String? var summary: String? var text: String? var annotations_count: Number var annotations_url: String } external interface WebhookPayloadCheckRunCheckRun { var id: Number var node_id: String? var head_sha: String var external_id: String var url: String var html_url: String var details_url: String? var status: String var conclusion: String? var started_at: String var completed_at: String? var output: WebhookPayloadCheckRunCheckRunOutput var name: String var check_suite: WebhookPayloadCheckRunCheckRunCheckSuite var app: WebhookPayloadCheckRunCheckRunApp var pull_requests: Array } external interface WebhookPayloadCheckRun { var action: String var check_run: WebhookPayloadCheckRunCheckRun var repository: PayloadRepository var sender: WebhookPayloadCheckRunSender var organization: WebhookPayloadCheckRunOrganization? var installation: WebhookPayloadCheckRunInstallation? } ================================================ FILE: wrappers/octokit-webhooks/src/jsMain/kotlin/octokit/webhooks/get-webhook-payload-type-from-event.module_@octokit_webhooks.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", "UNUSED_TYPEALIAS_PARAMETER") package octokit.webhooks.EventPayloads typealias GetWebhookPayloadTypeFromEvent = Any ================================================ FILE: wrappers/octokit-webhooks/src/jsMain/kotlin/octokit/webhooks/index.module_@octokit_webhooks.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") external fun verify(secret: String = definedExternally, eventPayload: Any? = definedExternally, signature: String = definedExternally): Boolean ================================================ FILE: wrappers/octokit-webhooks/src/jsMain/kotlin/octokit/webhooks/types.module_@octokit_webhooks.kt ================================================ @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") //import tsstdlib.IterableIterator external interface WebhookEvent { var id: String var name: String /* "check_run" | "check_suite" | "commit_comment" | "content_reference" | "create" | "delete" | "deploy_key" | "deployment" | "deployment_status" | "fork" | "github_app_authorization" | "gollum" | "installation" | "installation_repositories" | "issue_comment" | "issues" | "label" | "marketplace_purchase" | "member" | "membership" | "meta" | "milestone" | "organization" | "org_block" | "package" | "page_build" | "ping" | "project_card" | "project_column" | "project" | "public" | "pull_request" | "pull_request_review" | "pull_request_review_comment" | "push" | "release" | "repository_dispatch" | "repository" | "repository_import" | "repository_vulnerability_alert" | "security_advisory" | "sponsorship" | "star" | "status" | "team" | "team_add" | "watch" | "workflow_dispatch" | "workflow_run" */ var payload: T } external interface WebhookEvent__0 : WebhookEvent external interface Options { var path: String? var secret: String? var transform: TransformMethod? } typealias TransformMethod<@Suppress("UNUSED_TYPEALIAS_PARAMETER") T> = (event: WebhookEvent__0) -> dynamic external interface `T$74` { var event: WebhookEvent__0 } //external interface WebhookEventHandlerError : AggregateError { // var event: WebhookEvent__0 // var errors: Array //} // //open external class AggregateError : tsstdlib.Iterable { // fun iterator(): IterableIterator //}