diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9be205f..af6a9d9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,12 +6,12 @@ updates: # Maintain dependencies for Gradle dependencies - package-ecosystem: "gradle" directory: "/" - target-branch: "next" + target-branch: "main" schedule: - interval: "daily" + interval: "weekly" # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" - target-branch: "next" + target-branch: "main" schedule: - interval: "daily" + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c09b3e8..b18dea3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,173 +1,386 @@ -# GitHub Actions Workflow is created for testing and preparing the plugin release in the following steps: -# - validate Gradle Wrapper, -# - run 'test' and 'verifyPlugin' tasks, -# - run Qodana inspections, -# - run 'buildPlugin' task and prepare artifact for the further tests, -# - run 'runPluginVerifier' task, -# - create a draft release. -# -# Workflow is triggered on push and pull_request events. -# -# GitHub Actions reference: https://help.github.com/en/actions -# -## JBIJPPTPL - -name: Build +name: Pipeline + on: workflow_dispatch: - # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g. for dependabot pull requests) -# push: -# branches: [main] - # Trigger the workflow on any pull request -# pull_request: + inputs: + tag: + description: Release tag. Empty means today's vYYYY.M.D. + required: false + type: string + dry_run: + description: Validate release without publishing. + required: true + default: false + type: boolean + push: + branches: + - '**' + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review -jobs: +permissions: + actions: write + contents: write + packages: write - # Run Gradle Wrapper Validation Action to verify the wrapper's checksum - # Run verifyPlugin, IntelliJ Plugin Verifier, and test Gradle tasks - # Build plugin and provide the artifact for the next workflow jobs - build: - name: Build +concurrency: + group: pipeline-${{ github.event.pull_request.number || github.ref || github.run_id }} + cancel-in-progress: false + +jobs: + pipeline: + name: Test, Build, Release runs-on: ubuntu-latest - outputs: - version: ${{ steps.properties.outputs.version }} - changelog: ${{ steps.properties.outputs.changelog }} + timeout-minutes: 60 + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + steps: + - name: ๐Ÿงน Free disk space + shell: sh + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc + + - name: ๐Ÿ“ฅ Fetch sources + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.RELEASE_TOKEN || github.token }} - # Free GitHub Actions Environment Disk Space - - name: Maximize Build Space + - name: ๐Ÿงญ Plan run + id: plan + shell: sh + env: + HEAD_MESSAGE: ${{ github.event.head_commit.message || '' }} + INPUT_DRY_RUN: ${{ inputs.dry_run || 'false' }} + INPUT_TAG: ${{ inputs.tag || '' }} run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /usr/local/lib/android - sudo rm -rf /opt/ghc + release="false" + dry_run="false" + plugin_id="$(sed -n 's/^[[:space:]]*pluginId[[:space:]]*=[[:space:]]*//p' gradle.properties | head -n 1 | tr -d '\r')" + plugin_version="$(sed -n 's/^[[:space:]]*pluginVersion[[:space:]]*=[[:space:]]*//p' gradle.properties | head -n 1 | tr -d '\r')" - # Check out current repository - - name: Fetch Sources - uses: actions/checkout@v4 + if [ -z "$plugin_id" ] || [ -z "$plugin_version" ]; then + echo "Missing pluginId or pluginVersion in gradle.properties." >&2 + exit 1 + fi - # Validate wrapper - - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v3.5.0 + if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then + release="true" + dry_run="$INPUT_DRY_RUN" + elif [ "$GITHUB_EVENT_NAME" = "push" ] \ + && [ "$GITHUB_REF" = "refs/heads/main" ] \ + && ! printf '%s\n' "$HEAD_MESSAGE" | grep -Eq '^chore: release '; then + release="true" + fi - # Setup Java 17 environment for the next steps - - name: Setup Java - uses: actions/setup-java@v4 - with: - distribution: zulu - java-version: 17 + release_tag="" + target_version="$plugin_version" + if [ "$release" = "true" ]; then + if [ -n "$INPUT_TAG" ]; then + release_tag="$INPUT_TAG" + else + year="$(date -u +%Y)" + month="$(date -u +%m | sed 's/^0//')" + day="$(date -u +%d | sed 's/^0//')" + release_tag="v$year.$month.$day" + fi + + if ! printf '%s\n' "$release_tag" | grep -Eq '^v[0-9]{4}[.][1-9][0-9]?[.][1-9][0-9]?$'; then + echo "Tag must look like v2026.5.20: $release_tag" >&2 + exit 1 + fi + target_version="${release_tag#v}" + fi - # Set environment variables - - name: Export Properties - id: properties - shell: bash + echo "event=$GITHUB_EVENT_NAME" + echo "branch=${GITHUB_REF_NAME:-unknown}" + echo "release=$release" + echo "dry_run=$dry_run" + echo "plugin_id=$plugin_id" + echo "plugin_version=$plugin_version" + echo "target_version=$target_version" + echo "release_tag=$release_tag" + + echo "release=$release" >> "$GITHUB_OUTPUT" + echo "dry_run=$dry_run" >> "$GITHUB_OUTPUT" + echo "plugin_id=$plugin_id" >> "$GITHUB_OUTPUT" + echo "plugin_version=$plugin_version" >> "$GITHUB_OUTPUT" + echo "tag=$release_tag" >> "$GITHUB_OUTPUT" + echo "version=$target_version" >> "$GITHUB_OUTPUT" + + - name: ๐Ÿ—๏ธ Resolve cache key + id: cache-key + shell: sh run: | - PROPERTIES="$(./gradlew properties --console=plain -q)" - VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" - NAME="$(echo "$PROPERTIES" | grep "^pluginName:" | cut -f2- -d ' ')" - CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)" - - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "name=$NAME" >> $GITHUB_OUTPUT - echo "pluginVerifierHomeDir=~/.pluginVerifier" >> $GITHUB_OUTPUT - - echo "changelog<> $GITHUB_OUTPUT - echo "$CHANGELOG" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - ./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier - - # Run tests - - name: Run Tests - run: ./gradlew check - - # Collect Tests Result of failed tests - - name: Collect Tests Result - if: ${{ failure() }} - uses: actions/upload-artifact@v3 - with: - name: tests-result - path: ${{ github.workspace }}/build/reports/tests + { + sed '/^[[:space:]]*pluginVersion[[:space:]]*=/d' gradle.properties + cat build.gradle settings.gradle gradle/wrapper/gradle-wrapper.properties + } | sha256sum | awk '{ print "key=plugin-ci-${{ runner.os }}-" $1 }' >> "$GITHUB_OUTPUT" - # Upload Kover report to CodeCov - - name: Upload Code Coverage Report - uses: codecov/codecov-action@v4 + - name: โ™ป๏ธ Restore cache + id: cache + uses: actions/cache/restore@v4 with: - files: ${{ github.workspace }}/build/reports/kover/xml/report.xml + path: | + ~/.gradle/caches/transforms-* + ~/.gradle/caches/*/transforms + ~/.gradle/wrapper + key: ${{ steps.cache-key.outputs.key }} + restore-keys: | + plugin-ci-${{ runner.os }}- - # Cache Plugin Verifier IDEs - - name: Setup Plugin Verifier IDEs Cache - uses: actions/cache@v4 + - name: โ˜• Set up Java + uses: actions/setup-java@v5 with: - path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides - key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} + distribution: temurin + java-version: 25 - # Run Verify Plugin task and IntelliJ Plugin Verifier tool - - name: Run Plugin Verification tasks - run: ./gradlew runPluginVerifier -Dplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }} + - name: ๐Ÿท๏ธ Prepare release metadata + if: steps.plan.outputs.release == 'true' + shell: sh + env: + DRY_RUN: ${{ steps.plan.outputs.dry_run }} + RELEASE_TAG: ${{ steps.plan.outputs.tag }} + RELEASE_VERSION: ${{ steps.plan.outputs.version }} + run: | + git fetch --tags --force + if git rev-parse -q --verify "refs/tags/$RELEASE_TAG" >/dev/null; then + echo "Tag already exists: $RELEASE_TAG" >&2 + exit 1 + fi - # Collect Plugin Verifier Result - - name: Collect Plugin Verifier Result - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: pluginVerifier-result - path: ${{ github.workspace }}/build/reports/pluginVerifier + awk -v version="$RELEASE_VERSION" ' + /^[[:space:]]*pluginVersion[[:space:]]*=/ { + print "pluginVersion = " version + next + } + { print } + ' gradle.properties > gradle.properties.tmp + mv gradle.properties.tmp gradle.properties + + release_date="$(date -u +%Y-%m-%d)" + if grep -Eq "^## \\[$RELEASE_VERSION\\]" CHANGELOG.md; then + echo "CHANGELOG.md already contains $RELEASE_VERSION" + else + awk -v version="$RELEASE_VERSION" -v release_date="$release_date" ' + /^## \[Unreleased\]/ && !released { + print "## [Unreleased]" + print "" + print "## [" version "] - " release_date + released = 1 + next + } + { print } + ' CHANGELOG.md > CHANGELOG.md.tmp + mv CHANGELOG.md.tmp CHANGELOG.md + fi -# # Run Qodana inspections -# - name: Qodana - Code Inspection -# uses: JetBrains/qodana-action@v2022.3.4 + if ! git diff --quiet -- gradle.properties CHANGELOG.md; then + git config user.name "Kira" + git config user.email "kira@yuna.berlin" + git add gradle.properties CHANGELOG.md + if [ "$DRY_RUN" = "true" ]; then + echo "Dry run: would commit release metadata $RELEASE_VERSION" + else + git commit -m "chore: release $RELEASE_VERSION [skip ci]" + fi + fi - # Prepare plugin archive content for creating artifact - - name: Prepare Plugin Artifact - id: artifact - shell: bash + - name: ๐Ÿ” Validate release secrets + if: steps.plan.outputs.release == 'true' && steps.plan.outputs.dry_run != 'true' + shell: sh + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} run: | - cd ${{ github.workspace }}/build/distributions - FILENAME=`ls *.zip` - unzip "$FILENAME" -d content + if [ -z "$PUBLISH_TOKEN" ]; then + echo "Missing release secret: PUBLISH_TOKEN" >&2 + exit 1 + fi - echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT + case "$PUBLISH_TOKEN" in + *[[:space:]]*) + echo "PUBLISH_TOKEN contains whitespace." >&2 + exit 1 + ;; + perm-*|perm:*) + ;; + *) + echo "PUBLISH_TOKEN does not look like a JetBrains permanent token." >&2 + exit 1 + ;; + esac - # Store already-built plugin as an artifact for downloading - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.artifact.outputs.filename }} - path: ./build/distributions/content/*/* - - # Prepare a draft release for GitHub Releases page for the manual verification - # If accepted and published, release workflow would be triggered - releaseDraft: - name: Release Draft - if: github.event_name != 'pull_request' - needs: build - runs-on: ubuntu-latest - permissions: - contents: write - steps: + token_length="$(printf '%s' "$PUBLISH_TOKEN" | wc -c | tr -d ' ')" + if [ "$token_length" -lt 20 ]; then + echo "PUBLISH_TOKEN is too short." >&2 + exit 1 + fi + echo "publish_token=present length=$token_length format=permanent-token" + + - name: ๐Ÿงช Test and package + shell: sh + env: + RELEASE: ${{ steps.plan.outputs.release }} + run: | + if [ "$RELEASE" = "true" ]; then + ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all + else + ./gradlew --no-daemon check buildPlugin --warning-mode all + fi + + - name: ๐Ÿ“ฆ Locate release archive + id: archive + if: steps.plan.outputs.release == 'true' + shell: sh + run: | + archive="$(find build/distributions -maxdepth 1 -type f -name '*.zip' | sort | head -n 1)" - # Check out current repository - - name: Fetch Sources - uses: actions/checkout@v4 + if [ -z "$archive" ]; then + echo "No release archive found." >&2 + exit 1 + fi - # Remove old release drafts by using the curl request for the available releases with a draft flag - - name: Remove Old Release Drafts + echo "path=$archive" >> "$GITHUB_OUTPUT" + echo "name=$(basename "$archive")" >> "$GITHUB_OUTPUT" + + - name: ๐Ÿ“ Extract release notes + if: steps.plan.outputs.release == 'true' + shell: sh + env: + RELEASE_TAG: ${{ steps.plan.outputs.tag }} + run: | + version="${RELEASE_TAG#v}" + awk -v version="$version" ' + $0 ~ "^## \\[" version "\\]" { found = 1; next } + found && $0 ~ /^## / { exit } + found { print } + ' CHANGELOG.md > release-notes.md + + if [ ! -s release-notes.md ]; then + awk ' + /^## \[Unreleased\]/ { found = 1; next } + found && /^## / { exit } + found { print } + ' CHANGELOG.md > release-notes.md + fi + + if [ ! -s release-notes.md ]; then + printf 'Plugin release %s\n' "$RELEASE_TAG" > release-notes.md + fi + + - name: ๐Ÿ“š Publish GitHub package + if: steps.plan.outputs.release == 'true' && steps.plan.outputs.dry_run != 'true' + shell: sh + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ github.token }} + run: | + ./gradlew --no-daemon publishPluginZipPublicationToGitHubPackagesRepository --warning-mode all + + - name: ๐Ÿ›’ Publish Marketplace + if: steps.plan.outputs.release == 'true' && steps.plan.outputs.dry_run != 'true' + shell: sh + env: + ARCHIVE_PATH: ${{ steps.archive.outputs.path }} + MARKETPLACE_CHANNEL: ${{ vars.MARKETPLACE_CHANNEL || '' }} + PLUGIN_ID: ${{ steps.plan.outputs.plugin_id }} + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + run: | + set -- --fail-with-body --retry 3 --retry-delay 10 \ + --header "Authorization: Bearer $PUBLISH_TOKEN" \ + -F "xmlId=$PLUGIN_ID" \ + -F "file=@$ARCHIVE_PATH" + + if [ -n "$MARKETPLACE_CHANNEL" ]; then + set -- "$@" -F "channel=$MARKETPLACE_CHANNEL" + fi + curl "$@" https://plugins.jetbrains.com/api/updates/upload + + - name: ๐Ÿ“ค Push release commit and tag + if: steps.plan.outputs.release == 'true' && steps.plan.outputs.dry_run != 'true' + shell: sh env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ steps.plan.outputs.tag }} + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - gh api repos/{owner}/{repo}/releases \ - --jq '.[] | select(.draft == true) | .id' \ - | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} + git config user.name "Kira" + git config user.email "kira@yuna.berlin" - # Create a new release draft which is not publicly visible and requires manual acceptance - - name: Create Release Draft + if [ -n "$RELEASE_TOKEN" ]; then + git remote set-url origin "https://x-access-token:${RELEASE_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + fi + + target_branch="${GITHUB_REF_NAME:-main}" + git push origin "HEAD:$target_branch" + git tag -a "$RELEASE_TAG" -m "$RELEASE_TAG [skip ci]" + git push origin "refs/tags/$RELEASE_TAG" + + - name: ๐Ÿš€ Create GitHub release + if: steps.plan.outputs.release == 'true' && steps.plan.outputs.dry_run != 'true' + shell: sh + env: + ARCHIVE_PATH: ${{ steps.archive.outputs.path }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} + RELEASE_TAG: ${{ steps.plan.outputs.tag }} + run: | + if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then + gh release upload "$RELEASE_TAG" "$ARCHIVE_PATH" --clobber + gh release edit "$RELEASE_TAG" --title "$RELEASE_TAG" --notes-file release-notes.md + else + gh release create "$RELEASE_TAG" "$ARCHIVE_PATH" --title "$RELEASE_TAG" --notes-file release-notes.md --verify-tag + fi + + - name: ๐Ÿ’พ Save cache + if: success() && github.event_name != 'pull_request' && steps.cache.outputs.cache-hit != 'true' + continue-on-error: true + uses: actions/cache/save@v4 + with: + path: | + ~/.gradle/caches/transforms-* + ~/.gradle/caches/*/transforms + ~/.gradle/wrapper + key: ${{ steps.cache-key.outputs.key }} + + - name: ๐Ÿงฝ Prune old caches + if: success() && github.event_name != 'pull_request' + continue-on-error: true + shell: sh env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CACHE_KEY: ${{ steps.cache-key.outputs.key }} + GH_TOKEN: ${{ github.token }} run: | - gh release create v${{ needs.build.outputs.version }} \ - --draft \ - --title "v${{ needs.build.outputs.version }}" \ - --notes "$(cat << 'EOM' - ${{ needs.build.outputs.changelog }} - EOM - )" + gh cache list --json id,key --limit 100 \ + --jq ".[] | select(.key != \"$CACHE_KEY\") | .id" \ + | while IFS= read -r cache_id; do + if [ -n "$cache_id" ]; then + gh cache delete "$cache_id" || true + fi + done + + - name: ๐Ÿ“Š Upload test reports + if: failure() + uses: actions/upload-artifact@v7 + with: + name: test-reports + path: | + build/reports/tests + build/reports/jacoco + + - name: ๐Ÿงพ Upload verifier reports + if: always() && steps.plan.outputs.release == 'true' + uses: actions/upload-artifact@v7 + with: + name: plugin-verifier-reports + path: build/reports/pluginVerifier + + - name: ๐Ÿ“ฎ Upload plugin archive + if: success() + uses: actions/upload-artifact@v7 + with: + name: plugin-archive + path: build/distributions/*.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 9e1d109..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,97 +0,0 @@ -# GitHub Actions Workflow created for handling the release process based on the draft release prepared with the Build workflow. -# Running the publishPlugin task requires all following secrets to be provided: PUBLISH_TOKEN, PRIVATE_KEY, PRIVATE_KEY_PASSWORD, CERTIFICATE_CHAIN. -# See https://plugins.jetbrains.com/docs/intellij/plugin-signing.html for more information. - -name: Release -on: - workflow_dispatch: -# release: -# types: [prereleased, released] - -jobs: - - # Prepare and publish the plugin to the Marketplace repository - release: - name: Publish Plugin - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - # Check out current repository - - name: Fetch Sources - uses: actions/checkout@v3 - with: - ref: ${{ github.event.release.tag_name }} - - # Setup Java 11 environment for the next steps - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: zulu - java-version: 17 - - # Set environment variables - - name: Export Properties - id: properties - shell: bash - run: | - CHANGELOG="$(cat << 'EOM' | sed -e 's/^[[:space:]]*$//g' -e '/./,$!d' - ${{ github.event.release.body }} - EOM - )" - - echo "changelog<> $GITHUB_OUTPUT - echo "$CHANGELOG" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - # Update Unreleased section with the current release note - - name: Patch Changelog - if: ${{ steps.properties.outputs.changelog != '' }} - env: - CHANGELOG: ${{ steps.properties.outputs.changelog }} - run: | - ./gradlew patchChangelog --release-note="$CHANGELOG" - - # Publish the plugin to the Marketplace - - name: Publish Plugin - env: - PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} - CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} - run: ./gradlew publishPlugin - - # Upload artifact as a release asset - - name: Upload Release Asset - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release upload ${{ github.event.release.tag_name }} ./build/distributions/* - - # Create pull request - - name: Create Pull Request - if: ${{ steps.properties.outputs.changelog != '' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - VERSION="${{ github.event.release.tag_name }}" - BRANCH="changelog-update-$VERSION" - LABEL="release changelog" - - git config user.email "action@github.com" - git config user.name "GitHub Action" - - git checkout -b $BRANCH - git commit -am "Changelog update - $VERSION" - git push --set-upstream origin $BRANCH - - gh label create "$LABEL" \ - --description "Pull requests with release changelog update" \ - || true - - gh pr create \ - --title "Changelog update - \`$VERSION\`" \ - --body "Current pull request contains patched \`CHANGELOG.md\` file for the \`$VERSION\` version." \ - --label "$LABEL" \ - --head $BRANCH diff --git a/.github/workflows/run-ui-tests.yml b/.github/workflows/run-ui-tests.yml deleted file mode 100644 index 8f44ca2..0000000 --- a/.github/workflows/run-ui-tests.yml +++ /dev/null @@ -1,59 +0,0 @@ -# GitHub Actions Workflow for launching UI tests on Linux, Windows, and Mac in the following steps: -# - prepare and launch IDE with your plugin and robot-server plugin, which is needed to interact with UI -# - wait for IDE to start -# - run UI tests with separate Gradle task -# -# Please check https://github.com/JetBrains/intellij-ui-test-robot for information about UI tests with IntelliJ Platform -# -# Workflow is triggered manually. - -name: Run UI Tests -on: - workflow_dispatch: - -jobs: - - testUI: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - runIde: | - export DISPLAY=:99.0 - Xvfb -ac :99 -screen 0 1920x1080x16 & - gradle runIdeForUiTests & - - os: windows-latest - runIde: start gradlew.bat runIdeForUiTests - - os: macos-latest - runIde: ./gradlew runIdeForUiTests & - - steps: - - # Check out current repository - - name: Fetch Sources - uses: actions/checkout@v3 - - # Setup Java 11 environment for the next steps - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: zulu - java-version: 17 - - # Run IDEA prepared for UI testing - - name: Run IDE - run: ${{ matrix.runIde }} - - # Wait for IDEA to be started - - name: Health Check - uses: jtalk/url-health-check-action@v3 - with: - url: http://127.0.0.1:8082 - max-attempts: 15 - retry-delay: 30s - - # Run tests - - name: Tests - run: ./gradlew test diff --git a/.github/workflows/show_case.yml b/.github/workflows/show_case.yml deleted file mode 100644 index b67dfe7..0000000 --- a/.github/workflows/show_case.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: "PUBLISH" -# Screenshots made with 960 x 575 - but should be at least 1280px - -on: - workflow_call: - inputs: - input_1: - type: string - description: "Input Description 1" - required: false - input_2: - type: string - description: " " - required: false - secrets: - SECRET_1: - required: false - SECRET_2: - required: false - workflow_dispatch: - inputs: - input_1: - type: string - description: "Alternative" - input_2: - type: string - description: "Input Description 2" - -env: - day: "monday" - -jobs: - my_job_1: - runs-on: ubuntu-latest - outputs: - JAVA_VERSION: "${{steps.java_info.outputs.java_version}}" - IS_MAVEN: "${{steps.java_info.outputs.is_maven}}" - IS_GRADLE: "${{steps.java_info.outputs.is_gradle}}" - steps: - - name: "Checkout" - uses: actions/checkout@main - with: - ref: ${{ github.ref_name || github.head_ref }} - - name: "READ JAVA" - id: "java_info" - uses: YunaBraska/java-info-action@main - with: - INVALID: ${{ secrets.SECRET_1 }} - - name: "Invalid step 1" - uses: invalid\action1 - with: - java-version: ${{ steps.java_info.outputs.java_version }} - distribution: 'adopt' - - name: "Invalid step 2" - uses: invalid\action2 - with: - java-version: ${{ steps.java_info.outputs.java_version }} - distribution: 'adopt' - - name: "BUILD & TEST ${{steps.java_info.outputs.builder_name}}" - run: ${{ steps.java_info.outputs.cmd_test_build }} - my_job_2: - needs: my_job_1 - runs-on: ubuntu-latest - env: - job_env_1: "My Job Env 1" - job_env_2: "My Job Env 2" - steps: - - name: "Set Variables" - id: "my_variables" - run: | - echo "${{ needs.my_job_1.outputs.IS_GRADLE }}" - echo "custom_output_key=custom_output_value" >> $GITHUB_OUTPUT - echo "custom_env_key=custom_env_value" >> $GITHUB_ENV - - name: "Print Variables" - run: |- - echo "java version [${{ needs.my_job_1.outputs.JAVA_VERSION }}]" - echo "custom_output [${{ steps.my_variables.outputs.custom_output_key }}]" - echo "custom_env [${{ env.custom_env_key }}]" - echo "input_1 [${{ inputs.input_1 }}]" - echo "SECRET_1 [${{ secrets.SECRET_1 }}]" - env: - step_env_1: "My Step Env 1" - step_env_2: "My Step Env 2" - my_job_3: - needs: [ my_job_1, my_job_2, my_job_3 ] - runs-on: ubuntu-latest - env: - job_env_1: "My Job Env 1" - job_env_2: "My Job Env 2" - steps: - - name: "SETUP JAVA" - id: "setup_java" - uses: actions/setup-java@main - with: - INVALID: ${{ secrets.SECRET_1 }} - java-version: ${{ steps.INVALID.outputs.java_version }} - distribution: 'adopt' - - name: "Set Variables" - id: "my_variables" - run: | - echo "${{ needs.my_job_1.outputs.IS_GRADLE }}" - echo "custom_output_key=custom_output_value" >> $GITHUB_OUTPUT - echo "custom_env_key=custom_env_value" >> $GITHUB_ENV - - name: "Print Variables" - env: - step_env_1: "My Step Env 1" - step_env_2: "My Step Env 2" - run: |- - echo "java version [${{ needs.INVALID_JOB.outputs.JAVA_VERSION }} ${{ needs.my_job_1.outputs.INVALID_VAR }} ${{ needs.INCOMPLETE }} ${{ needs.my_job_1.outputs.JAVA_VERSION.TOO_LONG }} ${{ needs.my_job_1.outputs.JAVA_VERSION }}]" - echo "input_1 [${{ inputs.INVALID_INPUT }} ${{ inputs. }} ${{ inputs.input_1.TOO_LONG }} ${{ inputs.input_1 }}]" - echo "custom_output [${{ steps.INVALID_STEP.outputs.custom_output_key }}, ${{ steps.my_variables.outputs.INVALID_VAR }} ${{ steps.my_variables.INCOMPLETE }} ${{ steps.my_variables.outputs.custom_output_key.TOO_LONG }}] ${{ steps.setup_java.outputs.cache-hit }} ${{ steps.my_variables.outputs.custom_output_key }}" - echo "custom_env [${{ env.INVALID }} ${{ env.custom_env_key.payload }} ${{ env.custom_env_key }}]" - echo "custom_env [${{ github.INVALID }} ${{ github.repository_owner.payload }} ${{ github.repository_owner }}]" - echo "SECRET_1 [${{ secrets.SECRET_3 }} ${{ secrets.SECRET_1 }}]" - echo "step_env_1 [${{ env.step_env_3 }} ${{ env.step_env_1 }}]" - diff --git a/CHANGELOG.md b/CHANGELOG.md index ae3dc19..7a4d20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,45 @@ ## [Unreleased] +### Fresh Start + +- Better workflow editing with broader completion, highlighting, navigation, and hover help for GitHub Actions files. +- Resolved action and reusable workflow metadata now improves `with`, `secrets`, `outputs`, and version suggestions. +- Major-version action refs can now show an update quick fix when newer cached refs are available. +- GitHub Workflow Run Configurations can dispatch `workflow_dispatch`, collect inputs, show run status/logs, and cancel + remote runs from the IDE Run tool window. +- Gutter-created workflow runs now default to the current checked-out branch instead of always using `main`. +- Workflow runs now try IDE accounts in host-priority order, then configured/default local env tokens + (`GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_PAT`), then anonymous access, so live logs can fall back to a stronger token when + an IDE account token can dispatch but cannot download in-progress logs. +- `shell:` now completes GitHub-supported shell values. +- Public GitHub and GitHub Enterprise accounts configured in the IDE are used for action metadata resolution. +- Workflow expressions inside strings are highlighted separately from surrounding text. +- Shell snippets inside `run` blocks can use shell-aware editing where the IDE supports it. +- Cache controls are available through Find Action and, where visible, `Tools > GitHub Workflow` to refresh metadata, + clear cached entries, or restore hidden warnings. +- The plugin build, tests, verifier checks, release packaging, PR build checks, and local development setup are ready for the next version. +- Release automation now supports manual workflow runs, PR/branch testing, merged-PR tagging, tag-based GitHub releases, + and release-based JetBrains Marketplace publishing. +- Release tags now use date-based SemVer (`vYYYY.M.D`); the tag workflow updates `pluginVersion`. +- Thanks to @SilverNicktail, @tomsit-ionos, @jbw9964, @nyurik, @Lordfirespeed, @ris58h, @holomekc, + @InSyncWithFoo, @LecrisUT, @enobrev, @bartei, @gilzow, @zaaraungkam, @PeerHofmannGSG, and @zwj-cheer for reports and + context that shaped this hardening round. + +### Workflow Runs + +- Workflow runs now use one Run tool-window view with a JUnit-style workflow tree, grouped jobs, selected-node log + output, test-style status icons, and a thin progress bar instead of separate job tabs. +- Workflow job logs now render GitHub `group` blocks as named sections, reset to tidy `0001 |` line numbers per block, + show command markers as `run:`, strip ANSI escape noise, and classify warnings/errors for colored console output. + +### Settings And Localization + +- Added a `Settings > Tools > GitHub Workflow` page for language override, cache review/delete, cache import/export, + cache memory estimates, and a small support button with nerd fuel. +- Moved common run-configuration, cache, quick-fix, documentation, and settings strings into resource bundles and added + locale coverage checks for the top-20 language bundle set. + ## [3.2.1] - 2023-11-04 ### Investigating UI Freeze diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d96edc..c8194d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,8 +3,11 @@ When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. ## Pull Request Checklist -1) Create your branch from the main branch -2) Use [Semantic Commit Messages](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716) -3) Increase the version by using [Semantic Versioning](https://semver.org) -4) Ensure your changes are covered by tests -5) Follow the rules of [Clean Code](https://gist.github.com/wojteklu/73c6914cc446146b8b533c0988cf8d29) while coding + +1. Create your branch from `main`. +2. Keep changes boring: plain Java, Gradle wrapper, no new dependency unless it earns rent. +3. Add behavior-first tests through editor, run-configuration, cache, or client entrypoints. +4. Run `./gradlew test` before pushing. Run `./gradlew check verifyPlugin buildPlugin` before release work. +5. Do not bump `pluginVersion` by hand for release PRs; the tag workflow owns the date-based `YYYY.M.D` version bump. +6. Keep user-facing text in `src/main/resources/messages/GitHubWorkflowBundle*.properties`. +7. Update `README.md`, `doc/navigation.md`, ADRs, or `doc/spec/*` when behavior or workflow changes. diff --git a/README.md b/README.md index 606a842..a6da033 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,15 @@ [![Version](https://img.shields.io/jetbrains/plugin/v/21396-github-workflow.svg)](https://plugins.jetbrains.com/plugin/21396-github-workflow) [![Downloads](https://img.shields.io/jetbrains/plugin/d/21396-github-workflow.svg)](https://plugins.jetbrains.com/plugin/21396-github-workflow) [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/YunaBraska) -[![](https://img.shields.io/static/v1?label=DataPrivacy&message=%F0%9F%94%92&logo=springsecurity&color=%#6DB33F)](docs/DataPrivacy.md) +[![](https://img.shields.io/static/v1?label=DataPrivacy&message=%F0%9F%94%92&logo=springsecurity&color=%#6DB33F)](doc/DataPrivacy.md) -## โš ๏ธ Development Pause Notice โš ๏ธ +## Development Is Active Again -As of 01.01.2024, active development on this plugin is **paused**. Recent updates from JetBrains have introduced several -disruptions that significantly impact the plugin. Despite my previous year's intensive efforts. +After a long pause, this plugin is back in active development. The project now has a plain Java/Gradle setup, a much +larger editor test suite, refreshed release automation, and compatibility checks for current JetBrains IDE builds. -๐Ÿ”ฅ **I am actively seeking contributors** who can help tackle these challenges. If you're interested in contributing, or -know someone who might be, please feel free to dive in. I am more than willing to guide new contributors through the -plugin's architecture and collaborate on overcoming the current obstacles. - -Thank you for your understanding and support! +Contributors are still welcome. The difference now: the floor is sturdier, the lights are on, and the CI is expected to +complain before users do. --- @@ -33,6 +30,17 @@ _[See Screenshots](https://plugins.jetbrains.com/plugin/21396-github-workflow)_ * ๐ŸŒˆ Autocomplete & Syntax Highlighting: Write workflow YAML files with confidence. Autocomplete suggestions and clear syntax highlighting will make your code look and feel pristine. * ๐Ÿš€ Repository Access: Integrate with your private GitHub repositories for those secret projects you're working on. +* ๐Ÿข Self-hosted GitHub: Resolve metadata from public GitHub and GitHub Enterprise accounts already configured in + JetBrains settings, without storing plaintext tokens in this plugin. +* ๐Ÿงน Cache Controls: Refresh, inspect, export, import, or clear resolved action/workflow metadata from Find Action, + `Tools > GitHub Workflow`, or `Settings > Tools > GitHub Workflow`. +* โ–ถ๏ธ Workflow Runs: Create a GitHub Workflow Run Configuration from `workflow_dispatch`, default to the current branch, + provide inputs, follow job progress in a Run tool-window tree, inspect selected job logs, and stop remote runs through + the IDE or the `workflow_dispatch` gutter action. Job nodes use test-result style status icons and color warnings/errors in logs. + GitHub log groups render as named blocks with stable `0001 |` line numbers, `run:` command lines, and stripped ANSI + noise. +* โฌ†๏ธ Action Updates: Resolved major-version action refs such as `actions/checkout@v3` can offer a quick fix when newer + cached refs such as `v4` are available. * ๐Ÿ—บ๏ธ Local Path Resolution: Navigate effortlessly with one-click access to local paths. * โœ… Validation Engine: Validates linked local actions and workflows, but hey, you can turn this off too. * ๐Ÿ›ก๏ธ Security: We respect your privacy! The plugin doesn't use or store your personal data; it only accesses remote @@ -44,9 +52,59 @@ _[See Screenshots](https://plugins.jetbrains.com/plugin/21396-github-workflow)_ * **Installation**: Download the plugin from [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/21396-github-workflow). -* **Configuration**: Add your GitHub account via `File > Settings > Version Control > GitHub`. +* **Configuration**: Add GitHub or GitHub Enterprise accounts via `File > Settings > Version Control > GitHub`. The + plugin does not add a second server settings screen. +* **Cache**: Use Find Action (`Shift` twice) for `Refresh Action Cache`, `Clear Action Cache`, or `Restore Action + Warnings`. IDEs with the classic Tools menu also show these under `Tools > GitHub Workflow`. For review/delete, + import/export, plugin cache size, and language override, use + `Settings > Tools > GitHub Workflow`. +* **Workflow Runs**: Add GitHub accounts in `File > Settings > Version Control > GitHub`, then use the gutter play + action on `workflow_dispatch` or create a `GitHub Workflow` Run Configuration. GitHub jobs appear in one Run tree with + selected-node log output. + Context-created runs default to the checked-out Git branch and print clickable workflow/job URLs where GitHub exposes + them. The gutter play action is shown only when the workflow file belongs to a resolvable GitHub repository. Runs try + matching IDE accounts first, then other IDE GitHub accounts, then `GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_PAT`, then + anonymous access. An optional token environment variable can still be set explicitly for custom setups. GitHub log + timestamps, groups, command markers, and ANSI color codes are compacted before display. * **Usage**: Enjoy autocomplete, syntax highlighting, and much more as you code your GitHub Workflows and Actions. +## Local Development + +The project uses the Gradle wrapper and Java 25. No manual JetBrains JDK path is needed; the IntelliJ Platform Gradle +Plugin downloads the IDE, bundled plugins, verifier, and test runtime. + +1. Install Java 25 and make it available as `java`. +2. Run `./gradlew test` for the fast regression suite. +3. Run `./gradlew check verifyPlugin buildPlugin` before publishing or opening a release PR. + +## Release Automation + +One GitHub Actions workflow runs for branch pushes, PRs, and manual dispatches. It has one job and one cache. Branch and +PR runs do the normal test/package pass. A merge to `main`, or a manual workflow run, prepares the date-based version, +runs the full checks and Plugin Verifier, publishes the plugin ZIP to GitHub Packages, uploads the same ZIP to +JetBrains Marketplace, pushes the release commit and tag, and creates the GitHub release. + +The workflow prunes old GitHub Actions caches after a successful non-PR run so only the current pipeline cache remains. + +Required repository secrets: + +* `PUBLISH_TOKEN` + +Optional repository secret: + +* `RELEASE_TOKEN` - lets the workflow push the release commit and tag with a dedicated token. Without it, `GITHUB_TOKEN` + is used. + +Optional repository variable: + +* `MARKETPLACE_CHANNEL` - empty means the default stable Marketplace channel. + +For manual IDE testing, run `./gradlew runIde`. The default target tracks the latest stable IntelliJ IDEA platform that +the Gradle tooling can resolve (`platformVersion` in `gradle.properties`). The first run downloads IDE artifacts and can +take a while. The task also repairs stale custom color-scheme references in the generated sandbox only, so a missing +local theme is less likely to kick the test IDE back into light-mode betrayal. This is annoying, but at least it is +predictable. Progress. + ## Dependencies This plugin depends on: @@ -71,25 +129,25 @@ Yuna Morgenstern, your GitHub Jedi. -#### TODO +#### Project Checklist -- [ ] Autocomplete workflow and actions refs e.g. `@main`, `@v1`, ... -- [ ] Add links to Workflows and action files (GitHubUrl && MarketplaceUrl) -- [ ] Add links to Definitions (jobs, steps, needs, secrets, inputs, envs) +- [x] Autocomplete workflow and actions refs from resolved remote tags/branches e.g. `@main`, `@v1`, ... +- [x] Add links to Workflows and action files (GitHubUrl && MarketplaceUrl) +- [x] Add links to Definitions (jobs, steps, needs, secrets, inputs, envs) e.g. (https://github.com/cunla/ghactions-manager/blob/master/src/main/kotlin/com/dsoftware/ghmanager/api/Workflows.kt) -- [ ] Autogenerate `getGitHubContextEnvs` - from (https://docs.github.com/en/actions/learn-github-actions/contexts#github-context) -- [ ] Autogenerate `getGitHubEnvs` - from (https://docs.github.com/en/actions/learn-github-actions/variables#using-the-vars-context-to-access-configuration-variable-values) +- [x] Generate GitHub context data from (https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#github-context) +- [x] Generate default environment variable data from (https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables) ## Learning List -- [ ] Create Tests -- [ ] Refactor - less custom elements == less memory leaks -- [ ] Auto Complete Uses with local action files -- [ ] Auto Complete Uses field with Tags & Branches -- [ ] Link local files action files aka find usages -- [ ] implement CMD+B +- [x] Create Tests +- [ ] Refactor remaining custom editor UI into smaller JetBrains-native pieces where it reduces gutter noise, disposal + risk, or maintenance cost. +- [x] Auto Complete Uses with local action files +- [x] Auto Complete Uses with cached/current workflow refs +- [x] Auto Complete Uses field with Tags & Branches +- [x] Link local action/workflow files aka find usages +- [x] implement CMD+B - [x] Create a new [IntelliJ Platform Plugin Template][template] project. - [x] Get familiar with the [template documentation][template]. - [x] Adjust the [pluginGroup](./gradle.properties), [plugin ID](./src/main/resources/META-INF/plugin.xml). @@ -98,13 +156,12 @@ Yuna Morgenstern, your GitHub Jedi. the [Legal Agreements](https://plugins.jetbrains.com/docs/marketplace/legal-agreements.html?from=IJPluginTemplate). - [x] [Publish a plugin manually](https://plugins.jetbrains.com/docs/intellij/publishing-plugin.html?from=IJPluginTemplate) for the first time. -- [ ] Set the `21396-github-workflow` in the above README badges. -- [ ] Set the [Plugin Signing](https://plugins.jetbrains.com/docs/intellij/plugin-signing.html?from=IJPluginTemplate) +- [x] Set the `21396-github-workflow` in the above README badges. +- [ ] Confirm the [Plugin Signing](https://plugins.jetbrains.com/docs/intellij/plugin-signing.html?from=IJPluginTemplate) related [secrets](https://github.com/JetBrains/intellij-platform-plugin-template#environment-variables). -- [ ] Set +- [ ] Confirm the [Deployment Token](https://plugins.jetbrains.com/docs/marketplace/plugin-upload.html?from=IJPluginTemplate). -- [ ] Click the Watch button on the top of the [IntelliJ Platform Plugin Template][template] to be notified - about releases containing new features and fixes. +- [ ] Watch IntelliJ Platform Gradle Plugin and template releases during maintenance rounds. Plugin based on the [IntelliJ Platform Plugin Template][template]. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d643981 --- /dev/null +++ b/build.gradle @@ -0,0 +1,301 @@ +import org.jetbrains.changelog.Changelog +import org.jetbrains.intellij.platform.gradle.TestFrameworkType +import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask + +plugins { + id 'java' + id 'jacoco' + id 'maven-publish' + id 'org.jetbrains.intellij.platform' version '2.16.0' + id 'org.jetbrains.changelog' version '2.5.0' +} + +def requiredProperty = { name -> providers.gradleProperty(name).get() } +def csvProperty = { name -> + providers.gradleProperty(name).map { value -> + value.split(',').collect { it.trim() }.findAll { !it.isEmpty() } + }.get() +} +def htmlText = { value -> + value + .replaceAll(/(?s)/, ' ') + .replaceAll(/(?s)<[^>]+>/, '') + .replace('&', '&') + .replace('<', '<') + .replace(''', '\'') + .replace('"', '"') + .replace('>', '>') + .replaceAll(/\s+/, ' ') + .trim() +} +def downloadText = { url -> + def connection = new URI(url).toURL().openConnection() + connection.setRequestProperty('User-Agent', 'github-workflow-plugin-docs-generator') + connection.inputStream.withCloseable { stream -> stream.getText('UTF-8') } +} +def parseDocsTable = { html, tableId, prefix, typed -> + def tableMatcher = html =~ /(?s)/ + if (!tableMatcher.find()) { + throw new GradleException("Could not find GitHub Docs table: ${tableId}") + } + def result = new LinkedHashMap() + def rowPattern = typed + ? /(?s)
(.*?)<\/code><\/td>.*?<\/code><\/td>(.*?)<\/td><\/tr>/ + : /(?s)
(.*?)<\/code><\/td>(.*?)<\/td><\/tr>/ + def rowMatcher = tableMatcher.group() =~ rowPattern + rowMatcher.each { match -> + def name = htmlText(match[1]) + if (prefix != null && !name.startsWith("${prefix}.")) { + return + } + def key = prefix == null ? name : name.substring(prefix.length() + 1) + if (!key.contains('<')) { + result.put(key, htmlText(match[2])) + } + } + if (result.isEmpty()) { + throw new GradleException("GitHub Docs table produced no values: ${tableId}") + } + result +} +def writeTsv = { values, outputFile -> + outputFile.parentFile.mkdirs() + outputFile.withWriter('UTF-8') { writer -> + writer.writeLine('# Generated by ./gradlew generateGitHubDocsData from GitHub Docs.') + values.each { key, value -> writer.writeLine("${key}\t${value}") } + } +} + +group = requiredProperty('pluginGroup') +version = requiredProperty('pluginVersion') + +repositories { + mavenCentral() + intellijPlatform { + defaultRepositories() + } +} + +publishing { + repositories { + maven { + name = 'GitHubPackages' + url = uri("https://maven.pkg.github.com/${System.getenv('GITHUB_REPOSITORY') ?: 'YunaBraska/github-workflow-plugin'}") + credentials { + username = System.getenv('GITHUB_ACTOR') ?: 'github-actions' + password = System.getenv('GITHUB_TOKEN') ?: '' + } + } + } + + publications { + pluginZip(MavenPublication) { + groupId = requiredProperty('pluginGroup') + artifactId = rootProject.name + version = requiredProperty('pluginVersion') + artifact(tasks.named('buildPlugin').flatMap { it.archiveFile }) { + extension = 'zip' + } + pom { + name = requiredProperty('pluginName') + description = requiredProperty('pluginDescription') + url = requiredProperty('pluginUrl') + licenses { + license { + name = 'MIT License' + url = 'https://opensource.org/license/mit' + } + } + developers { + developer { + name = requiredProperty('vendorName') + email = requiredProperty('vendorEmail') + } + } + scm { + url = requiredProperty('pluginUrl') + connection = "scm:git:${requiredProperty('pluginUrl')}.git" + developerConnection = "scm:git:${requiredProperty('pluginUrl')}.git" + } + } + } + } +} + +dependencies { + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.assertj:assertj-core:3.27.7' + + intellijPlatform { + intellijIdea(requiredProperty('platformVersion')) + bundledPlugins(csvProperty('platformPlugins')) + pluginVerifier() + testFramework(TestFrameworkType.Platform.INSTANCE) + } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + options.release = 21 + options.compilerArgs.addAll(['-Xlint:deprecation', '-Xlint:unchecked']) +} + +tasks.withType(Test).configureEach { + useJUnit() + systemProperty 'PLUGIN_HOME_PATH', rootDir.absolutePath + testLogging { + events 'failed', 'skipped' + exceptionFormat = 'full' + } +} + +tasks.named('test') { + exclude '**/WorkflowPerformanceTest.class' + finalizedBy tasks.jacocoTestReport +} + +tasks.register('performanceTest', Test) { + def unitTest = tasks.named('test', Test).get() + group = 'verification' + description = 'Runs bounded editor performance regression tests.' + jacoco.enabled = false + dependsOn tasks.named('prepareTest') + testClassesDirs = unitTest.testClassesDirs + classpath = unitTest.classpath + javaLauncher.set(unitTest.javaLauncher) + useJUnit() + jvmArgumentProviders.addAll(unitTest.jvmArgumentProviders.findAll { + !it.class.name.contains('Jacoco') + }) + jvmArgs unitTest.jvmArgs + jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED' + systemProperties.putAll(unitTest.systemProperties) + systemProperty 'PLUGIN_HOME_PATH', rootDir.absolutePath + include '**/WorkflowPerformanceTest.class' + shouldRunAfter tasks.test + testLogging { + events 'failed', 'skipped' + exceptionFormat = 'full' + } +} + +tasks.named('check') { + dependsOn tasks.named('performanceTest') +} + +tasks.register('repairRunIdeSandboxTheme') { + group = 'intellij platform' + description = 'Repairs stale custom color-scheme references in the local runIde sandbox.' + doLast { + def sandboxRoot = file('.intellijPlatform/sandbox') + if (!sandboxRoot.exists()) { + return + } + fileTree(sandboxRoot).matching { + include '**/config/options/colors.scheme.xml' + include '**/config/options/other.xml' + }.each { optionsFile -> + def original = optionsFile.getText('UTF-8') + def repaired = original + .replace('_@user_Islands Dark', 'Darcula') + .replace('_@user_Islands Darcula', 'Darcula') + .replace('Islands Dark', 'Darcula') + .replace('Islands Darcula', 'Darcula') + if (repaired != original) { + optionsFile.write(repaired, 'UTF-8') + } + } + } +} + +tasks.matching { it.name == 'repairRunIdeSandboxTheme' }.configureEach { + mustRunAfter tasks.matching { it.name == 'prepareSandbox' } +} + +tasks.matching { it.name == 'runIde' || it.name == 'buildSearchableOptions' }.configureEach { + dependsOn tasks.named('repairRunIdeSandboxTheme') +} + +tasks.register('generateGitHubDocsData') { + group = 'documentation' + description = 'Refreshes checked-in GitHub Actions context and default environment variable snapshots from GitHub Docs.' + def githubContextFile = layout.projectDirectory.file('src/main/resources/github-docs/github-context.tsv') + def defaultEnvFile = layout.projectDirectory.file('src/main/resources/github-docs/default-env.tsv') + outputs.files(githubContextFile, defaultEnvFile) + doLast { + def contextsHtml = downloadText('https://docs.github.com/en/actions/reference/workflows-and-actions/contexts') + def variablesHtml = downloadText('https://docs.github.com/en/actions/reference/workflows-and-actions/variables') + + writeTsv(parseDocsTable(contextsHtml, 'github-context', 'github', true), githubContextFile.asFile) + writeTsv(parseDocsTable(variablesHtml, 'default-environment-variables', null, false), defaultEnvFile.asFile) + } +} + +jacoco { + toolVersion = '0.8.13' +} + +jacocoTestReport { + dependsOn tasks.test + reports { + xml.required = true + html.required = true + } +} + +intellijPlatform { + caching { + ides { + enabled = true + } + } + + pluginConfiguration { + id = requiredProperty('pluginId') + name = requiredProperty('pluginName') + version = requiredProperty('pluginVersion') + description = requiredProperty('pluginDescription') + changeNotes = provider { + changelog.renderItem(changelog.getLatest(), Changelog.OutputType.HTML) + } + + ideaVersion { + sinceBuild = requiredProperty('pluginSinceBuild') + } + + vendor { + name = requiredProperty('vendorName') + email = requiredProperty('vendorEmail') + url = requiredProperty('pluginUrl') + } + } + + pluginVerification { + failureLevel = [ + VerifyPluginTask.FailureLevel.COMPATIBILITY_PROBLEMS, + VerifyPluginTask.FailureLevel.MISSING_DEPENDENCIES, + VerifyPluginTask.FailureLevel.INVALID_PLUGIN, + VerifyPluginTask.FailureLevel.SCHEDULED_FOR_REMOVAL_API_USAGES, + VerifyPluginTask.FailureLevel.INTERNAL_API_USAGES, + VerifyPluginTask.FailureLevel.DEPRECATED_API_USAGES + ] + ides { + recommended() + } + } + + publishing { + token = providers.environmentVariable('PUBLISH_TOKEN') + } +} + +changelog { + groups.empty() + repositoryUrl = requiredProperty('pluginUrl') +} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 8e4c19f..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,120 +0,0 @@ -import org.jetbrains.changelog.Changelog -import org.jetbrains.changelog.markdownToHTML - -fun properties(key: String) = providers.gradleProperty(key) -fun environment(key: String) = providers.environmentVariable(key) -fun projectProperties(key: String) = project.findProperty(key).toString() - -plugins { - // Java support - kotlin("jvm") version "2.0.0" - id("java") - // Gradle IntelliJ Plugin - id("org.jetbrains.intellij.platform") version "2.1.0" - // Gradle Changelog Plugin - id("org.jetbrains.changelog") version "2.2.1" -} - -val pluginId = projectProperties("pluginId") -val pluginName = projectProperties("pluginName") -val pluginDescription = projectProperties("pluginDescription") -val pluginGroup = projectProperties("pluginGroup") -val pluginVersion = projectProperties("pluginVersion") -val pluginSinceBuild = projectProperties("pluginSinceBuild") -val pluginUntilBuild = projectProperties("pluginUntilBuild") -val vendorName = projectProperties("vendorName") -val vendorEmail = projectProperties("vendorEmail") -val pluginUrl = projectProperties("pluginUrl") -val platformVersion = projectProperties("platformVersion") -val platformPlugins = properties("platformPlugins").map { it.split(',') } - -println("pluginId [$pluginId]") -println("pluginName [$pluginName]") -println("pluginGroup [$pluginGroup]") -println("pluginDescription [$pluginDescription]") -println("pluginVersion [$pluginVersion]") -println("platformVersion [$platformVersion]") -println("pluginSinceBuild [$pluginSinceBuild]") -println("pluginUntilBuild [$pluginUntilBuild]") -println("vendorName [$vendorName]") -println("vendorEmail [$vendorEmail]") -println("pluginUrl [$pluginUrl]") -println("platformPlugins:\n${platformPlugins.get().joinToString(separator = "\n") { " - $it" }}") - -group = pluginGroup -version = pluginVersion - -// Configure project's dependencies -repositories { - mavenCentral() - - intellijPlatform { - defaultRepositories() - intellijDependencies() - } -} - -// Configure Gradle IntelliJ Plugin -// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html -intellijPlatform { - pluginConfiguration { - id = pluginId - name = pluginName - version = pluginVersion - changeNotes.set(provider { - changelog.renderItem(changelog.getLatest(), Changelog.OutputType.HTML) - }) - description = pluginDescription - - ideaVersion { - sinceBuild.set(pluginSinceBuild) -// untilBuild.set(pluginUntilBuild) - } - - vendor { - name.set(vendorName) - email.set(vendorEmail) - url.set(pluginUrl) - } - } - -// pluginVerification { -// failureLevel = listOf(VerifyPluginTask.FailureLevel.COMPATIBILITY_PROBLEMS) -// -// ides { -// recommended() -// } -// } - - publishing { - token.set(System.getenv("PUBLISH_TOKEN")) - } - - signing { - certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) - privateKey.set(System.getenv("PRIVATE_KEY")) - password.set(System.getenv("PRIVATE_KEY_PASSWORD")) - } -} - -dependencies { - testImplementation("org.assertj:assertj-core:3.26.3") - intellijPlatform { - instrumentationTools() - intellijIdeaCommunity(platformVersion) - pluginVerifier() - bundledPlugins(platformPlugins) - } -} - -// Set the JVM language level used to build the project. Use Java 11 for 2020.3+, and Java 17 for 2022.2+. -java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 -} - -// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin -changelog { - groups.empty() - repositoryUrl.set(pluginUrl) -} diff --git a/docs/DataPrivacy.md b/doc/DataPrivacy.md similarity index 81% rename from docs/DataPrivacy.md rename to doc/DataPrivacy.md index 9b1010c..5158a17 100644 --- a/docs/DataPrivacy.md +++ b/doc/DataPrivacy.md @@ -22,7 +22,7 @@ From version 3.0.0 onwards, significant improvements have been made in terms of ### What Data is Stored? -As of version 3.0.0, the plugin caches the following data for each workflow and action: +The plugin caches the following data for each workflow and action: 1) **Uses Value**: The value specified in the `use` field of your workflow file. 2) **Action Status**: A boolean indicating whether the `usesValue` refers to a GitHub Action or a workflow, identified @@ -41,6 +41,16 @@ As of version 3.0.0, the plugin caches the following data for each workflow and Among these, only the `input` and `output` variables have the potential to be sensitive or private. The level of their sensitivity depends on your unique data privacy considerations. +### Workflow Run Data + +When you run a workflow from the IDE, the plugin sends the selected repository, workflow file, ref, and +`workflow_dispatch` inputs to the GitHub REST API. It then reads workflow run status and job logs from GitHub and shows +them in the IDE Run tool window. + +The plugin does not store GitHub tokens. Workflow runs first use GitHub or GitHub Enterprise accounts already configured +in JetBrains settings. If an optional token environment variable is configured on the run configuration, that token is +used only after IDE accounts fail or are unavailable. + ### Security Measures Given that the plugin operates within the same IDE environment where your code resides, the risk profile is generally no diff --git a/doc/adr/0001-use-gradle-wrapper-for-plugin-build.md b/doc/adr/0001-use-gradle-wrapper-for-plugin-build.md new file mode 100644 index 0000000..9d52510 --- /dev/null +++ b/doc/adr/0001-use-gradle-wrapper-for-plugin-build.md @@ -0,0 +1,33 @@ +# 0001 Use Gradle Wrapper for Plugin Build + +## Status + +Accepted + +## Context + +The project is an IntelliJ Platform plugin. Local setup was hard because older tooling required a compatible runtime and +the CI workflow used stale IntelliJ plugin tasks. + +Maven was considered because it is boring for many Java projects. For IntelliJ Platform plugins, the boring path is the +official IntelliJ Platform Gradle Plugin: it wires IDE dependencies, bundled plugins, instrumentation, sandbox runs, +plugin packaging, signing, publishing, and Plugin Verifier tasks. + +## Decision + +Keep the Gradle wrapper as the build entrypoint and use a plain Groovy `build.gradle`. + +Use Java 25 to run Gradle and compile plugin bytecode with `--release 21`, matching the 2024.2 baseline while keeping the +developer runtime current. + +Use `verifyPlugin` as a required compatibility gate and fail on deprecated, internal, scheduled-for-removal, invalid +plugin, missing dependency, and compatibility verifier findings. + +## Consequences + +Local setup is three commands or fewer and no longer requires pointing the project at a local JetBrains JDK. + +The build remains Java-first and avoids Kotlin project code and Kotlin build scripts. + +Moving to Maven is rejected for now because it would either lose official JetBrains build behavior or require rebuilding +that behavior manually. That is not boring. That is paperwork wearing a mask. diff --git a/doc/adr/0002-configurable-remote-action-providers.md b/doc/adr/0002-configurable-remote-action-providers.md new file mode 100644 index 0000000..6c01879 --- /dev/null +++ b/doc/adr/0002-configurable-remote-action-providers.md @@ -0,0 +1,35 @@ +# 0002 Use JetBrains GitHub Accounts for Remote Action Providers + +## Status + +Accepted + +## Context + +Remote action metadata used to assume github.com URL shapes. That made GitHub Enterprise installs hard to support and +hard to test without live network calls. + +## Decision + +Use GitHub/GitHub Enterprise accounts already registered in JetBrains settings as GitHub remote server sources. Always +include public github.com as a fallback source. + +Do not add a plugin-owned server settings UI. Self-hosted GitHub belongs in `Version Control > GitHub > Log in to GitHub +Enterprise`, which already exists in the IDE. + +Resolve remote metadata through the configured server APIs first, then fall back to public GitHub. Use the repository +contents API for `action.yml`, fallback `action.yaml`, and reusable workflow metadata. The plugin does not persist +tokens. + +Use a JDK fake HTTP server in tests for GitHub Enterprise-shaped API behavior. Tests may inject temporary server +definitions directly into the service; that is not a user-facing settings surface. + +## Consequences + +Self-hosted GitHub action metadata can be resolved, linked, highlighted, styled, documented, and completed without +contacting public GitHub in tests. + +The plugin stays boring: no duplicate account UI, no token storage, and fewer settings to test. + +The provider currently resolves metadata and refs after a callable is known. It does not browse arbitrary remote +repositories to discover unknown action/workflow slugs. diff --git a/doc/adr/0003-use-verifier-clean-documentation-provider.md b/doc/adr/0003-use-verifier-clean-documentation-provider.md new file mode 100644 index 0000000..c1516cf --- /dev/null +++ b/doc/adr/0003-use-verifier-clean-documentation-provider.md @@ -0,0 +1,36 @@ +# 0003 Use Verifier-Clean Documentation Provider + +## Status + +Accepted + +## Context + +Hover documentation is needed for resolved actions, workflow variables, action inputs, secrets, and outputs. + +JetBrains documents the Documentation Target API as the current path for 2023.1 and newer IDEs. In the current verifier +matrix, that API still reports required `Pointer` and presentation types as experimental API usages. The project treats +clean Plugin Verifier output as a release gate. + +The older documentation provider extension is still supported by the platform and does not trigger verifier warnings in +the supported matrix. "Older" here means boring and stable, not broken. + +## Decision + +Use the verifier-clean documentation provider extension for workflow hover/quick documentation until the Documentation +Target API no longer produces verifier warnings across the supported IDE matrix. + +Register the provider before YAML JSON schema documentation so workflow-specific hovers for `uses`, variables, inputs, +outputs, and jobs win over generic schema text such as `uses: string`. + +Keep the implementation behind one provider class and test it through IntelliJ documentation entrypoints so it can be +swapped to the target API later without changing user-facing behavior. + +## Consequences + +The plugin keeps hover UX for resolved workflow symbols while Plugin Verifier can remain a release gate. + +The provider intentionally returns `null` for unsupported documentation targets because that is the IntelliJ Platform +documentation API contract. + +Revisit this decision when the supported JetBrains versions expose a warning-free Documentation Target API. diff --git a/doc/adr/0004-use-actions-for-cache-controls-and-resource-bundles.md b/doc/adr/0004-use-actions-for-cache-controls-and-resource-bundles.md new file mode 100644 index 0000000..1344c35 --- /dev/null +++ b/doc/adr/0004-use-actions-for-cache-controls-and-resource-bundles.md @@ -0,0 +1,35 @@ +# 0004 Use Actions for Cache Controls and Resource Bundles + +## Status + +Accepted + +## Context + +Resolved remote action and reusable workflow metadata should work offline after it has been fetched once. Users also +need a predictable way to clear or refresh that cache when metadata is stale. + +The project should avoid another settings page unless it carries real configuration. The current server source is already +JetBrains GitHub/GitHub Enterprise accounts plus public github.com fallback. + +JetBrains action-system documentation supports action/group text from resource bundles, including dedicated bundles on +``. + +## Decision + +Add `Tools > GitHub Workflow` actions for refreshing resolved remote metadata and clearing cached metadata. + +Keep cache operation logic in `GitHubActionCache` and keep action classes thin. + +Use `messages.GitHubWorkflowBundle` for plugin action labels, descriptions, and cache notifications. Add locale files +for the first localization pass and test that every locale has the same nonblank keys as the default bundle. + +## Consequences + +Users get explicit cache controls without a plugin-owned settings menu. + +The cache can be tested through public service methods and action registration can be tested through the IntelliJ action +system. + +This does not fully localize editor diagnostics, quick fixes, or documentation text yet. Those strings must be extracted +in later passes instead of pretending partial translation is complete. diff --git a/doc/adr/0005-bound-remote-discovery-and-testable-reload.md b/doc/adr/0005-bound-remote-discovery-and-testable-reload.md new file mode 100644 index 0000000..ec627a1 --- /dev/null +++ b/doc/adr/0005-bound-remote-discovery-and-testable-reload.md @@ -0,0 +1,34 @@ +# 0005 Bound Remote Discovery and Testable Reload + +## Status + +Accepted + +## Context + +Completion should help before an action is fully resolved, but arbitrary remote discovery can become slow, noisy, or +rate-limit hostile. + +Remote reload is triggered from editor gutter actions and used to perform network/file work in the background, which made +it hard to test without sleeping. + +## Decision + +Keep remote discovery bounded: + +- Search matching repositories only when the user has typed an owner prefix such as `actions/`. +- Suggest at most 10 refs for a typed callable such as `actions/checkout@`. +- Prefer tags before branches for ref completion. +- Cache discovered refs in the action cache so later completion can work offline. + +Add a resolver strategy boundary inside `GitHubActionCache` so remote reload gutter execution can be tested with a latch +instead of sleeps or real network calls. + +## Consequences + +Completion is useful before metadata resolution without turning every popup into an unbounded remote crawl. + +The reload gutter action remains asynchronous in production and deterministic in tests. + +Full marketplace-style search across all GitHub actions remains intentionally out of scope until the UX can rank results +without noise. diff --git a/doc/adr/0006-use-generated-github-docs-data-snapshots.md b/doc/adr/0006-use-generated-github-docs-data-snapshots.md new file mode 100644 index 0000000..09328f1 --- /dev/null +++ b/doc/adr/0006-use-generated-github-docs-data-snapshots.md @@ -0,0 +1,21 @@ +# 0006 Use Generated GitHub Docs Data Snapshots + +## Status + +Accepted + +## Context + +GitHub context keys and default environment variables change independently of the plugin. Keeping those values as Java +maps made completion, highlighting, and documentation drift easy. + +## Decision + +Store GitHub context and default environment metadata as checked-in TSV snapshots under `src/main/resources/github-docs`. +Refresh them with `./gradlew generateGitHubDocsData`, which reads the rendered official GitHub Docs tables. Runtime code +loads the snapshots through one path, and tests assert the exposed maps match the generated resources exactly. + +## Consequences + +The plugin does not need network access during normal build, test, or runtime. Updating to new GitHub docs is an explicit +maintainer action, and stale snapshots are visible in review instead of hidden in Java code. diff --git a/doc/adr/0007-use-date-based-semver-and-242-baseline.md b/doc/adr/0007-use-date-based-semver-and-242-baseline.md new file mode 100644 index 0000000..9bbc202 --- /dev/null +++ b/doc/adr/0007-use-date-based-semver-and-242-baseline.md @@ -0,0 +1,34 @@ +# 0007 Use Date-Based SemVer and 2024.2 Baseline + +## Status + +Accepted + +## Context + +The previous plugin version looked like an IntelliJ platform version. That made it unclear whether `2024.3.0` meant the +plugin release, the IDE baseline, or stale project state. + +JetBrains Marketplace requires plugin versions to follow semantic versioning. Slashes are not valid for that, so the +date-shaped release version must use dot-separated numeric identifiers. + +The lowest supported platform is currently 2024.2 / branch `242`. JetBrains lists 2024.2 as Java 21-based, which +matches the current bytecode target and keeps backward compatibility without carrying older Java 17-era platform +constraints. The plugin verifier passed against 2024.2, 2024.3, 2025.x, 2026.1, and 2026.2 EAP with this baseline. + +## Decision + +Use `YYYY.M.D` plugin versions and `vYYYY.M.D` tags. + +Keep `pluginSinceBuild = 242` until a verifier-clean feature needs APIs newer than 2024.2. When that happens, raise the +baseline deliberately and document the user-visible compatibility cost. + +The tag workflow updates `gradle.properties`, commits that version with the Kira bot identity, tags that commit, and +pushes both commit and tag. + +## Consequences + +Release versions are obvious, sortable enough for this project, and still compatible with Marketplace SemVer rules. + +Only one release per day is supported by the default format. If same-day patch releases become necessary, this ADR must +be revisited before adding suffixes. diff --git a/doc/adr/0008-test-through-editor-and-runtime-boundaries.md b/doc/adr/0008-test-through-editor-and-runtime-boundaries.md new file mode 100644 index 0000000..f69ef99 --- /dev/null +++ b/doc/adr/0008-test-through-editor-and-runtime-boundaries.md @@ -0,0 +1,32 @@ +# 0008 Test Through Editor and Runtime Boundaries + +## Status + +Accepted + +## Context + +The plugin used to be hard to test because behavior was spread across IntelliJ editor callbacks, cache state, remote +metadata, and YAML PSI traversal. + +Testing private helpers directly would make refactors painful and still miss the actual IDE behavior users see. + +## Decision + +Prefer public entrypoints: + +- Use IntelliJ fixture tests for completion, highlighting, references, quick documentation, quick fixes, gutter actions, + injected languages, and run configuration registration. +- Use fake HTTP servers or injectable transport boundaries for GitHub API behavior. +- Use dedicated plain unit tests only for boundary parsers such as release input parsing and repository URL resolution. +- Keep every meaningful case in its own test method unless the behavior is genuinely one scenario. +- Add a failing-first regression test for each fixed issue. +- Keep performance coverage in the dedicated `performanceTest` task instead of hiding timing assumptions in normal unit + tests. + +## Consequences + +Tests are slower than isolated helper tests, but they protect user-visible behavior and reduce false confidence. + +Helper code may still be refactored freely as long as the editor/runtime behavior stays stable. Good. Less ceremony, +more damage containment. diff --git a/doc/adr/0009-use-run-configurations-for-workflow-execution.md b/doc/adr/0009-use-run-configurations-for-workflow-execution.md new file mode 100644 index 0000000..ff6153e --- /dev/null +++ b/doc/adr/0009-use-run-configurations-for-workflow-execution.md @@ -0,0 +1,33 @@ +# 0009 Use Run Configurations for Workflow Execution + +## Status + +Accepted + +## Context + +Users need to trigger workflows, see whether a run is queued or running, stop it, and inspect logs without leaving the +IDE. A loose toolbar action would be easy to add but would not fit normal IntelliJ run/debug UX. + +GitHub only supports manual dispatch for workflows that declare `workflow_dispatch`. Other workflow events can be +observed and rerun through GitHub APIs, but they cannot honestly be triggered as arbitrary event simulations. + +## Decision + +Add a `GitHub Workflow` Run Configuration type. + +Create configurations from workflow YAML context, show a gutter play action on `workflow_dispatch`, expose dispatch +inputs as `key=value` lines, print status/log output in the Run tool window, and map Stop to GitHub's cancel-run API. +Represent GitHub jobs in one Run tool-window tree with a selected-job detail console instead of creating one tab per +job, so the UI stays close to normal IntelliJ execution views without pretending to be the built-in test runner. + +Use the repository remote to infer owner/repo/API URL. Use JetBrains GitHub accounts first, an optional token +environment variable second, and anonymous access last. Never send a token to a different GitHub host than the account +belongs to. Public read calls may work without a token; dispatch/cancel generally need an authenticated account. + +## Consequences + +Workflow execution behaves like a normal IDE run target instead of a custom panel. + +The first implementation supports `workflow_dispatch`. Broader "show running workflows for any event" UX can build on +the same client and Run tool window boundary without faking unsupported GitHub triggers. diff --git a/doc/adr/0010-use-host-matched-github-account-authentication.md b/doc/adr/0010-use-host-matched-github-account-authentication.md new file mode 100644 index 0000000..1e0be16 --- /dev/null +++ b/doc/adr/0010-use-host-matched-github-account-authentication.md @@ -0,0 +1,34 @@ +# 0010 Use Host-Matched GitHub Account Authentication + +## Status + +Accepted + +## Context + +Remote metadata and workflow dispatch calls need authentication for private repositories, higher rate limits, and +write APIs such as `workflow_dispatch`. The plugin already depends on JetBrains' GitHub integration, so a separate token +store or server UI would duplicate IDE settings and create more security surface. + +Tokens must not be sent to unrelated hosts. A github.com token must not be used for a GitHub Enterprise host, and an +Enterprise token must not be sent to github.com. + +## Decision + +For GitHub REST calls, use this order: + +1. GitHub accounts from JetBrains settings whose server host matches the request API host. +2. Optional token environment variable configured on the run configuration or remote server test fixture. +3. Anonymous request. + +For ambiguous remote action references, prefer github.com servers before Enterprise servers. For repository-derived +workflow runs, infer the API host from the Git remote and use matching accounts first. + +Authentication and rate-limit workflow dispatch failures must include a direct hint to `Settings > Version Control > +GitHub` and show a notification action that opens GitHub settings. + +## Consequences + +Public repositories still work without accounts where GitHub allows it. Authenticated dispatch uses the IDE account +instead of forcing a `GH_TOKEN` environment variable. Failed or missing accounts degrade predictably instead of throwing +an unexplained 401 at users. diff --git a/doc/adr/0011-release-once-publish-zip.md b/doc/adr/0011-release-once-publish-zip.md new file mode 100644 index 0000000..bdcb73a --- /dev/null +++ b/doc/adr/0011-release-once-publish-zip.md @@ -0,0 +1,35 @@ +# 0011 Use One Pipeline Job for CI and Release + +## Status + +Accepted + +## Context + +The release pipeline previously used separate build, tag, release, and Marketplace workflows. It also created several +GitHub Actions cache entries. That made the release path harder to reason about and let large caches crowd each other +out. + +## Decision + +Use one workflow file with one job and one manually managed cache key. + +The same job handles all modes: + +- branch and PR runs execute the normal test/package path; +- a `main` push that is not the generated release commit executes the release path; +- a manual dispatch executes the release path, with optional dry-run support. + +The release path prepares the version, runs the full checks and Plugin Verifier, publishes the plugin ZIP to GitHub +Packages, uploads the same ZIP directly to JetBrains Marketplace, pushes the release commit and tag, and creates or +updates the GitHub release. + +After a successful non-PR run, the job prunes every GitHub Actions cache entry except the current pipeline cache key. + +## Consequences + +- CI and release behavior live in one place. +- There is only one cache entry by design after a successful writable run. +- GitHub Packages and Marketplace publishing use the exact artifact attached to the GitHub release. +- Release publishing requires `PUBLISH_TOKEN`. +- The workflow has more conditional shell logic, but fewer moving GitHub Actions parts. diff --git a/docs/ghw_arch.png b/doc/ghw_arch.png similarity index 100% rename from docs/ghw_arch.png rename to doc/ghw_arch.png diff --git a/docs/images/data_privacy.png b/doc/images/data_privacy.png similarity index 100% rename from docs/images/data_privacy.png rename to doc/images/data_privacy.png diff --git a/doc/navigation.md b/doc/navigation.md new file mode 100644 index 0000000..caf8c5d --- /dev/null +++ b/doc/navigation.md @@ -0,0 +1,36 @@ +# Project Navigation + +The plugin is intentionally plain Java with Gradle wrapper entrypoints. The useful local commands are: + +- `./gradlew test` +- `./gradlew performanceTest` +- `./gradlew verifyPlugin buildPlugin` +- `./gradlew runIde` + +## Runtime Entry Points + +- `CodeCompletion` handles workflow expressions, `uses`, `with`, secrets, shell values, local files, remote action refs, + and GitHub context/default environment completions. +- `HighlightAnnotator` handles editor diagnostics, symbol coloring, quick fixes, action update suggestions, and + variable/run output highlighting. +- `ReferenceContributor` handles local PSI references and remote web references. +- `WorkflowDocumentationProvider` handles hover and quick documentation. +- `WorkflowRunLanguageInjector` injects shell-like languages into `run` blocks based on `shell`. +- `WorkflowRunConfigurationType` and related workflow-run classes handle workflow dispatch from the IDE Run tool window. +- `GitHubRequestAuthorizations` centralizes GitHub account, optional token-env fallback, and anonymous request ordering. +- `GitHubActionCache`, `RemoteActionProviders`, and `RemoteServerSettings` resolve and cache local/remote action and + reusable workflow metadata. +- `GitHubWorkflowSettingsConfigurable` exposes the plugin settings page for language override, cache review/delete, + cache import/export, plugin cache size, and the tiny support button with suspicious amounts of caffeine energy. +- `WorkflowRunLogRenderer` compacts GitHub Actions logs into named blocks with `0001 |` line numbers, `run:` command + lines, ANSI cleanup, and warning/error classification for Run tool-window job consoles. + +## Tests + +Editor behavior should be tested through IntelliJ fixture entrypoints. See: + +- `doc/adr/0008-test-through-editor-and-runtime-boundaries.md` +- `doc/spec/editor-test-matrix.md` + +Remote GitHub behavior should use fake HTTP servers or explicit client boundaries. Network access in tests is guilty +until proven innocent. diff --git a/doc/spec/editor-test-matrix.md b/doc/spec/editor-test-matrix.md new file mode 100644 index 0000000..35af12c --- /dev/null +++ b/doc/spec/editor-test-matrix.md @@ -0,0 +1,125 @@ +# Editor Test Matrix + +This matrix tracks editor behavior that should be tested through IntelliJ fixtures, not private helpers. + +Official syntax references: + +- GitHub workflow syntax: https://docs.github.com/actions/learn-github-actions/workflow-syntax-for-github-actions +- GitHub contexts: https://docs.github.com/actions/learn-github-actions/contexts +- GitHub action metadata: https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax + +## Covered + +- Action `with` input validation from resolved action metadata. +- Action `with` input completion from resolved remote action metadata, temp-file local `action.yaml` metadata, and project-local `action.yml` metadata. +- Local action `uses` reference resolution to nested `action.yml`, nested `action.yaml`, and root `action.yml`. +- Local `uses` completion for step-level action directories, root `action.yml`, and job-level reusable workflow files, with the wrong callable type excluded. +- Remote `uses` completion for callables and refs already known from the cache or current workflow. +- Remote `uses` target completion can discover matching owner repositories from configured GitHub servers before the + callable metadata has been resolved. +- Remote `uses` ref completion can discover the latest 10 refs for a typed callable such as `actions/checkout@`. +- Resolved actions with cached major-version refs show an update quick-fix for older `v?\d+` refs. +- Remote `uses` ref completion for tags/branches resolved from public GitHub and GitHub Enterprise-shaped servers + through fake HTTP servers. +- GitHub Enterprise servers registered in JetBrains GitHub settings are used as remote metadata sources; the plugin does + not add a parallel server settings UI. +- Cache actions and settings are registered through `plugin.xml`, localized through resource bundles, and covered by + cache summary, clear, inspect, selected-delete, import, and export behavior tests. +- The default resource bundle and 20 locale resource bundles keep matching key sets with nonblank values. Settings, + inspection, workflow-run, and issue-report keys resolve for every configured locale. +- Locale bundles are checked for placeholder parity, encoding sanity, leaked generation tokens, and accidental English + fallback except for short technical labels such as `API URL`, `Ref`, `OS`, and `PowerShell`. +- Local reusable workflow `uses` reference resolution to project workflow files. +- Remote action and reusable workflow `uses` reference creation through IntelliJ `WebReference`, including exact GitHub URL target metadata. +- Configured GitHub Enterprise action references and styling keep the configured server URL instead of hard-coding github.com. +- Resolved local and remote `uses` values are styled as highlighted references. +- Resolved `uses` values expose quick documentation with action/workflow name, description, URL, inputs, outputs, and secrets. +- `needs` reference resolution to a previous job in scalar, quoted scalar, and array forms. +- Expression reference resolution for `inputs.*`, `inputs['name']`, `secrets.*`, `env.*`, `matrix.*`, `steps.*`, `steps.*.outputs.*`, `needs.*`, `needs.*.outputs.*`, reusable workflow `jobs.*`, and reusable workflow `jobs.*.outputs.*`. +- Expression quick documentation for resolved inputs, secrets, envs, matrix keys, steps, step outputs, needs, jobs, and + context collection segments such as `outputs`. +- Multiple expressions in one scalar resolve only the expression segment at the caret. +- Resolved expression segments are styled as highlighted references where there is a local target. +- Resolved expression segments use a workflow variable color; job IDs, job names, step IDs, and step names use a workflow + declaration color. +- Invalid `needs` job IDs in scalar/list form. +- `inputs.*` validation and completion for `workflow_call` and `workflow_dispatch` inputs. +- `secrets.*` validation for workflow call secrets. +- Automatic `secrets.GITHUB_TOKEN` highlighting and completion. +- Secret use inside `if` conditions. +- `env.*` validation and completion for workflow, job, step, Bash `$GITHUB_ENV`, PowerShell `$env:GITHUB_ENV`, and multiline file-command values. +- Default environment variable completion inside `run` shell text, with tests guarding current documented GitHub and + runner variables. +- Completion stays workflow-aware outside `run:` blocks and deliberately avoids step/YAML key suggestions inside plain + shell `run:` text; shell/global environment variable completion still works there. +- `github.*`, including current ID/protection keys from the official context reference, nested `github.event.*`, + `runner.*`, including `runner.debug`, `job.*`, nested `job.container.*`, strict local `job.services.*`, `strategy.*`, + `matrix.*`, and unknown external `vars.*` contexts. +- `gitea.*` context highlighting and completion uses the same key map as `github.*`. +- `job.services.` validation/completion/reference/styling from local job service definitions, including `id`, `network`, `ports`, and mapped port keys. +- Matrix keys from direct `strategy.matrix` entries and `strategy.matrix.include`. +- `steps..outputs.` validation and completion for previous run outputs, multiline `$GITHUB_OUTPUT`, `tee -a $GITHUB_OUTPUT`, and resolved action outputs. +- `steps..outputs.` references resolve to the declaring run block for file-command outputs and to the + producing `uses` step for action metadata outputs. +- Bracket notation validation for `inputs['name']`, `github['ref_name']`, `steps['id'].outputs['name']`, and `needs['job'].result`. +- Bracket notation completion for `inputs['']`, `github['']`, `steps['']`, `steps['id'].outputs['']`, `needs['']`, and `needs['job'].outputs['']`. +- `steps..outcome` and `steps..conclusion`. +- `needs..outputs.` and `needs..result` validation and completion for direct job dependencies. +- `jobs..outputs.` and `jobs..result` validation and completion for reusable workflow outputs. +- Remote and local reusable workflow `jobs..uses` input completion, input validation, secret completion, secret validation, `secrets: inherit`, and downstream output validation/completion. +- `with`/`secrets` parameters on resolved actions/workflows expose quick documentation with available metadata. +- Expression validation in documented context-bearing fields including `run-name`, `concurrency`, `jobs..concurrency.group`, `jobs..runs-on`, `jobs..environment.url`, `jobs..steps.continue-on-error`, and `jobs..steps.timeout-minutes`. +- Expression validation in additional documented context-bearing fields: `on.workflow_call.inputs..default`, scalar `jobs..container`, `jobs..container.credentials`, scalar `jobs..environment`, `jobs..strategy.fail-fast`, `jobs..strategy.max-parallel`, `jobs..defaults.run.shell`, and `jobs..services..credentials`. +- Completion/reference/styling synchronization for workflow-call input defaults. +- Expression validation in `runs-on` scalar and sequence values, folded block scalars, and multiline block-scalar expressions. +- Job output unused/used highlighting. +- Job outputs used inside expression functions such as `fromJson(needs..outputs.)`. +- Folded scalar expression highlighting and alias-backed scalar/map environment keys. +- Composite action metadata input references and `runs.steps` output references/completion. +- Composite action `runs.steps` can reference outputs from previous `uses` steps. +- Root expression completion for locally available contexts. +- `$GITHUB_ENV` and `$GITHUB_OUTPUT` completion in `run` blocks. +- `shell:` completion for GitHub-supported shells. +- Typing newline, `:`, `.`, or `@` in workflow files can trigger relevant auto-popup completion through workflow-aware + typed/enter handlers. +- Quick-fix text for invalid action inputs, unknown workflow inputs, secrets in `if`, and unused job outputs. +- Quick-fix execution for input replacement, invalid action input deletion, invalid reusable workflow secret deletion, invalid suffix deletion, and invalid member deletion. +- Quick-fix menu entries use icons while the editor gutter is kept reserved for durable reference/run markers instead of + stacking every available intention in the line ruler. +- Gutter/info action execution for action suppression, input suppression, and local action jump action dispatch. +- Gutter/info action execution for remote reload is tested with a controllable resolver boundary and no sleeps. +- Left-ruler reference markers are intentionally limited to local action file targets and file-command declarations such + as `$GITHUB_ENV` and `$GITHUB_OUTPUT`. +- Cache tools can refresh metadata, clear metadata, and restore suppressed action/input/output warnings. +- GitHub Workflow run configuration registration, workflow_dispatch input parsing, repository remote resolution, + current-branch ref selection, dispatch/cancel/status/log client behavior, and gutter play registration for dispatchable + workflows. +- `workflow_dispatch` does not show a run gutter action when no GitHub repository can be resolved. +- Workflow run HTTP behavior retries configured GitHub authorizations before anonymous access and reports 401/rate-limit + failures with a GitHub settings hint. +- Workflow run HTTP behavior tries matching IDE accounts, then other IDE GitHub accounts, then configured/default + environment tokens (`GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_PAT`), then anonymous access. +- Workflow run HTTP behavior reuses the first successful account authorization across polling calls and does not fall + back to anonymous requests after an authenticated rate-limit response. +- Workflow run tools expose run/rerun-failed/rerun-all/cancel/delete/download-log/download-artifacts actions from the Run + tool-window toolbar, only enabling downloads when GitHub exposes the relevant data. +- Workflow run process behavior streams job log deltas without auth-strategy noise, routes each GitHub job to the + Run-window job tree/detail console, keeps a single workflow run tab, shows JUnit-style status icons and elapsed times, + updates root/progress state for success/failure/cancel, summarizes temporary HTML/504 log failures as short "logs will + appear" notices, quietly retries in-progress HTTP log failures, fetches completed job logs before the whole run + completes, and the `workflow_dispatch` gutter marker switches to Stop while a run is tracked. +- Workflow run log rendering strips GitHub timestamps, strips ANSI controls while preserving warning/error/system + meaning, formats GitHub `##[group]` / `##[endgroup]` / `##[/group]` markers as named blocks with four-digit line + numbers, formats `##[command]` markers as `run:` lines, and classifies warning/error output for IDE console coloring. +- Regression tests cover prior issues for multiline outputs, `tee` outputs, composite `uses` step outputs, + `needs..result` inside conditions, action metadata fallback to `action.yaml`, GitHub Enterprise metadata, failed + remote downloads, and invalid virtual-file paths. +- A showcase workflow test covers a large mixed workflow with anchors, matrix strategy, services, local actions, local + reusable workflows, remote actions, remote reusable workflows, Gitea context, job outputs, needs, functions, and file-command outputs. +- GitHub context/default-env data is generated into checked-in resource snapshots by `./gradlew generateGitHubDocsData`; + tests ensure completion/highlighting metadata uses exactly those snapshots. +- A bounded large-workflow highlighting performance test runs through the dedicated `performanceTest` Gradle task. + +## Missing + +- Full `vars.*` completion. Repository/organization/environment variables are external and need settings or GitHub API support. diff --git a/docs/navigation.md b/docs/navigation.md deleted file mode 100644 index b19a32e..0000000 --- a/docs/navigation.md +++ /dev/null @@ -1,87 +0,0 @@ -Here is a short Navigation: - -## General: - -- Test Pipeline: Test Pipeline as the Tests itself are broken, JetBrains Test System cant work with async tasks out of - the box. -- Plugin Complexity: This i want to reduce! I am really sorry about the Plugin complexity, when i started it was much - easier but then is was also unstable as hell. - - No own objects & No Constants - I try to keep the number of own objects low, as i have seen memory leaks while - using custom objects! Looks like i always need to clean them up by myself for every Context like: open & close - Project, PsiElementโ€ฆ - - JetBrains does not like to have Read, Write, IO, Network traffic, Syntax Highlighting,โ€ฆ in the same thread. Thats - why i often need things like this: - - `ApplicationManager.getApplication().executeOnPooledThread` - - `ApplicationManager.getApplication().invokeLater` - - `ApplicationManager.getApplication().isUnitTestMode()` - - `ApplicationManager.getApplication().runReadAction` - -## Package: [Services](https://github.com/YunaBraska/github-workflow-plugin/tree/main/src/main/java/com/github/yunabraska/githubworkflow/services): - -These are the trigger and entry place to start looking at. These are the Extensions which are registered in -the [Plugin.xml](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/resources/META-INF/plugin.xml) - -- [CodeCompletion](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java) - - completes code. Its one of my first classes. -- [FileIconProvider](https://github.com/YunaBraska/github-workflow-plugin/blobmain/src/main/java/com/github/yunabraska/githubworkflow/services/FileIconProvider.java) - - Marks the files with an GitHub Icon -- [GitHubActionCache](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java) - - This might be interesting for you, as this is the core logic to have a GitHub Actions and Workflow cache over all - Projects - - Careful, i did only manage to store java maps, everything else is pretty hard to `serialize` and `deserialize` - -- [HighlightAnnotator](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java) - - Adds Syntax Highlighting and text formats for the Reference Contributor (It receives PsiElements which are in your - View or have been changed) -- [ReferenceContributor](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java) - - Adds References inline or external links to `Actions`, `Workflows` and now also on `Needs` (It receives - PsiElements which are in your View or have been changed) -- [PluginErrorReportSubmitter](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitter.java) - - Users can submit an issue on GitHub on any Exception - _(pretty simple and clear. We donโ€™t need actions here)_ -- [ProjectStartup](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/ProjectStartup.java) - - as it already tells, its the executor on Project Startup -- [SchemaProvider](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/SchemaProvider.java) - - It provides several GitHub YAML Schemas - _(pretty simple and clear. We donโ€™t need actions here)_ - -## Package: [Logic](https://github.com/YunaBraska/github-workflow-plugin/tree/main/src/main/java/com/github/yunabraska/githubworkflow/logic): - -a doubtful attempt to move some common PsiElements extraction to named classes and hopefully get a faster overview whats -going on. - -- You will find the logic for Syntax Highlighting, Reference Contributor, Code Completion for each logical element - like `Action`, `Envs`, `GitHub`, `Inputs`, `Jobs`,โ€ฆ - -## Package: [Helper](https://github.com/YunaBraska/github-workflow-plugin/tree/main/src/main/java/com/github/yunabraska/githubworkflow/helper): - -Boring helper / utils classes - -- [PsiElementHelper](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/helper/PsiElementHelper.java) - - A core logic to navigate through the PsiElements -- [GitHubWorkflowConfig](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfig.java) - - A core config, mostly about the descriptions of the PsiElements - -## My next Plans: - -- Creation of a `PreProcessor` to have only one place to parse PsiElements: - - Why: - The [HighlightAnnotator](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java) - and [ReferenceContributor](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java) - are doing mostly the same operations - and [CodeCompletion](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java) - is also doing partially the same things. This leads to duplicated code, complexity and context issues like i - mentioned - about same thread with different contexts (Read, Write, IO, Network traffic, Syntax Highlighting) which JetBrains - doesnโ€™t like - - Cache: I would prefer to use the Build in cache for PsiElements - like: `PsiElement.getUserData()` & `PsiElement.putUserData()` This cache is managed by jetBrains and can - be - easily - picked up - by [HighlightAnnotator](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java) & [ReferenceContributor](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java) & [CodeCompletion](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java) - - - Trigger [HighlightAnnotator](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java) & [ReferenceContributor](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java): - after the `PreProcessor` is done. Currently i only know how to trigger Syntax - Highlighting: [triggerSyntaxHighlightingForActiveFiles()](https://github.com/YunaBraska/github-workflow-plugin/blob/main/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java) - - Trigger `Preprocessor` after FileChange: this should be simple and there should be a fixed delay for e.g. 5 - seconds, - so that we are not spamming the `PreProcessor` after every typing. diff --git a/gradle.properties b/gradle.properties index 150abd2..9d175f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,28 +2,26 @@ pluginId=com.github.yunabraska.githubworkflowplugin pluginGroup = berlin.yuna -pluginName = Github-Workflow-Plugin +pluginName = GitHub Workflow pluginDescription = Your Ultimate Wingman for GitHub Workflows and Actions -# SemVer format -> https://semver.org -pluginVersion = 2024.3.0 +# Date-based SemVer: YYYY.M.D. The tag workflow updates this before creating vYYYY.M.D tags. +pluginVersion = 2026.5.20 vendorName=Yuna Morgenstern vendorEmail=yuna@code-space.dev # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html -pluginSinceBuild = 243 +pluginSinceBuild = 242 # Not specifying until-build means it will include all future builds (including unreleased IDE versions, which might impact compatibility later). -pluginUntilBuild = 243.* # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension -platformType = IC -platformVersion = 2024.3 +platformVersion = 2026.1.2 pluginUrl=https://github.com/YunaBraska/github-workflow-plugin # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 -platformPlugins = org.jetbrains.plugins.github,org.jetbrains.plugins.yaml +platformPlugins = org.jetbrains.plugins.github,org.jetbrains.plugins.yaml,com.jetbrains.sh # Gradle Releases -> https://github.com/gradle/gradle/releases #gradleVersion = 8.9.0 @@ -35,3 +33,7 @@ kotlin.stdlib.default.dependency = false # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html # suppress inspection "UnusedProperty" org.gradle.unsafe.configuration-cache = false + +# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html +# suppress inspection "UnusedProperty" +org.gradle.caching = true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c8..4bdb2a9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/qodana.yml b/qodana.yml index c09f8da..7763e4b 100644 --- a/qodana.yml +++ b/qodana.yml @@ -4,7 +4,7 @@ version: 1.0 profile: name: qodana.recommended - projectJDK: 17 + projectJDK: 25 exclude: - name: All paths: diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..2daf929 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'github-workflow-plugin' diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index ae12a1f..0000000 --- a/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "github-workflow-plugin" diff --git a/src/main/java/com/github/yunabraska/githubworkflow/helper/AutoPopupInsertHandler.java b/src/main/java/com/github/yunabraska/githubworkflow/helper/AutoPopupInsertHandler.java index 67a68f9..dde14ba 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/AutoPopupInsertHandler.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/AutoPopupInsertHandler.java @@ -12,7 +12,7 @@ public class AutoPopupInsertHandler implements InsertHa @Override public void handleInsert(@NotNull final InsertionContext context, @NotNull final T item) { - AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(context.getEditor(), null); + AutoPopupController.getInstance(context.getProject()).scheduleAutoPopup(context.getEditor()); } public static void addSuffix(final InsertionContext ctx, final LookupElement item, final char suffix) { @@ -55,8 +55,8 @@ private static boolean isLineBreak(final char c) { private static String toInsertString(final char suffix, final CharSequence documentChars, final int tailOffset) { final StringBuilder sb = new StringBuilder(); sb.append(suffix); - final boolean isNextChatSpace = tailOffset < documentChars.length() && documentChars.charAt(tailOffset + 1) == ' '; - if (suffix != '.' && !isNextChatSpace) { + final boolean isNextCharSpace = tailOffset < documentChars.length() && documentChars.charAt(tailOffset) == ' '; + if (suffix != '.' && !isNextCharSpace) { sb.append(' '); } return sb.toString(); diff --git a/src/main/java/com/github/yunabraska/githubworkflow/helper/FileDownloader.java b/src/main/java/com/github/yunabraska/githubworkflow/helper/FileDownloader.java index a1e542e..2bfeb1a 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/FileDownloader.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/FileDownloader.java @@ -2,12 +2,10 @@ import com.intellij.ide.impl.ProjectUtil; import com.intellij.openapi.application.ApplicationInfo; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.ProjectManager; -import com.intellij.util.io.HttpRequests; +import com.intellij.util.concurrency.AppExecutorUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.github.api.GithubApiRequest; import org.jetbrains.plugins.github.api.GithubApiRequestExecutor; import org.jetbrains.plugins.github.api.GithubApiResponse; @@ -20,9 +18,8 @@ import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URI; -import java.net.URL; -import java.util.Objects; import java.util.Optional; +import java.util.Comparator; import java.util.concurrent.Future; import static java.util.Optional.ofNullable; @@ -37,10 +34,11 @@ private FileDownloader() { public static String downloadFileFromGitHub(final String downloadUrl) { return GHAccountsUtil.getAccounts().stream() + .sorted(Comparator.comparingInt(account -> account.getServer().isGithubDotCom() ? 0 : 1)) .map(account -> downloadFromGitHub(downloadUrl, account)) - .filter(Objects::nonNull) + .filter(PsiElementHelper::hasText) .findFirst() - .orElse(null); + .orElseGet(() -> downloadContent(downloadUrl)); } @@ -49,7 +47,8 @@ public static String downloadContent(final String urlString) { LOG.info("Download [" + urlString + "]"); try { final ApplicationInfo applicationInfo = ApplicationInfo.getInstance(); - final Future future = ApplicationManager.getApplication().executeOnPooledThread(() -> downloadSync(urlString, applicationInfo.getBuild().getProductCode() + "/" + applicationInfo.getFullVersion())); + final Future future = AppExecutorUtil.getAppExecutorService() + .submit(() -> downloadSync(urlString, applicationInfo.getBuild().getProductCode() + "/" + applicationInfo.getFullVersion())); return future.get(); } catch (final Exception e) { LOG.warn("Execution failed for [" + urlString + "] message [" + (e instanceof NullPointerException ? null : e.getMessage()) + "]"); @@ -57,62 +56,43 @@ public static String downloadContent(final String urlString) { return ""; } -// @Nullable -// public static String downloadSync(final String urlString, final String userAgent) { -// try { -// return HttpRequests -// .request(urlString) -// .gzip(true) -// .readTimeout(1000) -// .connectTimeout(1000) -// .userAgent(userAgent) -// .tuner(request -> request.setRequestProperty("Client-Name", "GitHub Workflow Plugin")) -// .readString(); -// } catch (final Exception e) { -// return null; -// } -// } -@Nullable -public static String downloadSync(final String urlString, final String userAgent) { - HttpURLConnection connection = null; - try { - connection = (HttpURLConnection) new URI(urlString).toURL().openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(1000); // Connect timeout - connection.setReadTimeout(1000); // Read timeout - connection.setRequestProperty("User-Agent", userAgent); - connection.setRequestProperty("Client-Name", "GitHub Workflow Plugin"); + public static String downloadSync(final String urlString, final String userAgent) { + HttpURLConnection connection = null; + try { + connection = (HttpURLConnection) new URI(urlString).toURL().openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(1000); + connection.setReadTimeout(1000); + connection.setRequestProperty("User-Agent", userAgent); + connection.setRequestProperty("Client-Name", "GitHub Workflow Plugin"); - // Check for successful response code or throw error - if (connection.getResponseCode() / 100 != 2) { - throw new IOException("HTTP error code: " + connection.getResponseCode()); - } + if (connection.getResponseCode() / 100 != 2) { + throw new IOException("HTTP error code: " + connection.getResponseCode()); + } - // Read response - try (final BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - final StringBuilder response = new StringBuilder(); - String inputLine; - while ((inputLine = in.readLine()) != null) { - response.append(inputLine).append(System.lineSeparator()); + try (final BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + final StringBuilder response = new StringBuilder(); + String inputLine; + while ((inputLine = in.readLine()) != null) { + response.append(inputLine).append(System.lineSeparator()); + } + return response.toString(); + } + } catch (final Exception ignored) { + return ""; + } finally { + if (connection != null) { + connection.disconnect(); } - return response.toString(); - } - } catch (final Exception e) { - // Handle exceptions accordingly, returning null is often not a good practice - return null; - } finally { - if (connection != null) { - connection.disconnect(); } } -} private static String downloadFromGitHub(final String downloadUrl, final GithubAccount account) { return ofNullable(ProjectUtil.getActiveProject()) .or(() -> Optional.of(ProjectManager.getInstance().getDefaultProject())) .map(project -> GHCompatibilityUtil.getOrRequestToken(account, project)) .map(token -> downloadContent(downloadUrl, account, token)) - .orElse(null); + .orElse(""); } private static String downloadContent(final String downloadUrl, final GithubAccount account, final String token) { @@ -132,12 +112,12 @@ public String extractResult(final @NotNull GithubApiResponse response) { } }); } catch (final IOException ignored) { - return null; + return ""; } } }); } catch (final Exception ignored) { - return null; + return ""; } } } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfig.java b/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfig.java index 664881f..db65eb8 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfig.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfig.java @@ -1,7 +1,15 @@ package com.github.yunabraska.githubworkflow.helper; -import java.util.HashMap; +import com.github.yunabraska.githubworkflow.services.GitHubWorkflowBundle; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -9,8 +17,11 @@ @SuppressWarnings("java:S2386") public class GitHubWorkflowConfig { - public static final Pattern PATTERN_GITHUB_OUTPUT = Pattern.compile("echo\\s+\"(\\w+)=(.*?)\"\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_OUTPUT\\}?\"?"); - public static final Pattern PATTERN_GITHUB_ENV = Pattern.compile("echo\\s+\"(\\w+)=(.*?)\"\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_ENV\\}?\"?"); + public static final Pattern PATTERN_GITHUB_OUTPUT = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_OUTPUT\\}?\"?"); + public static final Pattern PATTERN_GITHUB_OUTPUT_TEE = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*\\|\\s*tee\\s+(?:-[A-Za-z]+\\s+)*.*\\$\\w*:?\\{?GITHUB_OUTPUT\\}?"); + public static final Pattern PATTERN_GITHUB_ENV = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_ENV\\}?\"?"); + public static final Pattern PATTERN_GITHUB_OUTPUT_MULTILINE = Pattern.compile("(?:echo\\s+)?[\"']?([A-Za-z_][A-Za-z0-9_-]*)<<[^\"'\\r\\n]+[\"']?"); + public static final Pattern PATTERN_GITHUB_ENV_MULTILINE = Pattern.compile("(?:echo\\s+)?[\"']?([A-Za-z_][A-Za-z0-9_-]*)<<[^\"'\\r\\n]+[\"']?"); public static final long CACHE_ONE_DAY = 24L * 60 * 60 * 1000; public static final String FIELD_ON = "on"; public static final String FIELD_IF = "if"; @@ -18,149 +29,146 @@ public class GitHubWorkflowConfig { public static final String FIELD_ENVS = "env"; public static final String FIELD_RUN = "run"; public static final String FIELD_RUNS = "runs"; + public static final String FIELD_SHELL = "shell"; + public static final String FIELD_JOB = "job"; public static final String FIELD_JOBS = "jobs"; + public static final String FIELD_MATRIX = "matrix"; + public static final String FIELD_STRATEGY = "strategy"; public static final String FIELD_VARS = "vars"; public static final String FIELD_WITH = "with"; public static final String FIELD_USES = "uses"; public static final String FIELD_NEEDS = "needs"; public static final String FIELD_STEPS = "steps"; + public static final String FIELD_SERVICES = "services"; public static final String FIELD_RUNNER = "runner"; public static final String FIELD_GITHUB = "github"; + public static final String FIELD_GITEA = "gitea"; public static final String FIELD_DEFAULT = "${{}}"; public static final String FIELD_INPUTS = "inputs"; public static final String FIELD_OUTPUTS = "outputs"; public static final String FIELD_SECRETS = "secrets"; + public static final String FIELD_PORTS = "ports"; + public static final String FIELD_RESULT = "result"; public static final String FIELD_CONCLUSION = "conclusion"; public static final String FIELD_OUTCOME = "outcome"; public static final Map>> DEFAULT_VALUE_MAP = initProcessorMap(); + public static final Map SHELLS = initShells(); private static Map>> initProcessorMap() { - final Map>> result = new HashMap<>(); + final Map>> result = new LinkedHashMap<>(); result.put(FIELD_GITHUB, GitHubWorkflowConfig::getGitHubContextEnvs); + result.put(FIELD_GITEA, GitHubWorkflowConfig::getGitHubContextEnvs); + result.put(FIELD_JOB, GitHubWorkflowConfig::getJobItems); result.put(FIELD_ENVS, GitHubWorkflowConfig::getGitHubEnvs); result.put(FIELD_RUNNER, GitHubWorkflowConfig::getRunnerItems); + result.put(FIELD_STRATEGY, GitHubWorkflowConfig::getStrategyItems); result.put(FIELD_DEFAULT, GitHubWorkflowConfig::getCaretBracketItems); return result; } - private static HashMap getRunnerItems() { - final HashMap result = new HashMap<>(); - result.put("name", "The name of the runner executing the job."); - result.put("os", "The operating system of the runner executing the job. Possible values are Linux, Windows, or macOS."); - result.put("arch", "The architecture of the runner executing the job. Possible values are X86, X64, ARM, or ARM64."); - result.put("temp", "The path to a temporary directory on the runner. This directory is emptied at the beginning and end of each job. Note that files will not be removed if the runner's user account does not have permission to delete them."); - result.put("tool_cache", "he path to the directory containing preinstalled tools for GitHub-hosted runners. For more information, see \"About GitHub-hosted runners\"."); - result.put("debug", "The path to the directory containing preinstalled tools for GitHub-hosted runners. For more information, see \"About GitHub-hosted runners\"."); + private static Map initShells() { + final Map result = new LinkedHashMap<>(); + result.put("bash", message("completion.shell.bash")); + result.put("sh", message("completion.shell.sh")); + result.put("pwsh", message("completion.shell.pwsh")); + result.put("powershell", message("completion.shell.powershell")); + result.put("cmd", message("completion.shell.cmd")); + result.put("python", message("completion.shell.python")); + return Collections.unmodifiableMap(result); + } + + private static Map getRunnerItems() { + final Map result = new LinkedHashMap<>(); + result.put("name", message("completion.runner.name")); + result.put("os", message("completion.runner.os")); + result.put("arch", message("completion.runner.arch")); + result.put("temp", message("completion.runner.temp")); + result.put("tool_cache", message("completion.runner.toolCache")); + result.put("debug", message("completion.runner.debug")); + result.put("environment", message("completion.runner.environment")); return result; } - private static HashMap getCaretBracketItems() { - final HashMap result = new HashMap<>(); - result.put(FIELD_INPUTS, "Workflow inputs e.g. from workflow_dispatch, workflow_call"); - result.put(FIELD_SECRETS, "Workflow secrets"); - result.put(FIELD_JOBS, "Workflow jobs"); - result.put(FIELD_STEPS, "steps with 'id' of the current job"); - result.put(FIELD_ENVS, "Environment variables from jobs amd steps"); - result.put(FIELD_VARS, "The vars context contains custom configuration variables set at the organization, repository, and environment levels. For more information about defining configuration variables for use in multiple workflows"); - result.put(FIELD_NEEDS, "Identifies any jobs that must complete successfully before this job will run. It can be a string or array of strings. If a job fails, all jobs that need it are skipped unless the jobs use a conditional statement that causes the job to continue."); - result.put(FIELD_GITHUB, "Information about the workflow run and the event that triggered the run. You can also read most of the github context data in environment variables. For more information about environment variables"); + private static Map getJobItems() { + final Map result = new LinkedHashMap<>(); + result.put("status", message("completion.job.status")); + result.put("check_run_id", message("completion.job.checkRunId")); + result.put("container", message("completion.job.container")); + result.put("services", message("completion.job.services")); + result.put("workflow_ref", message("completion.job.workflowRef")); + result.put("workflow_sha", message("completion.job.workflowSha")); + result.put("workflow_repository", message("completion.job.workflowRepository")); + result.put("workflow_file_path", message("completion.job.workflowFilePath")); return result; } - //TODO: autogenerate this - //https://docs.github.com/en/actions/learn-github-actions/contexts#github-context - private static HashMap getGitHubContextEnvs() { - final HashMap result = new HashMap<>(); - result.put("action", "The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2."); - result.put("action_path", "The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action, for example by changing directories to the path: cd ${{ github.action_path }} ."); - result.put("action_ref", "For a step executing an action, this is the ref of the action being executed. For example, v2."); - result.put("action_repository", "For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout."); - result.put("action_status", "For a composite action, the current result of the composite action."); - result.put("actor", "The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from github.triggering_actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges."); - result.put("actor_id", "The account ID of the person or app that triggered the initial workflow run. For example, 1234567. Note that this is different from the actor username."); - result.put("api_url", "The URL of the GitHub REST API."); - result.put("base_ref", "The base_ref or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target."); - result.put("env", "Path on the runner to the file that sets environment variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see \"Workflow commands for GitHub Actions.\""); - result.put("event", "The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in \"Events that trigger workflows.\" For example, for a workflow run triggered by the push event, this object contains the contents of the push webhook payload."); - result.put("event_name", "The name of the event that triggered the workflow run."); - result.put("event_path", "The path to the file on the runner that contains the full event webhook payload."); - result.put("graphql_url", "The URL of the GitHub GraphQL API."); - result.put("head_ref", "The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target."); - result.put("job", "The job_id of the current job. \nNote: This context property is set by the Actions runner, and is only available within the execution steps of a job. Otherwise, the value of this property will be null."); - result.put("job_workflow_sha", "For jobs using a reusable workflow, the commit SHA for the reusable workflow file."); - result.put("path", "Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see \"Workflow commands for GitHub Actions.\""); - result.put("ref", "The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/, for pull requests it is refs/pull//merge, and for tags it is refs/tags/. For example, refs/heads/feature-branch-1."); - result.put("ref_name", "The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1."); - result.put("ref_protected", "true if branch protections are configured for the ref that triggered the workflow run."); - result.put("ref_type", "The type of ref that triggered the workflow run. Valid values are branch or tag."); - result.put("repository", "The owner and repository name. For example, octocat/Hello-World."); - result.put("repository_id", "The ID of the repository. For example, 123456789. Note that this is different from the repository name."); - result.put("repository_owner", "The repository owner's username. For example, octocat."); - result.put("repository_owner_id", "The repository owner's account ID. For example, 1234567. Note that this is different from the owner's name."); - result.put("repositoryUrl", "The Git URL to the repository. For example, git://github.com/octocat/hello-world.git."); - result.put("retention_days", "The number of days that workflow run logs and artifacts are kept."); - result.put("run_id", "A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run."); - result.put("run_number", "A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run."); - result.put("run_attempt", "A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run."); - result.put("secret_source", "The source of a secret used in a workflow. Possible values are None, Actions, Dependabot, or Codespaces."); - result.put("server_url", "The URL of the GitHub server. For example: https://github.com."); - result.put("sha", "The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see \"Events that trigger workflows.\" For example, ffac537e6cbbf934b08745a378932722df287a53."); - result.put("token", "A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the GITHUB_TOKEN secret. For more information, see \"Automatic token authentication.\" \nNote: This context property is set by the Actions runner, and is only available within the execution steps of a job. Otherwise, the value of this property will be null."); - result.put("triggering_actor", "The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges."); - result.put("workflow", "The name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository."); - result.put("workflow_ref", "The ref path to the workflow. For example, octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch."); - result.put("workflow_sha", "The commit SHA for the workflow file."); - result.put("workspace", "The default working directory on the runner for steps, and the default location of your repository when using the checkout action."); + private static Map getStrategyItems() { + final Map result = new LinkedHashMap<>(); + result.put("fail-fast", message("completion.strategy.failFast")); + result.put("job-index", message("completion.strategy.jobIndex")); + result.put("job-total", message("completion.strategy.jobTotal")); + result.put("max-parallel", message("completion.strategy.maxParallel")); return result; } - //TODO: autogenerate this - //https://docs.github.com/en/actions/learn-github-actions/variables#using-the-vars-context-to-access-configuration-variable-values - private static HashMap getGitHubEnvs() { - final HashMap result = new HashMap<>(); - result.put("CI", "Always set to true."); - result.put("GITHUB_ACTION", "The name of the action currently running, or the id of a step. For example, for an action, __repo-owner_name-of-action-repo.\n\nGitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same script or action more than once in the same job, the name will include a suffix that consists of the sequence number preceded by an underscore. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2."); - result.put("GITHUB_ACTION_PATH", "The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action. For example, /home/runner/work/_actions/repo-owner/name-of-action-repo/v1."); - result.put("GITHUB_ACTION_REPOSITORY", "For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout."); - result.put("GITHUB_ACTIONS", "Always set to true when GitHub Actions is running the workflow. You can use this variable to differentiate when tests are being run locally or by GitHub Actions."); - result.put("GITHUB_ACTOR", "The name of the person or app that initiated the workflow. For example, octocat."); - result.put("GITHUB_ACTOR_ID", "The account ID of the person or app that triggered the initial workflow run. For example, 1234567. Note that this is different from the actor username."); - result.put("GITHUB_API_URL", "Returns the API URL. For example: https://api.github.com."); - result.put("GITHUB_BASE_REF", "The name of the base ref or target branch of the pull request in a workflow run. This is only set when the event that triggers a workflow run is either pull_request or pull_request_target. For example, main."); - result.put("GITHUB_ENV", "The path on the runner to the file that sets variables from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/set_env_87406d6e-4979-4d42-98e1-3dab1f48b13a. For more information, see \"Workflow commands for GitHub Actions.\""); - result.put("GITHUB_EVENT_NAME", "The name of the event that triggered the workflow. For example, workflow_dispatch."); - result.put("GITHUB_EVENT_PATH", "The path to the file on the runner that contains the full event webhook payload. For example, /github/workflow/event.json."); - result.put("GITHUB_GRAPHQL_URL", "Returns the GraphQL API URL. For example: https://api.github.com/graphql"); - result.put("GITHUB_HEAD_REF", "The head ref or source branch of the pull request in a workflow run. This property is only set when the event that triggers a workflow run is either pull_request or pull_request_target. For example, feature-branch-1."); - result.put("GITHUB_JOB", "The job_id of the current job. For example, greeting_job."); - result.put("GITHUB_PATH", "The path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/add_path_899b9445-ad4a-400c-aa89-249f18632cf5. For more information, see \"Workflow commands for GitHub Actions.\""); - result.put("GITHUB_REF", "The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/, for pull requests it is refs/pull//merge, and for tags it is refs/tags/. For example, refs/heads/feature-branch-1."); - result.put("GITHUB_REF_NAME", "The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1."); - result.put("GITHUB_REF_PROTECTED", "true if branch protections are configured for the ref that triggered the workflow run."); - result.put("GITHUB_REF_TYPE", "The type of ref that triggered the workflow run. Valid values are branch or tag."); - result.put("GITHUB_REPOSITORY", "The owner and repository name. For example, octocat/Hello-World."); - result.put("GITHUB_REPOSITORY_ID", "The ID of the repository. For example, 123456789. Note that this is different from the repository name."); - result.put("GITHUB_REPOSITORY_OWNER", "The repository owner's account ID. For example, 1234567. Note that this is different from the owner's name."); - result.put("GITHUB_RETENTION_DAYS", "The number of days that workflow run logs and artifacts are kept. For example, 90."); - result.put("GITHUB_RUN_ATTEMPT", "A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. For example, 3."); - result.put("GITHUB_RUN_ID", "A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. For example, 1658821493."); - result.put("GITHUB_RUN_NUMBER", "A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. For example, 3."); - result.put("GITHUB_SERVER_URL", "The URL of the GitHub server. For example: https://github.com."); - result.put("GITHUB_SHA", "The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see \"Events that trigger workflows.\" For example, ffac537e6cbbf934b08745a378932722df287a53."); - result.put("GITHUB_STEP_SUMMARY", "The path on the runner to the file that contains job summaries from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/_layout/_work/_temp/_runner_file_commands/step_summary_1cb22d7f-5663-41a8-9ffc-13472605c76c. For more information, see \"Workflow commands for GitHub Actions.\""); - result.put("GITHUB_WORKFLOW", "The name of the workflow. For example, My test workflow. If the workflow file doesn't specify a name, the value of this variable is the full path of the workflow file in the repository."); - result.put("GITHUB_WORKFLOW_REF", "The ref path to the workflow. For example, octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch."); - result.put("GITHUB_WORKFLOW_SHA", "The commit SHA for the workflow file."); - result.put("GITHUB_WORKSPACE", "The default working directory on the runner for steps, and the default location of your repository when using the checkout action. For example, /home/runner/work/my-repo-name/my-repo-name."); - result.put("RUNNER_ARCH", "The architecture of the runner executing the job. Possible values are X86, X64, ARM, or ARM64."); - result.put("RUNNER_DEBUG", "This is set only if debug logging is enabled, and always has the value of 1. It can be useful as an indicator to enable additional debugging or verbose logging in your own job steps."); - result.put("RUNNER_NAME", "The name of the runner executing the job. For example, Hosted Agent"); - result.put("RUNNER_OS", "The operating system of the runner executing the job. Possible values are Linux, Windows, or macOS. For example, Windows"); - result.put("RUNNER_TEMP", "The path to a temporary directory on the runner. This directory is emptied at the beginning and end of each job. Note that files will not be removed if the runner's user account does not have permission to delete them. For example, D:\\a\\_temp"); - result.put("RUNNER_TOOL_CACHE", "The path to the directory containing preinstalled tools for GitHub-hosted runners. For more information, see \"About GitHub-hosted runners\". For example, C:\\hostedtoolcache\\windows"); + private static Map getCaretBracketItems() { + final Map result = new LinkedHashMap<>(); + result.put(FIELD_INPUTS, message("completion.context.inputs")); + result.put(FIELD_SECRETS, message("completion.context.secrets")); + result.put(FIELD_JOB, message("completion.context.job")); + result.put(FIELD_JOBS, message("completion.context.jobs")); + result.put(FIELD_MATRIX, message("completion.context.matrix")); + result.put(FIELD_STRATEGY, message("completion.context.strategy")); + result.put(FIELD_STEPS, message("completion.context.steps")); + result.put(FIELD_ENVS, message("completion.context.env")); + result.put(FIELD_VARS, message("completion.context.vars")); + result.put(FIELD_NEEDS, message("completion.context.needs")); + result.put(FIELD_GITHUB, message("completion.context.github")); + result.put(FIELD_GITEA, message("completion.context.gitea")); + result.put(FIELD_RUNNER, message("completion.context.runner")); return result; } + private static String message(final String key) { + return GitHubWorkflowBundle.message(key); + } + + private static Map getGitHubContextEnvs() { + return loadGeneratedItems("/github-docs/github-context.tsv"); + } + + private static Map getGitHubEnvs() { + return loadGeneratedItems("/github-docs/default-env.tsv"); + } + + private static Map loadGeneratedItems(final String resourcePath) { + try (InputStream stream = GitHubWorkflowConfig.class.getResourceAsStream(resourcePath)) { + if (stream == null) { + return Map.of(); + } + return readGeneratedItems(stream); + } catch (IOException ignored) { + return Map.of(); + } + } + + private static Map readGeneratedItems(final InputStream stream) throws IOException { + final Map result = new LinkedHashMap<>(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.isBlank() || line.startsWith("#")) { + continue; + } + final String[] parts = line.split("\t", 2); + if (parts.length == 2 && !parts[0].isBlank()) { + result.put(parts[0], parts[1]); + } + } + } + return Collections.unmodifiableMap(result); + } + private GitHubWorkflowConfig() { } } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelper.java b/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelper.java index cafbdaf..e68cec7 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelper.java @@ -8,34 +8,67 @@ import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import org.jetbrains.yaml.psi.YAMLScalar; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import static com.github.yunabraska.githubworkflow.helper.AutoPopupInsertHandler.addSuffix; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_IF; public class GitHubWorkflowHelper { + private static final String COMPLETION_DUMMY = "IntellijIdeaRulezzz"; private GitHubWorkflowHelper() { // static helper } public static Optional getCaretBracketItem(final PsiElement position, final int offset, final String[] prefix) { - final String wholeText = position.getText(); - if (wholeText == null) { + final PsiElement context = completionContextElement(position, offset); + final String rawText = context.getText(); + if (rawText == null) { return Optional.empty(); } - final int cursorRel = offset - position.getTextRange().getStartOffset(); + final int rawCursorRel = offset - context.getTextRange().getStartOffset(); + final int dummyIndex = rawText.indexOf(COMPLETION_DUMMY); + final String wholeText = rawText.replace(COMPLETION_DUMMY, ""); + final int adjustedCursorRel = dummyIndex >= 0 && dummyIndex < rawCursorRel + ? rawCursorRel - COMPLETION_DUMMY.length() + : rawCursorRel; + final int cursorRel = Math.max(0, Math.min(adjustedCursorRel, wholeText.length())); final String offsetText = wholeText.substring(0, cursorRel); final int bracketStart = offsetText.lastIndexOf("${{"); - if (cursorRel > 2 && isInBrackets(offsetText, bracketStart) || PsiElementHelper.getParent(position, FIELD_IF).isPresent()) { + if (cursorRel > 2 && isInBrackets(offsetText, bracketStart) || PsiElementHelper.getParent(context, FIELD_IF).isPresent()) { return getCaretBracketItem(prefix, wholeText, cursorRel); } return Optional.empty(); } + private static PsiElement completionContextElement(final PsiElement position, final int offset) { + PsiElement current = position; + PsiElement fallback = position; + while (current != null && current.getParent() != current) { + final boolean containsOffset = current.getTextRange() != null + && current.getTextRange().getStartOffset() <= offset + && offset <= current.getTextRange().getEndOffset(); + if (containsOffset && current.getText() != null && current.getText().contains(COMPLETION_DUMMY)) { + fallback = current; + if (PsiElementHelper.isTextElement(current) || current instanceof YAMLScalar) { + return current; + } + } + current = current.getParent(); + } + return fallback; + } + public static Optional getCaretBracketItem(final String[] prefix, final String wholeText, final int cursorRel) { + final Optional pathItems = getPathCompletionItems(prefix, wholeText, cursorRel); + if (pathItems.isPresent()) { + return pathItems; + } final char previousChar = cursorRel == 0 ? ' ' : wholeText.charAt(cursorRel - 1); if (cursorRel > 1 && previousChar == '.') { //NEXT ELEMENT @@ -54,6 +87,82 @@ public static Optional getCaretBracketItem(final String[] prefix, fina } } + private static Optional getPathCompletionItems(final String[] prefix, final String wholeText, final int cursorRel) { + final int pathStart = getPathStartIndex(wholeText, cursorRel); + if (pathStart >= cursorRel) { + return Optional.empty(); + } + final String path = wholeText.substring(pathStart, cursorRel); + if (!path.contains(".") && !path.contains("[")) { + return Optional.empty(); + } + final CaretPath caretPath = parseCaretPath(path); + if (caretPath.items().length == 0) { + return Optional.empty(); + } + prefix[0] = caretPath.prefix(); + return Optional.of(caretPath.items()); + } + + private static int getPathStartIndex(final String text, final int cursorRel) { + int result = Math.min(cursorRel, text.length()); + while (result > 0 && isPathChar(text.charAt(result - 1))) { + result--; + } + return result; + } + + private static CaretPath parseCaretPath(final String text) { + final List items = new ArrayList<>(); + final StringBuilder current = new StringBuilder(); + boolean inBracket = false; + char quote = 0; + for (int index = 0; index < text.length(); index++) { + final char character = text.charAt(index); + if (inBracket) { + if (quote != 0 && character == quote) { + quote = 0; + } else if (quote == 0 && (character == '\'' || character == '"')) { + quote = character; + } else if (quote == 0 && character == ']') { + addPathItem(items, current); + inBracket = false; + } else { + current.append(character); + } + } else if (character == '.') { + addPathItem(items, current); + } else if (character == '[') { + addPathItem(items, current); + inBracket = true; + } else if (character != '\'' && character != '"') { + current.append(character); + } + } + return new CaretPath(items.toArray(String[]::new), current.toString()); + } + + private static void addPathItem(final List items, final StringBuilder current) { + if (!current.isEmpty()) { + items.add(current.toString()); + current.setLength(0); + } + } + + private static boolean isPathChar(final char character) { + return Character.isLetterOrDigit(character) + || character == '_' + || character == '-' + || character == '.' + || character == '[' + || character == ']' + || character == '\'' + || character == '"'; + } + + private record CaretPath(String[] items, String prefix) { + } + public static int getStartIndex(final CharSequence currentText, final int fromIndex) { int result = fromIndex; while (result > 0) { @@ -126,7 +235,8 @@ public static boolean isWorkflowFile(final Path path) { return path != null && path.getNameCount() > 2 && isYamlFile(path) && path.getName(path.getNameCount() - 2).toString().equalsIgnoreCase("workflows") - && path.getName(path.getNameCount() - 3).toString().equalsIgnoreCase(".github"); + && (path.getName(path.getNameCount() - 3).toString().equalsIgnoreCase(".github") + || path.getName(path.getNameCount() - 3).toString().equalsIgnoreCase(".gitea")); } public static boolean isWorkflowTemplatePropertiesFile(final Path path) { @@ -146,7 +256,7 @@ public static boolean isIssueForms(final Path path) { public static boolean isIssueConfigFile(final Path path) { return path.getNameCount() > 2 && path.getName(path.getNameCount() - 3).toString().equalsIgnoreCase(".github") - && path.getName(path.getNameCount() - 2).toString().equalsIgnoreCase("workflow-templates") + && path.getName(path.getNameCount() - 2).toString().equalsIgnoreCase("ISSUE_TEMPLATE") && (path.getName(path.getNameCount() - 1).toString().equalsIgnoreCase("config.yml") || path.getName(path.getNameCount() - 1).toString().equalsIgnoreCase("config.yaml")); } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java b/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java index 2ef15a6..225ae93 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java @@ -5,8 +5,10 @@ import com.github.yunabraska.githubworkflow.model.SimpleElement; import com.github.yunabraska.githubworkflow.model.SyntaxAnnotation; import com.github.yunabraska.githubworkflow.services.GitHubActionCache; +import com.github.yunabraska.githubworkflow.services.GitHubWorkflowBundle; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.ide.util.PsiNavigationSupport; +import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.application.ApplicationManager; @@ -19,6 +21,7 @@ import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.yaml.psi.YAMLKeyValue; @@ -37,6 +40,7 @@ import static com.github.yunabraska.githubworkflow.model.NodeIcon.JUMP_TO_IMPLEMENTATION; import static com.github.yunabraska.githubworkflow.model.NodeIcon.RELOAD; import static com.github.yunabraska.githubworkflow.model.NodeIcon.SETTINGS; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.SUPPRESS_ON; import static com.github.yunabraska.githubworkflow.model.SyntaxAnnotation.createAnnotation; import static java.util.Optional.ofNullable; @@ -54,8 +58,8 @@ public static void addAnnotation(final AnnotationHolder holder, final PsiElement } public static void addAnnotation(final AnnotationHolder holder, final PsiElement element, final List result) { - if (holder != null) { - result.forEach(annotation -> annotation.createAnnotation(element, holder)); + if (holder != null && element != null && result != null && !result.isEmpty()) { + createAnnotation(element, element.getTextRange(), holder, result); } } @@ -70,17 +74,16 @@ public static void ifEnoughItems( if (parts.length < min || parts.length < 2) { final TextRange range = psiElement.getTextRange(); new SyntaxAnnotation( - "Incomplete statement [" + Arrays.stream(parts).map(SimpleElement::text).collect(Collectors.joining(".")) + "]", + GitHubWorkflowBundle.message("inspection.statement.incomplete", Arrays.stream(parts).map(SimpleElement::text).collect(Collectors.joining("."))), null, null ).createAnnotation(psiElement, new TextRange(range.getStartOffset() + parts[0].startIndexOffset(), range.getStartOffset() + parts[parts.length - 1].endIndexOffset()), holder); } else if (max != -1 && parts.length > max) { - final TextRange range = psiElement.getTextRange(); final SimpleElement[] tooLongPart = Arrays.copyOfRange(parts, max, parts.length); - final TextRange textRange = new TextRange(range.getStartOffset() + tooLongPart[0].startIndexOffset(), range.getStartOffset() + tooLongPart[tooLongPart.length - 1].endIndexOffset()); + final TextRange textRange = textRangeIncludingPreviousDot(psiElement, tooLongPart[0], tooLongPart[tooLongPart.length - 1]); new SyntaxAnnotation( - "Remove invalid suffix [" + Arrays.stream(tooLongPart).map(SimpleElement::text).collect(Collectors.joining(".")) + "]", - null, + GitHubWorkflowBundle.message("inspection.invalid.suffix.remove", Arrays.stream(tooLongPart).map(SimpleElement::text).collect(Collectors.joining("."))), + SUPPRESS_ON, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); } else { @@ -94,8 +97,8 @@ public static boolean isDefinedItem0(@NotNull final PsiElement psiElement, @NotN } else if (!items.contains(itemId.text())) { final TextRange textRange = simpleTextRange(psiElement, itemId); createAnnotation(psiElement, textRange, holder, items.stream().map(item -> new SyntaxAnnotation( - "Replace with [" + item + "]", - null, + GitHubWorkflowBundle.message("inspection.replace.with", item), + RELOAD, replaceAction(textRange, item) )).toList()); return false; @@ -109,10 +112,10 @@ public static boolean isField2Valid(@NotNull final PsiElement psiElement, @NotNu public static boolean isField2Valid(@NotNull final PsiElement psiElement, @NotNull final AnnotationHolder holder, final SimpleElement itemId, final List validFields) { if (!validFields.contains(itemId.text())) { - final TextRange textRange = simpleTextRange(psiElement, itemId); + final TextRange textRange = textRangeIncludingPreviousDot(psiElement, itemId, itemId); new SyntaxAnnotation( - "Remove invalid [" + itemId + "]", - null, + GitHubWorkflowBundle.message("inspection.invalid.remove", itemId.text()), + SUPPRESS_ON, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); return false; @@ -124,8 +127,8 @@ public static void isValidItem3(@NotNull final PsiElement psiElement, @NotNull f if (!isEmpty(outputs, itemId, psiElement, holder) && itemId != null && !outputs.contains(itemId.text())) { final TextRange textRange = simpleTextRange(psiElement, itemId); createAnnotation(psiElement, textRange, holder, outputs.stream().filter(PsiElementHelper::hasText).map(item -> new SyntaxAnnotation( - "Replace with [" + item + "]", - null, + GitHubWorkflowBundle.message("inspection.replace.with", item), + RELOAD, replaceAction(textRange, item) )).toList()); } @@ -134,7 +137,7 @@ public static void isValidItem3(@NotNull final PsiElement psiElement, @NotNull f @NotNull public static SyntaxAnnotation newReloadAction(final GitHubAction action) { return new SyntaxAnnotation( - "Reload [" + action.name() + "]", + GitHubWorkflowBundle.message("inspection.action.reload", action.name()), RELOAD, HighlightSeverity.INFORMATION, ProblemHighlightType.INFORMATION, @@ -145,7 +148,7 @@ public static SyntaxAnnotation newReloadAction(final GitHubAction action) { @NotNull public static SyntaxAnnotation newUnresolvedAction(final YAMLKeyValue element) { return new SyntaxAnnotation( - "Unresolved [" + removeQuotes(element.getValueText()) + "] - you may need to connect your GitHub", + GitHubWorkflowBundle.message("inspection.action.unresolved", removeQuotes(element.getValueText())), SETTINGS, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, @@ -159,8 +162,8 @@ public static SyntaxAnnotation newUnresolvedAction(final YAMLKeyValue element) { public static SyntaxAnnotation deleteInvalidAction(final YAMLKeyValue element) { final TextRange textRange = ofNullable(element.getValue()).map(PsiElement::getTextRange).orElseGet(element::getTextRange); return new SyntaxAnnotation( - "Remove invalid [" + element.getValueText() + "]", - null, + GitHubWorkflowBundle.message("inspection.invalid.remove", element.getValueText()), + SUPPRESS_ON, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, deleteElementAction(textRange) @@ -169,9 +172,8 @@ public static SyntaxAnnotation deleteInvalidAction(final YAMLKeyValue element) { @NotNull public static SyntaxAnnotation newJumpToFile(final GitHubAction action) { - //TODO: List Workflows connected to the action file return new SyntaxAnnotation( - "Jump to file [" + action.name() + "]", + GitHubWorkflowBundle.message("inspection.action.jump", action.name()), JUMP_TO_IMPLEMENTATION, HighlightSeverity.INFORMATION, ProblemHighlightType.INFORMATION, @@ -200,7 +202,8 @@ public static Consumer replaceAction(final TextRange textRang return fix -> { final PsiElement psiElement = fix.file().findElementAt(fix.editor().getCaretModel().getOffset()); if (psiElement != null) { - final Document document = PsiDocumentManager.getInstance(fix.project()).getDocument(psiElement.getContainingFile()); + final PsiFile topLevelFile = InjectedLanguageManager.getInstance(fix.project()).getTopLevelFile(psiElement); + final Document document = PsiDocumentManager.getInstance(fix.project()).getDocument(topLevelFile); if (document != null) { WriteCommandAction.runWriteCommandAction(fix.project(), () -> document.replaceString(textRange.getStartOffset(), textRange.getEndOffset(), newValue)); } @@ -221,12 +224,29 @@ public static TextRange simpleTextRange(@NotNull final PsiElement psiElement, @N ); } + private static TextRange textRangeIncludingPreviousDot( + @NotNull final PsiElement psiElement, + @NotNull final SimpleElement firstItem, + @NotNull final SimpleElement lastItem + ) { + final TextRange textRange = psiElement.getTextRange(); + final int startOffset = textRange.getStartOffset(); + final int localStart = firstItem.startIndexOffset(); + final int start = localStart > 0 && psiElement.getText().charAt(localStart - 1) == '.' + ? startOffset + localStart - 1 + : startOffset + localStart; + return new TextRange( + Math.max(start, startOffset), + Math.min(startOffset + lastItem.endIndexOffset(), textRange.getEndOffset()) + ); + } + private static boolean isEmpty(final Collection items, final SimpleElement itemId, @NotNull final PsiElement psiElement, @NotNull final AnnotationHolder holder) { if (itemId != null && items.isEmpty()) { final TextRange textRange = simpleTextRange(psiElement, itemId); createAnnotation(psiElement, textRange, holder, List.of(new SyntaxAnnotation( - "Delete invalid [" + itemId.text() + "]", - null, + GitHubWorkflowBundle.message("inspection.invalid.remove", itemId.text()), + SUPPRESS_ON, deleteElementAction(textRange) ))); return true; diff --git a/src/main/java/com/github/yunabraska/githubworkflow/helper/PsiElementHelper.java b/src/main/java/com/github/yunabraska/githubworkflow/helper/PsiElementHelper.java index bff6093..a75f2ec 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/PsiElementHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/PsiElementHelper.java @@ -8,9 +8,13 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.impl.source.tree.LeafPsiElement; +import com.github.yunabraska.githubworkflow.services.GitHubWorkflowBundle; import org.jetbrains.annotations.NotNull; +import org.jetbrains.yaml.psi.YAMLAlias; +import org.jetbrains.yaml.psi.YAMLAnchor; import org.jetbrains.yaml.psi.YAMLKeyValue; import org.jetbrains.yaml.psi.YAMLQuotedText; +import org.jetbrains.yaml.psi.YAMLScalarText; import org.jetbrains.yaml.psi.YAMLSequenceItem; import org.jetbrains.yaml.psi.impl.YAMLBlockScalarImpl; import org.jetbrains.yaml.psi.impl.YAMLBlockSequenceImpl; @@ -23,6 +27,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -35,7 +40,10 @@ import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_JOBS; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_STEPS; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.PATTERN_GITHUB_ENV; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.PATTERN_GITHUB_ENV_MULTILINE; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.PATTERN_GITHUB_OUTPUT; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.PATTERN_GITHUB_OUTPUT_MULTILINE; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.PATTERN_GITHUB_OUTPUT_TEE; import static java.util.Collections.unmodifiableList; import static java.util.Optional.ofNullable; @@ -127,7 +135,7 @@ public static void getTextElements(final List result, final PsiEleme } public static boolean isTextElement(final PsiElement element) { - return element instanceof YAMLPlainTextImpl || element instanceof YAMLQuotedText; + return element instanceof YAMLScalarText || element instanceof YAMLPlainTextImpl || element instanceof YAMLQuotedText; } public static List getAllElements(final PsiElement psiElement, final String keyName) { @@ -166,6 +174,7 @@ public static List getChildren(final PsiElement psiElement, final Class Arrays.stream(psiElements).filter(clazz::isInstance).map(clazz::cast).toList()) .filter(children -> !children.isEmpty()) + .or(() -> getAliasedChildren(psiElement, clazz)) .or(() -> ofNullable(psiElement) .map(PsiElement::getChildren) .flatMap(psiElements -> Arrays.stream(psiElements).map(child -> getChildren(child, clazz)).filter(children -> !children.isEmpty()).findFirst()) @@ -173,6 +182,51 @@ public static List getChildren(final PsiElement psiElement, final Class Optional> getAliasedChildren(final PsiElement psiElement, final Class clazz) { + return findDirectAlias(psiElement) + .flatMap(alias -> findAnchor(alias.getContainingFile(), alias.getAliasName())) + .map(YAMLAnchor::getMarkedValue) + .map(value -> getChildren(value, clazz)) + .filter(children -> !children.isEmpty()); + } + + private static Optional findDirectAlias(final PsiElement psiElement) { + return ofNullable(psiElement) + .map(PsiElement::getChildren) + .flatMap(children -> Arrays.stream(children) + .filter(YAMLAlias.class::isInstance) + .map(YAMLAlias.class::cast) + .findFirst()); + } + + private static Optional findAnchor(final PsiElement psiElement, final String name) { + final String normalizedName = normalizeAnchorName(name); + return ofNullable(psiElement) + .map(element -> { + if (element instanceof final YAMLAnchor anchor && isAnchorName(anchor, normalizedName)) { + return Optional.of(anchor); + } + return Arrays.stream(element.getChildren()) + .map(child -> findAnchor(child, normalizedName)) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + }) + .orElseGet(Optional::empty); + } + + private static boolean isAnchorName(final YAMLAnchor anchor, final String name) { + return Objects.equals(name, normalizeAnchorName(anchor.getName())) + || Objects.equals(name, normalizeAnchorName(anchor.getText())); + } + + private static String normalizeAnchorName(final String name) { + if (name == null || name.isBlank()) { + return ""; + } + return name.startsWith("&") || name.startsWith("*") ? name.substring(1) : name; + } + public static Optional getChild(final PsiElement psiElement, final String childKey) { return psiElement == null || childKey == null ? Optional.empty() : Optional.of(psiElement) .map(PsiElementHelper::getChildren) @@ -216,9 +270,22 @@ public static Optional toYAMLKeyValue(final PsiElement psiElement) } public static String getDescription(final PsiElement psiElement, final boolean requiredField) { - return psiElement == null ? "" : requiredString(psiElement, requiredField) - + getText(psiElement, "default").map(def -> "def[" + def + "]").orElse("") - + getText(psiElement, "description").or(() -> getText(psiElement, "desc")).map(desc -> " " + desc).orElse(""); + if (psiElement == null) { + return ""; + } + final List details = new ArrayList<>(); + getText(psiElement, "description").or(() -> getText(psiElement, "desc")) + .ifPresent(description -> details.add(GitHubWorkflowBundle.message("documentation.description", description))); + getText(psiElement, "type") + .ifPresent(type -> details.add(GitHubWorkflowBundle.message("documentation.type", type))); + if (requiredField) { + details.add(GitHubWorkflowBundle.message("documentation.required", getText(psiElement, "required").map(Boolean::parseBoolean).orElse(false))); + } + getText(psiElement, "default") + .ifPresent(defaultValue -> details.add(GitHubWorkflowBundle.message("documentation.default", defaultValue))); + getText(psiElement, "deprecationMessage") + .ifPresent(message -> details.add(GitHubWorkflowBundle.message("documentation.deprecated", message))); + return String.join("\n", details); } public static Optional toPath(final VirtualFile virtualFile) { @@ -227,16 +294,19 @@ public static Optional toPath(final VirtualFile virtualFile) { public static Optional toPath(final String path) { try { - return ofNullable(path).map(Paths::get).filter(p -> Files.exists(p) || ApplicationManager.getApplication().isUnitTestMode()); + return ofNullable(path) + .map(String::trim) + .filter(PsiElementHelper::looksLikePathText) + .map(Paths::get) + .filter(p -> Files.exists(p) || ApplicationManager.getApplication().isUnitTestMode()); } catch (final Exception ignored) { //e.g. java.nio.file.InvalidPathException: Illegal char <<> at index 0: <36ba1c43-b8f1-4f54-ace0-cef443d1e8f0>/etc/php/8.1/apache2/php.ini return Optional.empty(); } } - @NotNull - private static String requiredString(final PsiElement psiElement, final boolean requiredField) { - return requiredField ? "r[" + getText(psiElement, "required").map(Boolean::parseBoolean).orElse(false) + "] " : ""; + private static boolean looksLikePathText(final String path) { + return hasText(path) && !path.startsWith("{") && !path.matches("^<[0-9a-fA-F-]{36}>.*"); } public static Project getProject(final PsiElement psiElement) { @@ -252,7 +322,7 @@ public static boolean hasText(final String str) { } public static String goToDeclarationString() { - return String.format("Open declaration (%s)", Arrays.stream(KeymapUtil.getActiveKeymapShortcuts("GotoDeclaration").getShortcuts()) + return GitHubWorkflowBundle.message("documentation.open.declaration", Arrays.stream(KeymapUtil.getActiveKeymapShortcuts("GotoDeclaration").getShortcuts()) .limit(2) .map(KeymapUtil::getShortcutText) .collect(Collectors.joining(", ")) @@ -262,12 +332,9 @@ public static String goToDeclarationString() { private static Map toGithubOutputs(final String text) { final Map variables = new HashMap<>(); if (text.contains("GITHUB_OUTPUT")) { - final Matcher matcher = PATTERN_GITHUB_OUTPUT.matcher(text); - while (matcher.find()) { - if (matcher.groupCount() >= 2) { - variables.put(matcher.group(1), matcher.group(2)); - } - } + putMatches(variables, PATTERN_GITHUB_OUTPUT.matcher(text), false); + putMatches(variables, PATTERN_GITHUB_OUTPUT_TEE.matcher(text), false); + putMatches(variables, PATTERN_GITHUB_OUTPUT_MULTILINE.matcher(text), true); } return variables; } @@ -275,16 +342,20 @@ private static Map toGithubOutputs(final String text) { private static Map toGithubEnvs(final String text) { final Map variables = new HashMap<>(); if (text.contains("GITHUB_ENV")) { - final Matcher matcher = PATTERN_GITHUB_ENV.matcher(text); - while (matcher.find()) { - if (matcher.groupCount() >= 2) { - variables.put(matcher.group(1), matcher.group(2)); - } - } + putMatches(variables, PATTERN_GITHUB_ENV.matcher(text), false); + putMatches(variables, PATTERN_GITHUB_ENV_MULTILINE.matcher(text), true); } return variables; } + private static void putMatches(final Map variables, final Matcher matcher, final boolean multiline) { + while (matcher.find()) { + if (matcher.groupCount() >= 1) { + variables.putIfAbsent(matcher.group(1), multiline ? "" : matcher.group(2)); + } + } + } + private static String removeBrackets(final String text, final char... chars) { if (text != null && text.length() > 1) { for (final char c : chars) { @@ -331,7 +402,10 @@ private static List parseVariables(final LeafPsiElement element, private static List parseVariables(final PsiElement psiElement, final Function> method) { final List lineElements = getLineElements(psiElement); - return lineElements.stream().flatMap(line -> method.apply(line.text()).entrySet().stream().map(env -> new SimpleElement(env.getKey(), env.getValue(), line.range()))).toList(); + final Map result = new LinkedHashMap<>(); + lineElements.forEach(line -> method.apply(line.text()).forEach((key, value) -> result.putIfAbsent(key, new SimpleElement(key, value, line.range())))); + method.apply(psiElement.getText()).forEach((key, value) -> result.putIfAbsent(key, new SimpleElement(key, value, psiElement.getTextRange()))); + return new ArrayList<>(result.values()); } private static List getLineElements(final PsiElement psiElement) { diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java index 611fe36..f64eaaa 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java @@ -1,11 +1,14 @@ package com.github.yunabraska.githubworkflow.logic; import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.github.yunabraska.githubworkflow.model.IconRenderer; import com.github.yunabraska.githubworkflow.model.LocalActionReferenceResolver; import com.github.yunabraska.githubworkflow.model.SimpleElement; import com.github.yunabraska.githubworkflow.model.SyntaxAnnotation; import com.github.yunabraska.githubworkflow.services.GitHubActionCache; +import com.github.yunabraska.githubworkflow.services.GitHubWorkflowBundle; import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.lang.annotation.AnnotationBuilder; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; @@ -24,6 +27,8 @@ import java.util.Set; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_USES; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_ON; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_SECRETS; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_WITH; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.addAnnotation; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.deleteElementAction; @@ -31,6 +36,7 @@ import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.newJumpToFile; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.newReloadAction; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.newUnresolvedAction; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.replaceAction; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChild; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParent; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentStepOrJob; @@ -40,6 +46,9 @@ import static com.github.yunabraska.githubworkflow.model.NodeIcon.EMPTY; import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_OUTPUT; import static com.github.yunabraska.githubworkflow.model.NodeIcon.IGNORED; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.JUMP_TO_IMPLEMENTATION; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.RELOAD; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.SUPPRESS_ON; import static com.github.yunabraska.githubworkflow.model.NodeIcon.SUPPRESS_OFF; import static com.github.yunabraska.githubworkflow.model.SimpleElement.completionItemsOf; import static com.github.yunabraska.githubworkflow.services.GitHubActionCache.triggerSyntaxHighlightingForActiveFiles; @@ -56,34 +65,46 @@ public static void highLightAction(final AnnotationHolder holder, final YAMLKeyV .ifPresentOrElse(action -> { if (action.isResolved() && !action.isLocal()) { result.add(newReloadAction(action)); + newerMajorActionRef(element, action).ifPresent(result::add); } result.add(newSuppressAction(action)); - highlightLocalActions(holder, element, action, result); + highlightResolvedActionReference(holder, element, action, result); if (element != null && !action.isResolved() && (!action.isSuppressed())) { result.add(action.isLocal() ? deleteInvalidAction(element) : newUnresolvedAction(element)); } - }, () -> result.add(newUnresolvedAction(element))); //FIXME: is this a valid state? + }, () -> ofNullable(element).ifPresent(value -> result.add(newUnresolvedAction(value)))); addAnnotation(holder, element, result); } public static void highlightActionInput(final AnnotationHolder holder, final PsiElement psiElement) { toYAMLKeyValue(psiElement) .filter(withItem -> getParent(withItem.getParent(), FIELD_WITH).isPresent()) - .ifPresent(withItem -> getAction(getParentStepOrJob(withItem).orElse(null)) - .filter(GitHubAction::isResolved) - .ifPresent(action -> { - final Set inputs = action.freshInputs().keySet(); - final String inputId = withItem.getKeyText(); - newSuppressInput(action, inputId).createAnnotation(withItem, ofNullable(withItem.getKey()).map(PsiElement::getTextRange).orElseGet(withItem::getTextRange), holder); - if (!inputs.contains(inputId)) { - addAnnotation(holder, withItem, new SyntaxAnnotation( - "Delete invalid input [" + inputId + "]", - null, - deleteElementAction(withItem.getTextRange()) - )); - } - - })); + .ifPresent(withItem -> highlightCallableParameter(holder, withItem, FIELD_WITH)); + toYAMLKeyValue(psiElement) + .filter(secretItem -> getParent(secretItem.getParent(), FIELD_SECRETS).filter(secrets -> getParent(secrets, FIELD_ON).isEmpty()).isPresent()) + .ifPresent(secretItem -> highlightCallableParameter(holder, secretItem, FIELD_SECRETS)); + } + + private static void highlightCallableParameter(final AnnotationHolder holder, final YAMLKeyValue item, final String parameterType) { + getAction(getParentStepOrJob(item).orElse(null)) + .filter(GitHubAction::isResolved) + .ifPresent(action -> { + final Set validIds = FIELD_SECRETS.equals(parameterType) ? action.freshSecrets().keySet() : action.freshInputs().keySet(); + final String id = item.getKeyText(); + if (FIELD_WITH.equals(parameterType)) { + newSuppressInput(action, id).createAnnotation(item, ofNullable(item.getKey()).map(PsiElement::getTextRange).orElseGet(item::getTextRange), holder); + } + if (!validIds.contains(id)) { + final String label = FIELD_SECRETS.equals(parameterType) + ? GitHubWorkflowBundle.message("inspection.parameter.secret") + : GitHubWorkflowBundle.message("inspection.parameter.input"); + addAnnotation(holder, item, new SyntaxAnnotation( + GitHubWorkflowBundle.message("inspection.action.delete.invalid", label, id), + SUPPRESS_ON, + deleteElementAction(item.getTextRange()) + )); + } + }); } public static List highlightActionOutputs(final YAMLSequenceItem stepItem, final SimpleElement part) { @@ -113,20 +134,62 @@ public static Optional referenceGithubAction(final PsiElement ps ); } - private static void highlightLocalActions(final AnnotationHolder holder, final YAMLKeyValue element, final GitHubAction action, final List result) { - if (action.isResolved() && action.isLocal()) { + private static void highlightResolvedActionReference(final AnnotationHolder holder, final YAMLKeyValue element, final GitHubAction action, final List result) { + if (action.isResolved() && !action.isSuppressed()) { final String tooltip = goToDeclarationString(); getTextElement(element).ifPresent(textElement -> { - holder.newAnnotation(HighlightSeverity.INFORMATION, tooltip) + final AnnotationBuilder annotation = holder.newAnnotation(HighlightSeverity.INFORMATION, tooltip) .range(textElement) .textAttributes(DefaultLanguageHighlighterColors.HIGHLIGHTED_REFERENCE) - .tooltip(tooltip) - .create(); - result.add(newJumpToFile(action)); + .tooltip(tooltip); + if (action.isLocal()) { + final SyntaxAnnotation jumpToFile = newJumpToFile(action); + result.add(jumpToFile); + annotation.gutterIconRenderer(new IconRenderer(jumpToFile, element, JUMP_TO_IMPLEMENTATION)); + } + annotation.create(); }); } } + private static Optional newerMajorActionRef(final YAMLKeyValue element, final GitHubAction action) { + final String usesValue = action.usesValue(); + final int separator = usesValue.lastIndexOf('@'); + if (separator < 1 || separator == usesValue.length() - 1 || action.remoteRefs().isEmpty()) { + return Optional.empty(); + } + final String currentRef = usesValue.substring(separator + 1); + final Optional currentMajor = majorRef(currentRef); + if (currentMajor.isEmpty()) { + return Optional.empty(); + } + final Optional latestRef = action.remoteRefs().stream() + .filter(ref -> majorRef(ref).map(major -> major > currentMajor.get()).orElse(false)) + .max((left, right) -> Integer.compare(majorRef(left).orElse(0), majorRef(right).orElse(0))); + if (latestRef.isEmpty()) { + return Optional.empty(); + } + final String newUsesValue = usesValue.substring(0, separator + 1) + latestRef.get(); + final TextRange range = getTextElement(element).map(PsiElement::getTextRange).orElseGet(element::getTextRange); + return Optional.of(new SyntaxAnnotation( + GitHubWorkflowBundle.message("inspection.action.update.major", usesValue, newUsesValue), + RELOAD, + HighlightSeverity.WEAK_WARNING, + ProblemHighlightType.WEAK_WARNING, + replaceAction(range, newUsesValue) + )); + } + + private static Optional majorRef(final String ref) { + final String normalized = ofNullable(ref).orElse("").trim(); + final String digits = normalized.startsWith("v") || normalized.startsWith("V") + ? normalized.substring(1) + : normalized; + return digits.matches("\\d+") + ? Optional.of(Integer.parseInt(digits)) + : Optional.empty(); + } + // ########## CODE COMPLETION ########## public static List listActionsOutputs(final PsiElement psiElement) { return getAction(psiElement) @@ -147,7 +210,7 @@ private static SyntaxAnnotation newSuppressAction(final GitHubAction action) { final boolean suppressed = action.isSuppressed(); return new SyntaxAnnotation( toggleText(action.name(), suppressed), - suppressed ? SUPPRESS_OFF : null, + suppressed ? SUPPRESS_OFF : SUPPRESS_ON, HighlightSeverity.INFORMATION, suppressed ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.INFORMATION, f -> { @@ -162,7 +225,7 @@ private static SyntaxAnnotation newSuppressInput(final GitHubAction action, fina final boolean suppressed = action.ignoredInputs().contains(id); return new SyntaxAnnotation( toggleText(id, suppressed), - suppressed ? IGNORED : EMPTY, + suppressed ? IGNORED : SUPPRESS_ON, HighlightSeverity.INFORMATION, suppressed ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.INFORMATION, f -> { @@ -178,7 +241,7 @@ private static SyntaxAnnotation newSuppressOutput(final GitHubAction action, fin final HighlightSeverity level = action.freshOutputs().containsKey(id) ? HighlightSeverity.INFORMATION : HighlightSeverity.ERROR; return new SyntaxAnnotation( toggleText(id, suppressed), - null, + suppressed ? IGNORED : SUPPRESS_ON, level, suppressed ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.INFORMATION, f -> { @@ -190,7 +253,7 @@ private static SyntaxAnnotation newSuppressOutput(final GitHubAction action, fin @NotNull private static String toggleText(final String id, final boolean suppressed) { - return "Toggle warnings [" + (suppressed ? "on" : "off") + "] for [" + id + "]"; + return GitHubWorkflowBundle.message("inspection.warning.toggle", GitHubWorkflowBundle.message(suppressed ? "inspection.warning.on" : "inspection.warning.off"), id); } private Action() { diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/Envs.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/Envs.java index a1d6621..4589358 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Envs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Envs.java @@ -25,7 +25,6 @@ import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentJob; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentStep; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getText; -import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getTextElement; import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_ENV; import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_ENV_JOB; import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_ENV_ROOT; @@ -101,7 +100,6 @@ private static void addStepEnvs(final PsiElement psiElement, final List, Map> toMapWithKeyAndText() { return elements -> elements.stream() - .filter(keyValue -> getTextElement(keyValue).isPresent()) .collect(Collectors.toMap(YAMLKeyValue::getKeyText, keyValue -> getText(keyValue).orElse(""), (existing, replacement) -> existing)); } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/GitHub.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/GitHub.java index 4713c04..cac16b0 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/GitHub.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/GitHub.java @@ -8,6 +8,7 @@ import java.util.List; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.DEFAULT_VALUE_MAP; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_GITEA; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_GITHUB; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.ifEnoughItems; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isDefinedItem0; @@ -24,6 +25,14 @@ public static List codeCompletionGithub() { return completionItemsOf(DEFAULT_VALUE_MAP.get(FIELD_GITHUB).get(), ICON_ENV); } + public static void highLightGitea(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { + ifEnoughItems(holder, element, parts, 2, -1, envId -> isDefinedItem0(element, holder, envId, new ArrayList<>(DEFAULT_VALUE_MAP.get(FIELD_GITEA).get().keySet()))); + } + + public static List codeCompletionGitea() { + return completionItemsOf(DEFAULT_VALUE_MAP.get(FIELD_GITEA).get(), ICON_ENV); + } + private GitHub() { // static helper class } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java new file mode 100644 index 0000000..1d62895 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java @@ -0,0 +1,163 @@ +package com.github.yunabraska.githubworkflow.logic; + +import com.github.yunabraska.githubworkflow.model.SimpleElement; +import com.github.yunabraska.githubworkflow.services.GitHubWorkflowBundle; +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import org.jetbrains.yaml.psi.YAMLKeyValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.DEFAULT_VALUE_MAP; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_JOB; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_SERVICES; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.ifEnoughItems; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isDefinedItem0; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isField2Valid; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isValidItem3; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChild; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentJob; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getTextElements; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.removeQuotes; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_JOB; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_NODE; +import static com.github.yunabraska.githubworkflow.model.SimpleElement.completionItemsOf; + +public class JobContext { + + private static final String FIELD_CONTAINER = "container"; + private static final String FIELD_ID = "id"; + private static final String FIELD_NETWORK = "network"; + private static final String FIELD_PORTS = "ports"; + private static final Pattern PORT_PATTERN = Pattern.compile("(\\d+)(?:/(?:tcp|udp))?(?::\\d+)?"); + private static final List CONTAINER_FIELDS = List.of(FIELD_ID, FIELD_NETWORK); + private static final List SERVICE_FIELDS = List.of(FIELD_ID, FIELD_NETWORK, FIELD_PORTS); + + public static void highlightJob(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { + ifEnoughItems(holder, element, parts, 2, -1, field -> { + if (!isDefinedItem0(element, holder, field, new ArrayList<>(DEFAULT_VALUE_MAP.get(FIELD_JOB).get().keySet()))) { + return; + } + switch (field.text()) { + case FIELD_CONTAINER -> highlightContainer(holder, element, parts); + case FIELD_SERVICES -> highlightServices(holder, element, parts); + default -> ifEnoughItems(holder, element, parts, 2, 2, ignored -> { + // Valid scalar job context field. + }); + } + }); + } + + public static List codeCompletionJob() { + return completionItemsOf(DEFAULT_VALUE_MAP.get(FIELD_JOB).get(), ICON_JOB); + } + + public static List codeCompletionJob(final String parent, final PsiElement position) { + return switch (parent) { + case FIELD_CONTAINER -> completionItemsOf(toMap(CONTAINER_FIELDS, GitHubWorkflowBundle.message("completion.job.containerField")), ICON_NODE); + case FIELD_SERVICES -> completionItemsOf(toMap(listServiceIds(position), GitHubWorkflowBundle.message("completion.job.service")), ICON_NODE); + default -> List.of(); + }; + } + + public static List codeCompletionJob(final String parent, final String child, final PsiElement position) { + if (FIELD_SERVICES.equals(parent) && listServiceIds(position).contains(child)) { + return completionItemsOf(toMap(SERVICE_FIELDS, GitHubWorkflowBundle.message("completion.job.serviceField")), ICON_NODE); + } + return List.of(); + } + + public static List codeCompletionJob(final String parent, final String serviceId, final String serviceField, final PsiElement position) { + if (FIELD_SERVICES.equals(parent) && FIELD_PORTS.equals(serviceField)) { + return completionItemsOf(toMap(listServicePorts(position, serviceId), GitHubWorkflowBundle.message("completion.job.mappedServicePort")), ICON_NODE); + } + return List.of(); + } + + public static List listServiceIds(final PsiElement psiElement) { + return getParentJob(psiElement) + .flatMap(job -> getChild(job, FIELD_SERVICES)) + .map(services -> com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChildren(services).stream() + .map(YAMLKeyValue::getKeyText) + .toList()) + .orElseGet(List::of); + } + + public static Optional getService(final PsiElement psiElement, final String serviceId) { + return getParentJob(psiElement) + .flatMap(job -> getChild(job, FIELD_SERVICES)) + .flatMap(services -> com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChildren(services).stream() + .filter(service -> serviceId.equals(service.getKeyText())) + .findFirst()); + } + + public static List listServicePorts(final PsiElement psiElement, final String serviceId) { + return getService(psiElement, serviceId) + .flatMap(service -> getChild(service, FIELD_PORTS)) + .map(ports -> getTextElements(ports).stream() + .map(PsiElement::getText) + .map(JobContext::toServicePort) + .flatMap(Optional::stream) + .distinct() + .toList()) + .orElseGet(List::of); + } + + private static void highlightContainer(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { + if (parts.length > 2 && isField2Valid(element, holder, parts[2], CONTAINER_FIELDS)) { + ifEnoughItems(holder, element, parts, 2, 3, ignored -> { + // Valid container object/member. + }); + } + } + + private static void highlightServices(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { + if (parts.length < 3) { + return; + } + if (!isDefinedItem0(element, holder, parts[2], listServiceIds(element))) { + return; + } + if (parts.length == 3) { + return; + } + if (!isField2Valid(element, holder, parts[3], SERVICE_FIELDS)) { + return; + } + if (!FIELD_PORTS.equals(parts[3].text())) { + ifEnoughItems(holder, element, parts, 2, 4, ignored -> { + // Valid service scalar member. + }); + return; + } + if (parts.length > 4) { + isValidItem3(element, holder, parts[4], listServicePorts(element, parts[2].text())); + } + ifEnoughItems(holder, element, parts, 2, 5, ignored -> { + // Valid service port access. + }); + } + + private static java.util.Map toMap(final List keys, final String description) { + return keys.stream().collect(java.util.stream.Collectors.toMap( + key -> key, + key -> description, + (existing, replacement) -> existing, + java.util.LinkedHashMap::new + )); + } + + private static Optional toServicePort(final String text) { + final Matcher matcher = PORT_PATTERN.matcher(removeQuotes(text)); + return matcher.find() ? Optional.of(matcher.group(1)) : Optional.empty(); + } + + private JobContext() { + // static helper class + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/Jobs.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/Jobs.java index 8a3ccf2..d7455ae 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Jobs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Jobs.java @@ -17,6 +17,7 @@ import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_JOBS; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_ON; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_OUTPUTS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_RESULT; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_USES; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.ifEnoughItems; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isDefinedItem0; @@ -35,11 +36,16 @@ public class Jobs { public static void highLightJobs(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { - ifEnoughItems(holder, element, parts, 4, 4, jobId -> { + ifEnoughItems(holder, element, parts, 3, 4, jobId -> { final List jobs = listJobs(element); - if (isDefinedItem0(element, holder, jobId, jobs.stream().map(YAMLKeyValue::getKeyText).toList()) && isField2Valid(element, holder, parts[2])) { + if (isDefinedItem0(element, holder, jobId, jobs.stream().map(YAMLKeyValue::getKeyText).toList()) && isField2Valid(element, holder, parts[2], List.of(FIELD_OUTPUTS, FIELD_RESULT))) { + if (FIELD_RESULT.equals(parts[2].text())) { + return; + } final List outputs = listJobOutputs(jobs.stream().filter(job -> job.getKeyText().equals(jobId.text())).findFirst().orElse(null)).stream().map(SimpleElement::key).toList(); - isValidItem3(element, holder, parts[3], outputs); + if (parts.length > 3) { + isValidItem3(element, holder, parts[3], outputs); + } } }); } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/Matrix.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/Matrix.java new file mode 100644 index 0000000..0c6bf69 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Matrix.java @@ -0,0 +1,61 @@ +package com.github.yunabraska.githubworkflow.logic; + +import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; +import com.github.yunabraska.githubworkflow.model.SimpleElement; +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import org.jetbrains.yaml.psi.YAMLKeyValue; +import org.jetbrains.yaml.psi.YAMLSequenceItem; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_MATRIX; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_STRATEGY; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.ifEnoughItems; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isDefinedItem0; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChild; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentJob; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_NODE; +import static com.github.yunabraska.githubworkflow.model.SimpleElement.completionItemsOf; + +public class Matrix { + + public static void highlightMatrix(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { + ifEnoughItems(holder, element, parts, 2, -1, field -> isDefinedItem0(element, holder, field, listMatrix(element).stream().map(SimpleElement::key).toList())); + } + + public static List listMatrix(final PsiElement psiElement) { + return completionItemsOf(listMatrixRaw(psiElement), ICON_NODE); + } + + private static Map listMatrixRaw(final PsiElement psiElement) { + final Map result = new LinkedHashMap<>(); + getParentJob(psiElement) + .flatMap(job -> getChild(job, FIELD_STRATEGY)) + .flatMap(strategy -> getChild(strategy, FIELD_MATRIX)) + .ifPresent(matrix -> { + PsiElementHelper.getChildren(matrix).stream() + .filter(Matrix::isMatrixProperty) + .forEach(property -> result.putIfAbsent(property.getKeyText(), PsiElementHelper.getText(property).orElse(""))); + getChild(matrix, "include") + .map(include -> PsiElementHelper.getChildren(include, YAMLSequenceItem.class)) + .stream() + .flatMap(List::stream) + .flatMap(item -> PsiElementHelper.getChildren(item).stream()) + .forEach(property -> result.putIfAbsent(property.getKeyText(), PsiElementHelper.getText(property).orElse(""))); + }); + return result; + } + + private static boolean isMatrixProperty(final YAMLKeyValue keyValue) { + final String key = keyValue.getKeyText(); + return !"include".equals(key) && !"exclude".equals(key); + } + + private Matrix() { + // static helper class + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java index e0fbf3f..6012c35 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java @@ -1,46 +1,40 @@ package com.github.yunabraska.githubworkflow.logic; import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; -import com.github.yunabraska.githubworkflow.model.GitHubAction; import com.github.yunabraska.githubworkflow.model.LocalReferenceResolver; import com.github.yunabraska.githubworkflow.model.SimpleElement; import com.github.yunabraska.githubworkflow.model.SyntaxAnnotation; +import com.github.yunabraska.githubworkflow.services.GitHubWorkflowBundle; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; -import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.psi.impl.source.tree.LeafPsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.yaml.psi.YAMLKeyValue; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_NEEDS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_RESULT; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.addAnnotation; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.deleteElementAction; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.ifEnoughItems; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isDefinedItem0; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isField2Valid; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isValidItem3; -import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.newJumpToFile; -import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getAllJobs; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChild; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParent; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentJob; -import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getTextElement; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getTextElements; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.goToDeclarationString; -import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.isTextElement; -import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.removeQuotes; import static com.github.yunabraska.githubworkflow.logic.Jobs.listAllJobs; import static com.github.yunabraska.githubworkflow.logic.Jobs.listJobOutputs; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.SUPPRESS_ON; import static java.util.Optional.ofNullable; public class Needs { @@ -49,13 +43,16 @@ public class Needs { // variable field public static void highlightNeeds(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { - ifEnoughItems(holder, element, parts, 4, 4, jobId -> { + ifEnoughItems(holder, element, parts, 3, 4, jobId -> { final List jobIds = listJobNeeds(element); - if (isDefinedItem0(element, holder, jobId, jobIds) && isField2Valid(element, holder, parts[2])) { - // TODO: find target for highlighting reference - // TODO: implement reference ... does highlighting comes after reference which could add an reference indicator? + if (isDefinedItem0(element, holder, jobId, jobIds) && isField2Valid(element, holder, parts[2], List.of("outputs", FIELD_RESULT))) { + if (FIELD_RESULT.equals(parts[2].text())) { + return; + } final List outputs = listJobOutputs(listAllJobs(element).stream().filter(job -> job.getKeyText().equals(jobId.text())).findFirst().orElse(null)).stream().map(SimpleElement::key).toList(); - isValidItem3(element, holder, parts[3], outputs); + if (parts.length > 3) { + isValidItem3(element, holder, parts[3], outputs); + } } }); } @@ -70,8 +67,8 @@ public static void highlightNeeds(final AnnotationHolder holder, final PsiElemen if (!jobsNames.contains(element.getText())) { // INVALID JOB_ID addAnnotation(holder, psiElement, new SyntaxAnnotation( - "Remove invalid jobId [" + element.getText() + "] - this jobId doesn't match any previous job", - null, + GitHubWorkflowBundle.message("inspection.needs.invalid.job", element.getText()), + SUPPRESS_ON, deleteElementAction(psiElement.getTextRange()) )); } else { @@ -86,6 +83,10 @@ public static void highlightNeeds(final AnnotationHolder holder, final PsiElemen } // ########## CODE COMPLETION ########## + public static List codeCompletionPreviousJobs(final PsiElement psiElement) { + return listJobs(psiElement).stream().map(Jobs::jobToCompletionItem).toList(); + } + public static List codeCompletionNeeds(final PsiElement psiElement) { final List jobs = listJobs(psiElement); return listJobNeeds(psiElement).stream() @@ -108,27 +109,18 @@ public static Optional referenceNeeds(final PsiElement psiElemen .map(job -> new PsiReference[]{new LocalReferenceResolver(psiElement, job)}); } - private static void highlightLocalActions(final AnnotationHolder holder, final YAMLKeyValue element, final GitHubAction action, final List result) { - if (action.isResolved() && action.isLocal()) { - final String tooltip = String.format("Open declaration (%s)", Arrays.stream(KeymapUtil.getActiveKeymapShortcuts("GotoDeclaration").getShortcuts()) - .limit(2) - .map(KeymapUtil::getShortcutText) - .collect(Collectors.joining(", ")) - ); - getTextElement(element).ifPresent(textElement -> { - holder.newAnnotation(HighlightSeverity.INFORMATION, tooltip) - .range(textElement) - .textAttributes(DefaultLanguageHighlighterColors.HIGHLIGHTED_REFERENCE) - .tooltip(tooltip) - .create(); - result.add(newJumpToFile(action)); - }); - } - } - // ########## COMMONS ########## private static List listJobs(final PsiElement psiElement) { - return getParentJob(psiElement).map(job -> listAllJobs(psiElement).stream().takeWhile(j -> !j.getKeyText().equals(job.getKeyText())).toList()).orElseGet(Collections::emptyList); + return currentJob(psiElement).map(job -> listAllJobs(psiElement).stream().takeWhile(j -> !j.getKeyText().equals(job.getKeyText())).toList()).orElseGet(Collections::emptyList); + } + + private static Optional currentJob(final PsiElement psiElement) { + final int offset = ofNullable(psiElement).map(PsiElement::getTextRange).map(range -> range.getStartOffset()).orElse(-1); + return getParentJob(psiElement).or(() -> ofNullable(psiElement) + .map(element -> listAllJobs(element).stream() + .filter(job -> job.getTextRange().contains(element.getTextRange().getStartOffset()) || job.getTextRange().getStartOffset() <= offset) + .reduce((previous, current) -> previous.getTextRange().contains(offset) ? previous : current) + .orElse(null))); } public static List listJobNeeds(final PsiElement psiElement) { diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java index 6bc9a0e..82022c2 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java @@ -2,6 +2,7 @@ import com.github.yunabraska.githubworkflow.model.SimpleElement; import com.github.yunabraska.githubworkflow.model.SyntaxAnnotation; +import com.github.yunabraska.githubworkflow.services.GitHubWorkflowBundle; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.HighlightSeverity; @@ -10,9 +11,10 @@ import com.intellij.psi.impl.source.tree.LeafPsiElement; import org.jetbrains.yaml.psi.YAMLKeyValue; -import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_IF; @@ -28,11 +30,15 @@ import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParent; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getText; import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_SECRET_WORKFLOW; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.RELOAD; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.SUPPRESS_ON; import static com.github.yunabraska.githubworkflow.model.SimpleElement.completionItemsOf; import static com.github.yunabraska.githubworkflow.model.SyntaxAnnotation.createAnnotation; public class Secrets { + private static final String GITHUB_TOKEN = "GITHUB_TOKEN"; + public static void highLightSecrets( final AnnotationHolder holder, final PsiElement psiElement, @@ -46,8 +52,8 @@ public static void highLightSecrets( final TextRange range = psiElement.getTextRange(); final TextRange textRange = new TextRange(range.getStartOffset() + parts[0].startIndexOffset(), range.getStartOffset() + parts[parts.length - 1].endIndexOffset()); new SyntaxAnnotation( - "Remove [" + simpleElement.text() + "] - Secrets are not valid in `if` statements", - null, + GitHubWorkflowBundle.message("inspection.secret.invalid.if", simpleElement.text()), + SUPPRESS_ON, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); } @@ -55,8 +61,8 @@ public static void highLightSecrets( if (!secrets.contains(secretId.text())) { final TextRange textRange = simpleTextRange(element, secretId); createAnnotation(element, textRange, holder, secrets.stream().map(secret -> new SyntaxAnnotation( - "Replace [" + secretId.text() + "] with [" + secret + "] - if it is not provided at runtime", - null, + GitHubWorkflowBundle.message("inspection.secret.replace.runtime", secretId.text(), secret), + RELOAD, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, replaceAction(textRange, secret), @@ -68,11 +74,20 @@ public static void highLightSecrets( public static List listSecrets(final PsiElement psiElement) { //WORKFLOW SECRETS - return getParent(psiElement, FIELD_IF).isPresent() ? Collections.emptyList() : getChild(psiElement.getContainingFile(), FIELD_ON) + if (getParent(psiElement, FIELD_IF).isPresent()) { + return Collections.emptyList(); + } + final Map result = getChild(psiElement.getContainingFile(), FIELD_ON) .map(on -> getAllElements(on, FIELD_SECRETS)) - .map(secrets -> secrets.stream().flatMap(secret -> getChildren(secret).stream()).collect(Collectors.toMap(YAMLKeyValue::getKeyText, keyValue -> getText(keyValue, "description").orElse(""), (existing, replacement) -> existing))) - .map(map -> completionItemsOf(map, ICON_SECRET_WORKFLOW)) - .orElseGet(ArrayList::new); + .map(secrets -> secrets.stream().flatMap(secret -> getChildren(secret).stream()).collect(Collectors.toMap( + YAMLKeyValue::getKeyText, + keyValue -> getText(keyValue, "description").orElse(""), + (existing, replacement) -> existing, + LinkedHashMap::new + ))) + .orElseGet(LinkedHashMap::new); + result.putIfAbsent(GITHUB_TOKEN, GitHubWorkflowBundle.message("completion.secret.githubToken")); + return completionItemsOf(result, ICON_SECRET_WORKFLOW); } private Secrets() { diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java index d5d5d0e..4fe4183 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java @@ -91,19 +91,26 @@ public static List listSteps(final PsiElement psiElement) { final YAMLSequenceItem currentStep = getParentStep(psiElement).orElse(null); final boolean isOutput = getParent(psiElement, FIELD_OUTPUTS).isPresent(); return getChildSteps(job).stream().takeWhile(step -> isOutput || step != currentStep).toList(); - }).orElseGet(() -> getParent(psiElement, FIELD_OUTPUTS) + }).orElseGet(() -> getParent(psiElement, FIELD_RUNS) + // Composite action [runs.steps] + .flatMap(runs -> getChild(runs, FIELD_STEPS)) + .map(steps -> { + final YAMLSequenceItem currentStep = getParentStep(psiElement).orElse(null); + final boolean isOutput = getParent(psiElement, FIELD_OUTPUTS).isPresent(); + return getChildSteps(steps).stream().takeWhile(step -> isOutput || step != currentStep).toList(); + }) + .orElseGet(() -> getParent(psiElement, FIELD_OUTPUTS) //Action.yaml [runs.steps] .map(outputs -> psiElement.getContainingFile()) .flatMap(psiFile -> getChild(psiFile, FIELD_RUNS)) .flatMap(runs -> getChild(runs, FIELD_STEPS)) .map(PsiElementHelper::getChildSteps) - .orElseGet(Collections::emptyList) + .orElseGet(Collections::emptyList)) ); } - //TODO: split uses and run outputs public static List listStepOutputs(final YAMLSequenceItem step) { - //STEP RUN & ACTION OUTPUTS + // Run file-command outputs and action metadata outputs are both valid step outputs. return Stream.concat(listRunOutputs(step).stream(), listActionsOutputs(step).stream()).toList(); } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/logic/Strategy.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/Strategy.java new file mode 100644 index 0000000..0eec442 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Strategy.java @@ -0,0 +1,30 @@ +package com.github.yunabraska.githubworkflow.logic; + +import com.github.yunabraska.githubworkflow.model.SimpleElement; +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.psi.impl.source.tree.LeafPsiElement; + +import java.util.ArrayList; +import java.util.List; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.DEFAULT_VALUE_MAP; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_STRATEGY; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.ifEnoughItems; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.isDefinedItem0; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_NODE; +import static com.github.yunabraska.githubworkflow.model.SimpleElement.completionItemsOf; + +public class Strategy { + + public static void highlightStrategy(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { + ifEnoughItems(holder, element, parts, 2, 2, field -> isDefinedItem0(element, holder, field, new ArrayList<>(DEFAULT_VALUE_MAP.get(FIELD_STRATEGY).get().keySet()))); + } + + public static List codeCompletionStrategy() { + return completionItemsOf(DEFAULT_VALUE_MAP.get(FIELD_STRATEGY).get(), ICON_NODE); + } + + private Strategy() { + // static helper class + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/model/CustomClickAction.java b/src/main/java/com/github/yunabraska/githubworkflow/model/CustomClickAction.java index d4be1ac..bc72f83 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/CustomClickAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/CustomClickAction.java @@ -13,6 +13,7 @@ public class CustomClickAction extends AnAction { private final PsiElement psiElement; public CustomClickAction(final SyntaxAnnotation quickFix, final PsiElement psiElement) { + super(quickFix.getText()); this.quickFix = quickFix; this.psiElement = psiElement; } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java b/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java index 54a5feb..b1d8a26 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java @@ -1,12 +1,13 @@ package com.github.yunabraska.githubworkflow.model; -import com.esotericsoftware.kryo.kryo5.minlog.Log; import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; -import com.intellij.openapi.application.ApplicationManager; +import com.github.yunabraska.githubworkflow.services.RemoteActionProviders; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectUtil; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -20,31 +21,29 @@ import java.io.Serial; import java.io.Serializable; import java.nio.file.Files; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.github.yunabraska.githubworkflow.helper.FileDownloader.downloadContent; -import static com.github.yunabraska.githubworkflow.helper.FileDownloader.downloadFileFromGitHub; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.CACHE_ONE_DAY; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_INPUTS; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_ON; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_OUTPUTS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_SECRETS; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChild; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.hasText; import static java.lang.Boolean.parseBoolean; import static java.util.Collections.unmodifiableMap; -import static java.util.Collections.unmodifiableSet; import static java.util.Optional.of; import static java.util.Optional.ofNullable; @@ -60,6 +59,7 @@ public class GitHubAction implements Serializable { private final Map metaData = new ConcurrentHashMap<>(); private final Map inputs = new ConcurrentHashMap<>(); private final Map outputs = new ConcurrentHashMap<>(); + private final Map secrets = new ConcurrentHashMap<>(); // NON SERIALIZABLE private final Set ignoredInputs = ConcurrentHashMap.newKeySet(); @@ -90,7 +90,7 @@ public static GitHubAction createGithubAction(final boolean isLocal, final Strin String slug = null; String tmpSub = null; - final boolean isAction = isLocal || (!absolutePath.contains(".yaml") && !absolutePath.contains(".yml") && !absolutePath.contains(".action.y")); + final boolean isAction = isLocal ? !isWorkflowFile(usesValue) : (!absolutePath.contains(".yaml") && !absolutePath.contains(".yml") && !absolutePath.contains(".action.y")); // START [EXTRACT PARTS] if (tagIndex != -1 && userNameIndex < tagIndex) { @@ -108,7 +108,7 @@ public static GitHubAction createGithubAction(final boolean isLocal, final Strin // END [EXTRACT PARTS] return new GitHubAction() - .name(slug != null ? slug : tmpName) + .name(slug != null ? slug + ofNullable(tmpSub).orElse("") : tmpName) .usesValue(usesValue) .downloadUrl(isLocal ? absolutePath : toRemoteDownloadUrl(isAction, ref, slug, tmpSub, tmpName)) .githubUrl(isAction ? toGitHubActionUrl(ref, slug, tmpSub) : toGitHubWorkflowUrl(ref, slug, tmpName)) @@ -120,16 +120,21 @@ public static GitHubAction createGithubAction(final boolean isLocal, final Strin } public Optional getLocalPath(final Project project) { + return getLocalVirtualFile(project) + .map(VirtualFile::getPath) + .or(() -> isLocal() ? ofNullable(downloadUrl()).filter(PsiElementHelper::hasText) : Optional.empty()); + } + + public Optional getLocalVirtualFile(final Project project) { return isLocal() ? ofNullable(project) .map(ProjectUtil::guessProjectDir) .map(dir -> findActionYaml(usesValue(), dir)) - .map(VirtualFile::getPath) : Optional.empty(); + .or(() -> ofNullable(downloadUrl()).map(path -> LocalFileSystem.getInstance().refreshAndFindFileByPath(path))) : Optional.empty(); } // !!! Performs Network and File Operations !!! - //TODO: get Tags for autocompletion public synchronized GitHubAction resolve() { - if (!isResolved() && !isSuppressed()) { + if ((!isResolved() || System.currentTimeMillis() >= expiryTime()) && !isSuppressed()) { extractParameters(); } return this; @@ -153,6 +158,13 @@ public Map freshOutputs(final boolean withIgnoredItems) { return withIgnoredItems ? concatMap(outputs, ignoredOutputs.stream().filter(PsiElementHelper::hasText).collect(Collectors.toMap(key -> key, value -> "*** manual added output ***"))) : unmodifiableMap(outputs); } + public Map freshSecrets() { + if (isLocal()) { + extractLocalParameters(); + } + return unmodifiableMap(secrets); + } + public String name() { return metaData.getOrDefault("name", ""); } @@ -162,6 +174,24 @@ public GitHubAction name(final String name) { return this; } + public String displayName() { + return metaData.getOrDefault("displayName", name()); + } + + public GitHubAction displayName(final String displayName) { + ofNullable(displayName).filter(PsiElementHelper::hasText).ifPresent(s -> metaData.put("displayName", s)); + return this; + } + + public String description() { + return metaData.getOrDefault("description", ""); + } + + public GitHubAction description(final String description) { + ofNullable(description).filter(PsiElementHelper::hasText).ifPresent(s -> metaData.put("description", s)); + return this; + } + public String downloadUrl() { return metaData.getOrDefault("downloadUrl", ""); } @@ -255,20 +285,48 @@ public GitHubAction suppressOutput(final String id, final boolean supress) { } public Set ignoredInputs() { - return unmodifiableSet(ignoredInputs); + return ignoredInputs.stream() + .filter(PsiElementHelper::hasText) + .collect(Collectors.toUnmodifiableSet()); } public Set ignoredOutputs() { - return unmodifiableSet(ignoredOutputs); + return ignoredOutputs.stream() + .filter(PsiElementHelper::hasText) + .collect(Collectors.toUnmodifiableSet()); + } + + /** + * Checks whether this action has any suppressed validation warnings. + * + * @return {@code true} when the whole action, at least one input, or at least one output is suppressed + */ + public boolean hasSuppressedWarnings() { + return isSuppressed() + || ignoredInputs.stream().anyMatch(PsiElementHelper::hasText) + || ignoredOutputs.stream().anyMatch(PsiElementHelper::hasText); + } + + /** + * Restores all validation warnings for this action metadata entry. + * + * @return this action instance after suppressions were removed + */ + public GitHubAction restoreWarnings() { + isSuppressed(false); + ignoredInputs.clear(); + ignoredOutputs.clear(); + metaData.put("ignoredInputs", ""); + metaData.put("ignoredOutputs", ""); + return this; } - public Map getInputs() { return unmodifiableMap(inputs); } public GitHubAction setInputs(final Map inputs) { - this.inputs.putAll(inputs); + ofNullable(inputs).ifPresent(this.inputs::putAll); return this; } @@ -277,7 +335,16 @@ public Map getOutputs() { } public GitHubAction setOutputs(final Map outputs) { - this.outputs.putAll(outputs); + ofNullable(outputs).ifPresent(this.outputs::putAll); + return this; + } + + public Map getSecrets() { + return unmodifiableMap(secrets); + } + + public GitHubAction setSecrets(final Map secrets) { + ofNullable(secrets).ifPresent(this.secrets::putAll); return this; } @@ -286,20 +353,43 @@ public Map getMetaData() { } public GitHubAction setMetaData(final Map metaData) { - this.metaData.putAll(metaData); - this.ignoredInputs.addAll(Arrays.stream(metaData.getOrDefault("ignoredInputs", "").split(";")).toList()); - this.ignoredOutputs.addAll(Arrays.stream(metaData.getOrDefault("ignoredOutputs", "").split(";")).toList()); + ofNullable(metaData).ifPresent(values -> { + this.metaData.putAll(values); + this.ignoredInputs.addAll(Arrays.stream(values.getOrDefault("ignoredInputs", "").split(";")).filter(PsiElementHelper::hasText).toList()); + this.ignoredOutputs.addAll(Arrays.stream(values.getOrDefault("ignoredOutputs", "").split(";")).filter(PsiElementHelper::hasText).toList()); + }); return this; } public static VirtualFile findActionYaml(final String subPath, final VirtualFile projectDir) { - return ofNullable(projectDir.findFileByRelativePath(subPath)).filter(p -> !p.isDirectory()) - .or(() -> ofNullable(projectDir.findFileByRelativePath(subPath + "/action.yml")).filter(VirtualFile::isValid).filter(p -> !p.isDirectory())) - .or(() -> ofNullable(projectDir.findFileByRelativePath(subPath + "/action.yaml")).filter(VirtualFile::isValid).filter(p -> !p.isDirectory())) + final String localPath = normalizeLocalPath(subPath); + return ofNullable(projectDir.findFileByRelativePath(localPath)).filter(p -> !p.isDirectory()) + .or(() -> ofNullable(projectDir.findFileByRelativePath(actionYamlPath(localPath, "action.yml"))).filter(VirtualFile::isValid).filter(p -> !p.isDirectory())) + .or(() -> ofNullable(projectDir.findFileByRelativePath(actionYamlPath(localPath, "action.yaml"))).filter(VirtualFile::isValid).filter(p -> !p.isDirectory())) .orElse(null); } + private static String normalizeLocalPath(final String subPath) { + if (subPath == null || subPath.isBlank() || ".".equals(subPath) || "./".equals(subPath)) { + return ""; + } + return subPath.startsWith("./") ? subPath.substring(2) : subPath; + } + + private static String actionYamlPath(final String localPath, final String fileName) { + return localPath.isBlank() ? fileName : localPath + "/" + fileName; + } + + private static boolean isWorkflowFile(final String usesValue) { + return ofNullable(usesValue) + .map(value -> value.replace('\\', '/')) + .filter(value -> value.contains(".github/workflows/") || value.contains(".gitea/workflows/")) + .filter(value -> value.endsWith(".yml") || value.endsWith(".yaml")) + .isPresent(); + } + private void extractParameters() { + final boolean wasResolved = isResolved(); try { if (isLocal()) { extractLocalParameters(); @@ -308,17 +398,31 @@ private void extractParameters() { } } catch (final Exception e) { LOG.warn("Failed to set parameters [" + this.name() + "]", e); - isResolved(false); + if (wasResolved) { + expiryTime(System.currentTimeMillis() + CACHE_ONE_DAY); + } else { + isResolved(false); + } } } private void extractRemoteParameters() { - try { - CompletableFuture.runAsync(() -> ofNullable(downloadFileFromGitHub(downloadUrl())).or(() -> ofNullable(downloadContent(downloadUrl()))).ifPresent(this::setParameters)).orTimeout(5000, TimeUnit.MILLISECONDS).join(); - ofNullable(downloadFileFromGitHub(downloadUrl())).or(() -> ofNullable(downloadContent(downloadUrl()))).ifPresent(this::setParameters); - } catch (final Exception exception) { - Log.error("Download failed", exception); - } + final boolean wasResolved = isResolved(); + RemoteActionProviders.resolve(usesValue()) + .ifPresentOrElse(resolution -> { + name(resolution.name()); + downloadUrl(resolution.downloadUrl()); + githubUrl(resolution.githubUrl()); + setAction(resolution.action()); + remoteRefs(resolution.refs()); + setParameters(resolution.content()); + }, () -> { + if (wasResolved) { + expiryTime(System.currentTimeMillis() + CACHE_ONE_DAY); + } else { + isResolved(false); + } + }); } private void extractLocalParameters() { @@ -328,16 +432,38 @@ private void extractLocalParameters() { } catch (final IOException ignored) { return null; } - }).ifPresent(this::setParameters); + }).or(this::readLocalVirtualFileContent).ifPresent(this::setParameters); + } + + private Optional readLocalVirtualFileContent() { + return Arrays.stream(ProjectManager.getInstance().getOpenProjects()) + .map(this::getLocalVirtualFile) + .flatMap(Optional::stream) + .filter(VirtualFile::isValid) + .filter(virtualFile -> !virtualFile.isDirectory()) + .findFirst() + .flatMap(GitHubAction::readVirtualFileContent); + } + + private static Optional readVirtualFileContent(final VirtualFile virtualFile) { + try { + return Optional.of(new String(virtualFile.contentsToByteArray(), StandardCharsets.UTF_8)); + } catch (final IOException ignored) { + return Optional.empty(); + } } private void setParameters(final String content) { isResolved(hasText(content)); readPsiElement(ProjectManager.getInstance().getDefaultProject(), downloadUrl(), content, psiFile -> { + displayName(PsiElementHelper.getText(psiFile.getContainingFile(), "name").orElse(name())); + description(PsiElementHelper.getText(psiFile.getContainingFile(), "description").orElse("")); inputs.clear(); inputs.putAll(getActionParameters(psiFile, FIELD_INPUTS, isAction())); outputs.clear(); outputs.putAll(getActionParameters(psiFile, FIELD_OUTPUTS, isAction())); + secrets.clear(); + secrets.putAll(getActionParameters(psiFile, FIELD_SECRETS, isAction())); }); } @@ -368,6 +494,20 @@ private static String toGitHubActionUrl(final String ref, final String slug, fin return (ref != null && slug != null && sub != null) ? "https://github.com/" + slug + "/tree/" + ref + sub + "#readme" : null; } + public List remoteRefs() { + return Arrays.stream(metaData.getOrDefault("remoteRefs", "").split(";")) + .filter(PsiElementHelper::hasText) + .toList(); + } + + public GitHubAction remoteRefs(final List refs) { + metaData.put("remoteRefs", ofNullable(refs).orElseGet(List::of).stream() + .filter(PsiElementHelper::hasText) + .distinct() + .collect(Collectors.joining(";"))); + return this; + } + @NotNull private static Map getActionParameters(final PsiElement psiElement, final String fieldName, final boolean action) { if (action) { @@ -388,20 +528,22 @@ private static Map readActionParameters(final PsiElement psiElem @NotNull private static Map readWorkflowParameters(final PsiElement psiElement, final String fieldName) { return getChild(psiElement.getContainingFile(), FIELD_ON) - .flatMap(keyValue -> getChild(psiElement.getContainingFile(), fieldName)) + .flatMap(on -> getChild(on, "workflow_call")) + .flatMap(workflowCall -> getChild(workflowCall, fieldName)) .map(PsiElementHelper::getChildren) .map(children -> children.stream().collect(Collectors.toMap(YAMLKeyValue::getKeyText, field -> PsiElementHelper.getDescription(field, FIELD_INPUTS.equals(fieldName))))) .orElseGet(Collections::emptyMap); } private static void readPsiElement(final Project project, final String fileName, final String fileContent, final Consumer action) { - ApplicationManager.getApplication().runReadAction(() -> { + ReadAction.nonBlocking(() -> { try { ofNullable(action).ifPresent(consumer -> consumer.accept(PsiFileFactory.getInstance(project).createFileFromText(fileName, YAMLFileType.YML, fileContent.replaceAll("\r?\\n|\\r", "\n")))); } catch (final Exception ignored) { // ignored } - }); + return null; + }).executeSynchronously(); } private static Map concatMap(final Map map1, final Map map2) { @@ -427,6 +569,7 @@ public String toString() { .add("metaData=" + metaData) .add("inputs=" + (inputs.size() + ignoredInputs.size())) .add("outputs=" + (outputs.size() + ignoredOutputs.size())) + .add("secrets=" + secrets.size()) .toString(); } } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/model/IconRenderer.java b/src/main/java/com/github/yunabraska/githubworkflow/model/IconRenderer.java index 3444154..4832069 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/IconRenderer.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/IconRenderer.java @@ -1,29 +1,51 @@ package com.github.yunabraska.githubworkflow.model; +import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.editor.markup.GutterIconRenderer; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.List; +import java.util.Objects; public class IconRenderer extends GutterIconRenderer { private final NodeIcon icon; - private final SyntaxAnnotation quickFix; + private final List quickFixes; private final PsiElement psiElement; public IconRenderer(final SyntaxAnnotation quickFix, final PsiElement psiElement, final NodeIcon icon) { + this(quickFix, psiElement, icon, quickFix == null ? List.of() : List.of(quickFix)); + } + + public IconRenderer(final SyntaxAnnotation quickFix, final PsiElement psiElement, final NodeIcon icon, final List quickFixes) { this.icon = icon; - this.quickFix = quickFix; + this.quickFixes = quickFixes == null ? List.of() : quickFixes.stream() + .filter(Objects::nonNull) + .filter(SyntaxAnnotation::hasExecution) + .distinct() + .toList(); this.psiElement = psiElement; } @Nullable @Override public AnAction getClickAction() { - return icon != null && quickFix != null ? new CustomClickAction(quickFix, psiElement) : null; + return quickFixes.size() == 1 ? new CustomClickAction(quickFixes.get(0), psiElement) : null; + } + + @Override + public @Nullable ActionGroup getPopupMenuActions() { + if (quickFixes.size() <= 1) { + return null; + } + final DefaultActionGroup group = new DefaultActionGroup(); + quickFixes.forEach(fix -> group.add(new CustomClickAction(fix, psiElement))); + return group; } @NotNull @@ -34,13 +56,15 @@ public Icon getIcon() { @Override public boolean isNavigateAction() { - return quickFix != null; + return quickFixes.size() == 1; } @Override @Nullable public String getTooltipText() { - return quickFix == null ? null : quickFix.getText(); + return quickFixes.isEmpty() + ? null + : quickFixes.stream().map(SyntaxAnnotation::getText).distinct().reduce((left, right) -> left + "\n" + right).orElse(null); } @Override diff --git a/src/main/java/com/github/yunabraska/githubworkflow/model/LocalActionReferenceResolver.java b/src/main/java/com/github/yunabraska/githubworkflow/model/LocalActionReferenceResolver.java index d86145d..da76f68 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/LocalActionReferenceResolver.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/LocalActionReferenceResolver.java @@ -1,7 +1,7 @@ package com.github.yunabraska.githubworkflow.model; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementResolveResult; import com.intellij.psi.PsiManager; @@ -23,12 +23,13 @@ public LocalActionReferenceResolver(@NotNull final PsiElement element) { @Override public @NotNull ResolveResult @NotNull [] multiResolve(final boolean incompleteCode) { - return ofNullable(myElement.getUserData(ACTION_KEY)).flatMap(action -> { + return ofNullable(myElement.getUserData(ACTION_KEY)).flatMap(cachedAction -> { final Project project = getProject(myElement); return ofNullable(project) - .flatMap(action::getLocalPath) - .map(path -> LocalFileSystem.getInstance().findFileByPath(path)) + .flatMap(cachedAction::getLocalVirtualFile) + .filter(VirtualFile::isValid) .map(virtualFile -> PsiManager.getInstance(project).findFile(virtualFile)) + .filter(target -> target != null && target.isValid()) .map(target -> target.getChildren().length > 0 ? target.getChildren()[0] : target) .map(target -> new ResolveResult[]{(new PsiElementResolveResult(target))}); }).orElse(ResolveResult.EMPTY_ARRAY); diff --git a/src/main/java/com/github/yunabraska/githubworkflow/model/NodeIcon.java b/src/main/java/com/github/yunabraska/githubworkflow/model/NodeIcon.java index 1a396a8..25c152e 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/NodeIcon.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/NodeIcon.java @@ -26,6 +26,7 @@ public enum NodeIcon { SETTINGS(AllIcons.General.Settings), HYPERLINK(AllIcons.Ide.External_link_arrow), JUMP_TO_IMPLEMENTATION(AllIcons.Gutter.ImplementedMethod), + SUPPRESS_ON(AllIcons.Actions.Cancel), SUPPRESS_OFF(AllIcons.Actions.OfflineMode), IGNORED(AllIcons.Actions.Regex), EMPTY(AllIcons.Nodes.EmptyNode), diff --git a/src/main/java/com/github/yunabraska/githubworkflow/model/SyntaxAnnotation.java b/src/main/java/com/github/yunabraska/githubworkflow/model/SyntaxAnnotation.java index 22038ae..dccbb52 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/SyntaxAnnotation.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/SyntaxAnnotation.java @@ -11,6 +11,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.util.IncorrectOperationException; +import com.intellij.openapi.util.Iconable; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -22,7 +23,7 @@ import static java.util.Optional.ofNullable; -public class SyntaxAnnotation implements IntentionAction { +public class SyntaxAnnotation implements IntentionAction, Iconable { private final String text; private final NodeIcon icon; @@ -99,7 +100,6 @@ public static void createAnnotation( ofNullable(range != null ? range : psiElement.getTextRange()).ifPresent(annotation::range); ofNullable(firstItem.type).ifPresent(annotation::highlightType); ofNullable(firstItem.text).filter(text -> firstItem.showToolTip).ifPresent(annotation::tooltip); - ofNullable(firstItem.icon).map(i -> new IconRenderer(firstItem, psiElement, firstItem.icon)).ifPresent(annotation::gutterIconRenderer); group.forEach(fix -> ofNullable(fix.execute).ifPresent(exec -> annotation.withFix(fix))); annotation.create(); }); @@ -111,6 +111,11 @@ public Icon icon() { return icon.icon(); } + @Override + public Icon getIcon(final int flags) { + return icon == null ? null : icon.icon(); + } + @NotNull @Override public String getText() { @@ -133,6 +138,10 @@ public boolean startInWriteAction() { return false; } + boolean hasExecution() { + return execute != null; + } + @Override public boolean equals(final Object o) { if (this == o) return true; diff --git a/src/main/java/com/github/yunabraska/githubworkflow/model/VariableReferenceResolver.java b/src/main/java/com/github/yunabraska/githubworkflow/model/VariableReferenceResolver.java index 9898646..1488b46 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/VariableReferenceResolver.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/VariableReferenceResolver.java @@ -1,62 +1,35 @@ package com.github.yunabraska.githubworkflow.model; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementResolveResult; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReferenceBase; import com.intellij.psi.ResolveResult; -import com.jetbrains.rd.util.AtomicReference; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_ID; -import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_INPUTS; -import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_JOBS; -import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_NEEDS; -import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_STEPS; -import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChild; -import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getTextElements; -import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.removeQuotes; -import static com.github.yunabraska.githubworkflow.logic.Inputs.listInputsRaw; -import static com.github.yunabraska.githubworkflow.logic.Jobs.listJobs; -import static com.github.yunabraska.githubworkflow.logic.Needs.getJobNeed; -import static com.github.yunabraska.githubworkflow.logic.Steps.listSteps; -import static com.github.yunabraska.githubworkflow.services.HighlightAnnotator.VARIABLE_ELEMENTS; -import static com.github.yunabraska.githubworkflow.services.HighlightAnnotator.splitToElements; import static java.util.Optional.ofNullable; public class VariableReferenceResolver extends PsiReferenceBase implements PsiPolyVariantReference { - public VariableReferenceResolver(@NotNull final PsiElement element) { - super(element); + private final PsiElement targetElement; + + public VariableReferenceResolver( + @NotNull final PsiElement element, + @NotNull final TextRange rangeInElement, + @NotNull final PsiElement targetElement + ) { + super(element, rangeInElement); + this.targetElement = targetElement; } @Override public @NotNull ResolveResult @NotNull [] multiResolve(final boolean incompleteCode) { - return ofNullable(myElement.getUserData(VARIABLE_ELEMENTS)).map(simpleElements -> { - final List resolveResults = new ArrayList<>(); - for (final SimpleElement simpleElement : simpleElements) { - final SimpleElement[] parts = splitToElements(simpleElement); - final AtomicReference reference = new AtomicReference<>(null); - switch (parts.length > 1 ? parts[0].text() : "N/A") { - case FIELD_INPUTS -> - listInputsRaw(myElement).stream().filter(input -> input.getKeyText().equals(parts[1].text())).findFirst().ifPresent(reference::getAndSet); - case FIELD_JOBS -> - listJobs(myElement).stream().filter(input -> input.getKeyText().equals(parts[1].text())).findFirst().ifPresent(reference::getAndSet); - case FIELD_NEEDS -> - getJobNeed(myElement).stream().flatMap(need -> getTextElements(need).stream()).filter(need -> removeQuotes(need.getText()).equals(parts[1].text())).findFirst().ifPresent(reference::getAndSet); - case FIELD_STEPS -> - listSteps(myElement).stream().map(step -> getChild(step, FIELD_ID).orElse(null)).filter(Objects::nonNull).filter(input -> input.getKeyText().equals(parts[1].text())).findFirst().ifPresent(reference::getAndSet); - default -> reference.getAndSet(null); - } - ofNullable(reference.get()).map(PsiElementResolveResult::new).ifPresent(resolveResults::add); - } - return resolveResults.toArray(new ResolveResult[0]); - }).orElse(ResolveResult.EMPTY_ARRAY); + return ofNullable(targetElement) + .map(PsiElementResolveResult::new) + .map(result -> new ResolveResult[]{result}) + .orElse(ResolveResult.EMPTY_ARRAY); } @Nullable diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java b/src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java new file mode 100644 index 0000000..37ff048 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java @@ -0,0 +1,41 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.notification.NotificationGroupManager; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.ActionUpdateThread; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.DumbAwareAction; +import org.jetbrains.annotations.NotNull; + +public final class ClearActionCacheAction extends DumbAwareAction { + + @Override + public void actionPerformed(@NotNull final AnActionEvent event) { + final GitHubActionCache.CacheSummary before = GitHubActionCache.getActionCache().summary(); + GitHubActionCache.getActionCache().clear(); + notify(event, GitHubWorkflowBundle.message("notification.cache.cleared", before.total())); + } + + @Override + public void update(@NotNull final AnActionEvent event) { + localize(event.getPresentation()); + } + + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.BGT; + } + + private static void notify(final AnActionEvent event, final String content) { + NotificationGroupManager.getInstance() + .getNotificationGroup("GitHub Workflow") + .createNotification(content, NotificationType.INFORMATION) + .notify(event.getProject()); + } + + private static void localize(final Presentation presentation) { + presentation.setText(GitHubWorkflowBundle.message("action.GitHubWorkflow.ClearActionCache.text")); + presentation.setDescription(GitHubWorkflowBundle.message("action.GitHubWorkflow.ClearActionCache.description")); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java b/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java index cafbdb7..f22d868 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java @@ -12,37 +12,63 @@ import com.intellij.codeInsight.completion.CompletionType; import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.patterns.PlatformPatterns; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiLanguageInjectionHost; import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; +import org.jetbrains.yaml.psi.YAMLKeyValue; +import org.jetbrains.yaml.psi.YAMLSequenceItem; +import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.nio.file.Path; import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.*; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.getCaretBracketItem; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.getStartIndex; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.getWorkflowFile; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.isActionFile; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.isWorkflowFile; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.toLookupElement; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getAllElements; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChildSteps; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentStep; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getProject; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParent; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentStepOrJob; import static com.github.yunabraska.githubworkflow.logic.Envs.listEnvs; +import static com.github.yunabraska.githubworkflow.logic.GitHub.codeCompletionGitea; import static com.github.yunabraska.githubworkflow.logic.GitHub.codeCompletionGithub; import static com.github.yunabraska.githubworkflow.logic.Inputs.listInputs; +import static com.github.yunabraska.githubworkflow.logic.JobContext.codeCompletionJob; import static com.github.yunabraska.githubworkflow.logic.Jobs.codeCompletionJobs; import static com.github.yunabraska.githubworkflow.logic.Jobs.listJobs; +import static com.github.yunabraska.githubworkflow.logic.Matrix.listMatrix; import static com.github.yunabraska.githubworkflow.logic.Needs.codeCompletionNeeds; +import static com.github.yunabraska.githubworkflow.logic.Needs.codeCompletionPreviousJobs; import static com.github.yunabraska.githubworkflow.logic.Needs.listJobNeeds; import static com.github.yunabraska.githubworkflow.logic.Runner.codeCompletionRunner; import static com.github.yunabraska.githubworkflow.logic.Secrets.listSecrets; import static com.github.yunabraska.githubworkflow.logic.Steps.codeCompletionSteps; import static com.github.yunabraska.githubworkflow.logic.Steps.listSteps; +import static com.github.yunabraska.githubworkflow.logic.Strategy.codeCompletionStrategy; import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_JOB; import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_NODE; import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_OUTPUT; @@ -53,10 +79,20 @@ public class CodeCompletion extends CompletionContributor { + private static final Pattern REMOTE_USES_REF_PATTERN = Pattern.compile(".*\\buses\\s*:\\s*['\"]?([^\\s'\"#]+)@([^\\s'\"]*)$"); + private static final Pattern REMOTE_USES_TARGET_PATTERN = Pattern.compile(".*\\buses\\s*:\\s*['\"]?([^\\s'\"#@]*)$"); + public CodeCompletion() { extend(CompletionType.BASIC, PlatformPatterns.psiElement(), completionProvider()); } + static boolean workflowCompletionTrigger(final char typeChar) { + return switch (typeChar) { + case '\n', ':', '.', '@' -> true; + default -> false; + }; + } + @NotNull private static CompletionProvider completionProvider() { return new CompletionProvider<>() { @@ -66,32 +102,86 @@ public void addCompletions( @NotNull final ProcessingContext processingContext, @NotNull final CompletionResultSet resultSet ) { - final PsiElement position = parameters.getPosition(); + final CompletionPsi completionPsi = completionPsi(parameters); + final PsiElement position = completionPsi.position(); getWorkflowFile(position).ifPresent(file -> { - final int offset = parameters.getOffset(); + final int offset = completionPsi.offset(); final String[] prefix = new String[]{""}; final Optional caretBracketItem = offset < 1 ? Optional.of(prefix) : getCaretBracketItem(position, offset, prefix); + final Optional yamlValueCompletion = workflowValueCompletion(completionPsi); + if (yamlValueCompletion.isPresent()) { + final StructureCompletion completion = yamlValueCompletion.get(); + addLookupElements( + resultSet.withPrefixMatcher(getDefaultPrefix(completionPsi)), + completion.items(), + ICON_NODE, + completion.suffix() + ); + return; + } caretBracketItem.ifPresent(cbi -> addCodeCompletionItems(resultSet, cbi, position, prefix)); //ACTIONS && WORKFLOWS if (caretBracketItem.isEmpty()) { - if (getParent(position, FIELD_RUN).isPresent() && position.getText().contains("$IntellijIdeaRulezzz")) { - // AUTO COMPLETE [$GITHUB_ENV, $GITHUB_OUTPUT] - final Map defaults = ofNullable(DEFAULT_VALUE_MAP.get(FIELD_DEFAULT)).map(Supplier::get).orElseGet(Collections::emptyMap); - addLookupElements(resultSet.withPrefixMatcher(prefix[0]), Map.of("GITHUB_ENV", defaults.getOrDefault(FIELD_ENVS, ""), "GITHUB_OUTPUT", defaults.getOrDefault(FIELD_GITHUB, "")), NodeIcon.ICON_ENV, Character.MIN_VALUE); - } else if (getParent(position, FIELD_NEEDS).isPresent()) { + if (isCompletingRunEnvironmentVariable(completionPsi)) { + // AUTO COMPLETE DEFAULT RUNNER ENVIRONMENT VARIABLES + final Map defaults = ofNullable(DEFAULT_VALUE_MAP.get(FIELD_ENVS)).map(Supplier::get).orElseGet(Collections::emptyMap); + addLookupElements(resultSet.withPrefixMatcher(prefix[0]), defaults, NodeIcon.ICON_ENV, Character.MIN_VALUE); + } else if (isInsideExecutableRunField(position)) { + return; + } else if (isCompletingNeedsField(parameters, position)) { //[jobs.job_name.needs] list previous jobs - Optional.of(codeCompletionNeeds(position)).filter(cil -> !cil.isEmpty()) + Optional.of(codeCompletionPreviousJobs(position)).filter(cil -> !cil.isEmpty()) .map(CodeCompletion::toLookupItems) .ifPresent(lookupElements -> addElementsWithPrefix(resultSet, getDefaultPrefix(parameters), lookupElements)); + } else if (isCompletingUsesField(parameters, position)) { + final Optional remoteUsesRef = remoteUsesRef(parameters); + if (remoteUsesRef.isPresent()) { + final RemoteUsesRef ref = remoteUsesRef.get(); + addLookupElements( + resultSet.withPrefixMatcher(ref.prefix()), + knownRemoteRefs(position, ref.usesBase()), + NodeIcon.ICON_NODE, + Character.MIN_VALUE + ); + return; + } + addLookupElements( + resultSet.withPrefixMatcher(getDefaultPrefix(parameters)), + callableUsesCompletions(position, remoteUsesTargetPrefix(parameters).orElse("")), + NodeIcon.ICON_NODE, + Character.MIN_VALUE + ); + } else if (isCompletingShellField(parameters, position)) { + addLookupElements( + resultSet.withPrefixMatcher(getDefaultPrefix(parameters)), + SHELLS, + NodeIcon.ICON_NODE, + Character.MIN_VALUE + ); } else { - //USES COMPLETION [jobs.job_id.steps.step_id:with] - final Optional> withCompletion = getParentStepOrJob(position) - .flatMap(step -> PsiElementHelper.getChild(step, FIELD_USES)) - .map(GitHubActionCache::getAction) - .filter(GitHubAction::isResolved) - .map(GitHubAction::freshInputs); - withCompletion.ifPresent(map -> addLookupElements(resultSet.withPrefixMatcher(getDefaultPrefix(parameters)), map, NodeIcon.ICON_INPUT, ':')); + final Optional structureCompletion = workflowStructureCompletion(completionPsi); + if (structureCompletion.isPresent()) { + final StructureCompletion completion = structureCompletion.get(); + addLookupElements( + resultSet.withPrefixMatcher(getDefaultPrefix(completionPsi)), + completion.items(), + ICON_NODE, + completion.suffix() + ); + return; + } + if (isCompletingCallableSecrets(position)) { + currentCallableAction(parameters, position) + .map(GitHubAction::freshSecrets) + .ifPresent(map -> addLookupElements(resultSet.withPrefixMatcher(getDefaultPrefix(parameters)), map, NodeIcon.ICON_SECRET_WORKFLOW, ':')); + } else { + //USES COMPLETION [jobs.job_id.steps.step_id:with] + final Optional> withCompletion = currentCallableAction(parameters, position) + .filter(GitHubAction::isResolved) + .map(GitHubAction::freshInputs); + withCompletion.ifPresent(map -> addLookupElements(resultSet.withPrefixMatcher(getDefaultPrefix(parameters)), map, NodeIcon.ICON_INPUT, ':')); + } } } }); @@ -99,13 +189,598 @@ public void addCompletions( }; } + private static CompletionPsi completionPsi(final CompletionParameters parameters) { + final PsiElement position = parameters.getPosition(); + final InjectedLanguageManager injectionManager = InjectedLanguageManager.getInstance(position.getProject()); + final PsiLanguageInjectionHost host = injectionManager.getInjectionHost(position); + return host == null + ? new CompletionPsi(position, parameters.getOffset()) + : new CompletionPsi(host, injectionManager.injectedToHost(position, parameters.getOffset())); + } + + private static boolean isCompletingCallableSecrets(final PsiElement position) { + return getParent(position, FIELD_SECRETS) + .filter(secrets -> getParent(secrets, FIELD_ON).isEmpty()) + .isPresent(); + } + + private static boolean isCompletingUsesField(final CompletionParameters parameters, final PsiElement position) { + if (getParent(position, FIELD_USES).isPresent()) { + return true; + } + final String wholeText = parameters.getOriginalFile().getText(); + final String beforeCaret = lineBeforeCaret(wholeText, parameters.getOffset()); + return beforeCaret.matches("\\s*-?\\s*" + FIELD_USES + "\\s*:\\s*.*"); + } + + private static boolean isCompletingShellField(final CompletionParameters parameters, final PsiElement position) { + if (getParent(position, FIELD_SHELL).isPresent()) { + return true; + } + final String wholeText = parameters.getOriginalFile().getText(); + final String beforeCaret = lineBeforeCaret(wholeText, parameters.getOffset()); + return beforeCaret.matches("\\s*" + FIELD_SHELL + "\\s*:\\s*.*"); + } + + private static boolean isCompletingRunEnvironmentVariable(final CompletionPsi completionPsi) { + if (!isInsideExecutableRunField(completionPsi.position())) { + return false; + } + final String wholeText = completionPsi.position().getContainingFile().getText(); + final String beforeCaret = lineBeforeCaret(wholeText, completionPsi.offset()); + return beforeCaret.matches(".*\\$[A-Za-z0-9_]*$"); + } + + private static boolean isInsideExecutableRunField(final PsiElement position) { + return getParent(position, FIELD_RUN) + .filter(run -> getParent(run, "defaults").isEmpty()) + .isPresent(); + } + + private static Optional workflowStructureCompletion(final CompletionPsi completionPsi) { + final Optional context = yamlKeyContext(completionPsi); + if (context.isEmpty() || isYamlValueCompletion(context.get().currentLine())) { + return Optional.empty(); + } + final List path = context.get().path(); + if (path.isEmpty()) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.topLevelKeys(), ':')); + } + if (pathEndsWith(path, FIELD_ON)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.eventKeys(), ':')); + } + if (pathEndsWith(path, FIELD_ON, "workflow_dispatch")) { + return Optional.of(new StructureCompletion(workflowDispatchTriggerKeys(), ':')); + } + if (pathEndsWith(path, FIELD_ON, "workflow_call")) { + return Optional.of(new StructureCompletion(workflowCallTriggerKeys(), ':')); + } + if (pathEndsWith(path, FIELD_ON, "workflow_dispatch", FIELD_INPUTS) + || pathEndsWith(path, FIELD_ON, "workflow_call", FIELD_INPUTS)) { + return Optional.empty(); + } + if (isChildOf(path, FIELD_ON, "workflow_dispatch", FIELD_INPUTS) + || isChildOf(path, FIELD_ON, "workflow_call", FIELD_INPUTS)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.workflowInputPropertyKeys(), ':')); + } + if (pathEndsWith(path, FIELD_ON, "workflow_call", FIELD_OUTPUTS)) { + return Optional.empty(); + } + if (isChildOf(path, FIELD_ON, "workflow_call", FIELD_OUTPUTS)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.workflowOutputPropertyKeys(), ':')); + } + if (isChildOf(path, FIELD_ON, "workflow_call", FIELD_SECRETS)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.workflowSecretPropertyKeys(), ':')); + } + if (pathMatches(path, FIELD_ON, "*")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.eventFilterKeysFor(path.get(path.size() - 1)), ':')); + } + if (pathMatches(path, FIELD_ON, "*", "types")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.eventActivityTypesFor(path.get(1)), Character.MIN_VALUE)); + } + if (pathMatches(path, FIELD_ON, "*", "*")) { + return workflowEventFilterValueCompletion(completionPsi, context.get()); + } + if (pathEndsWith(path, "permissions")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.permissionScopes(), ':')); + } + if (pathMatches(path, "defaults", FIELD_RUN) || pathMatches(path, FIELD_JOBS, "*", "defaults", FIELD_RUN)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.defaultsRunKeys(), ':')); + } + if (pathMatches(path, "concurrency") || pathMatches(path, FIELD_JOBS, "*", "concurrency")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.concurrencyKeys(), ':')); + } + if (pathMatches(path, FIELD_JOBS, "*", "environment")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.environmentKeys(), ':')); + } + if (pathMatches(path, FIELD_JOBS, "*")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.jobKeys(), ':')); + } + if (pathMatches(path, FIELD_JOBS, "*", FIELD_STRATEGY)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.strategyKeys(), ':')); + } + if (pathMatches(path, FIELD_JOBS, "*", FIELD_STRATEGY, FIELD_MATRIX)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.matrixKeys(), ':')); + } + if (pathMatches(path, FIELD_JOBS, "*", "container")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.containerKeys(), ':')); + } + if (pathMatches(path, FIELD_JOBS, "*", "container", "credentials")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.credentialsKeys(), ':')); + } + if (pathMatches(path, FIELD_JOBS, "*", FIELD_SERVICES, "*")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.serviceKeys(), ':')); + } + if (pathMatches(path, FIELD_JOBS, "*", FIELD_SERVICES, "*", "credentials")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.credentialsKeys(), ':')); + } + if (pathMatches(path, FIELD_JOBS, "*", FIELD_STEPS)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.stepKeys(), ':')); + } + return Optional.empty(); + } + + private static Optional workflowValueCompletion(final CompletionPsi completionPsi) { + final Optional context = yamlKeyContext(completionPsi); + if (context.isEmpty()) { + return Optional.empty(); + } + final Optional key = currentLineKey(context.get().currentLine()); + if (key.isEmpty()) { + return Optional.empty(); + } + if (context.get().currentLine().contains("${{")) { + return Optional.empty(); + } + final List path = context.get().path(); + final String currentKey = key.get(); + final Optional eventFilterValueCompletion = workflowEventFilterValueCompletion(completionPsi, context.get()); + if (eventFilterValueCompletion.isPresent()) { + return eventFilterValueCompletion; + } + if ("runs-on".equals(currentKey)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.runnerLabels(), Character.MIN_VALUE)); + } + if ("permissions".equals(currentKey)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.permissionShorthandValues(), Character.MIN_VALUE)); + } + if ("types".equals(currentKey) && pathMatches(path, FIELD_ON, "*")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.eventActivityTypesFor(path.get(1)), Character.MIN_VALUE)); + } + if ("type".equals(currentKey) && isChildOf(path, FIELD_ON, "workflow_dispatch", FIELD_INPUTS)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.workflowInputTypes(), Character.MIN_VALUE)); + } + if ("type".equals(currentKey) && isChildOf(path, FIELD_ON, "workflow_call", FIELD_INPUTS)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.reusableWorkflowInputTypes(), Character.MIN_VALUE)); + } + if (pathEndsWith(path, "permissions")) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.permissionValuesFor(currentKey), Character.MIN_VALUE)); + } + if ("required".equals(currentKey) + || "continue-on-error".equals(currentKey) + || "fail-fast".equals(currentKey) + || "cancel-in-progress".equals(currentKey)) { + return Optional.of(new StructureCompletion(WorkflowSyntaxSchema.booleanValues(), Character.MIN_VALUE)); + } + return Optional.empty(); + } + + private static Optional workflowEventFilterValueCompletion(final CompletionPsi completionPsi, final YamlKeyContext context) { + return eventFilterContext(context) + .map(eventFilter -> eventFilterValueCompletions(completionPsi.position(), eventFilter.event(), eventFilter.filter())) + .filter(values -> !values.isEmpty()) + .map(values -> new StructureCompletion(values, Character.MIN_VALUE)); + } + + private static Optional eventFilterContext(final YamlKeyContext context) { + final List path = context.path(); + final Optional currentKey = currentLineKey(context.currentLine()); + if (currentKey.isPresent() && pathMatches(path, FIELD_ON, "*")) { + final String event = path.get(1); + final String filter = currentKey.get(); + return WorkflowSyntaxSchema.eventFilterKeysFor(event).containsKey(filter) + ? Optional.of(new EventFilterContext(event, filter)) + : Optional.empty(); + } + if (pathMatches(path, FIELD_ON, "*", "*")) { + final String event = path.get(1); + final String filter = path.get(2); + return WorkflowSyntaxSchema.eventFilterKeysFor(event).containsKey(filter) + ? Optional.of(new EventFilterContext(event, filter)) + : Optional.empty(); + } + return Optional.empty(); + } + + private static Map eventFilterValueCompletions(final PsiElement position, final String event, final String filter) { + return switch (filter) { + case "types" -> WorkflowSyntaxSchema.eventActivityTypesFor(event); + case "branches", "branches-ignore" -> localGitRefs(position, "heads", "completion.workflow.eventFilter.branches"); + case "tags", "tags-ignore" -> localGitRefs(position, "tags", "completion.workflow.eventFilter.tags"); + case "paths", "paths-ignore" -> localProjectPaths(position); + default -> Collections.emptyMap(); + }; + } + + private static Optional yamlKeyContext(final CompletionPsi completionPsi) { + final String wholeText = completionPsi.position().getContainingFile().getText(); + final int offset = boundedOffset(wholeText, completionPsi.offset()); + final int lineStart = currentLineStart(wholeText, offset); + final String currentLine = lineBeforeCaret(wholeText, offset); + final int currentIndent = leadingSpaces(currentLine); + final List stack = new ArrayList<>(); + wholeText.substring(0, Math.min(lineStart, wholeText.length())).lines().forEach(raw -> { + final String content = raw.trim(); + if (!content.isBlank() && !content.startsWith("#")) { + final Optional key = yamlKey(content); + key.ifPresent(value -> { + final int indent = leadingSpaces(raw); + while (!stack.isEmpty() && stack.get(stack.size() - 1).indent() >= indent) { + stack.remove(stack.size() - 1); + } + stack.add(new YamlAncestor(indent, value)); + }); + } + }); + while (!stack.isEmpty() && stack.get(stack.size() - 1).indent() >= currentIndent) { + stack.remove(stack.size() - 1); + } + return Optional.of(new YamlKeyContext(stack.stream().map(YamlAncestor::key).toList(), currentLine)); + } + + private static Optional currentLineKey(final String currentLine) { + return yamlKey(currentLine.trim()); + } + + private static Optional yamlKey(final String content) { + final String normalized = content.startsWith("- ") ? content.substring(2).trim() : content; + final int separator = normalized.indexOf(':'); + if (separator <= 0) { + return Optional.empty(); + } + return Optional.of(stripYamlKeyQuotes(normalized.substring(0, separator).trim())) + .filter(key -> !key.isBlank()); + } + + private static boolean isYamlValueCompletion(final String currentLine) { + return currentLine.replace("IntellijIdeaRulezzz", "").matches("\\s*[^:#]+:\\s*.*"); + } + + private static int leadingSpaces(final String value) { + int result = 0; + while (result < value.length() && value.charAt(result) == ' ') { + result++; + } + return result; + } + + private static String stripYamlKeyQuotes(final String value) { + if (value.length() >= 2 && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) { + return value.substring(1, value.length() - 1); + } + return value; + } + + private static boolean pathEndsWith(final List path, final String... expected) { + if (path.size() < expected.length) { + return false; + } + final int offset = path.size() - expected.length; + for (int index = 0; index < expected.length; index++) { + if (!expected[index].equals(path.get(offset + index))) { + return false; + } + } + return true; + } + + private static boolean isChildOf(final List path, final String... expectedParent) { + return path.size() == expectedParent.length + 1 && pathEndsWith(path.subList(0, path.size() - 1), expectedParent); + } + + private static boolean pathMatches(final List path, final String... pattern) { + if (path.size() != pattern.length) { + return false; + } + for (int index = 0; index < pattern.length; index++) { + if (!"*".equals(pattern[index]) && !pattern[index].equals(path.get(index))) { + return false; + } + } + return true; + } + + private static Map workflowDispatchTriggerKeys() { + final Map result = new LinkedHashMap<>(); + result.put(FIELD_INPUTS, GitHubWorkflowBundle.message("completion.context.inputs")); + return result; + } + + private static Map workflowCallTriggerKeys() { + final Map result = new LinkedHashMap<>(); + result.put(FIELD_INPUTS, GitHubWorkflowBundle.message("completion.context.inputs")); + result.put(FIELD_OUTPUTS, GitHubWorkflowBundle.message("completion.jobs.outputs")); + result.put(FIELD_SECRETS, GitHubWorkflowBundle.message("completion.context.secrets")); + return result; + } + + private static Optional remoteUsesRef(final CompletionParameters parameters) { + final String wholeText = parameters.getOriginalFile().getText(); + final String beforeCaret = lineBeforeCaret(wholeText, parameters.getOffset()); + final Matcher matcher = REMOTE_USES_REF_PATTERN.matcher(beforeCaret); + return matcher.matches() + ? Optional.of(new RemoteUsesRef(matcher.group(1), matcher.group(2))) + : Optional.empty(); + } + + private static Optional remoteUsesTargetPrefix(final CompletionParameters parameters) { + final String wholeText = parameters.getOriginalFile().getText(); + final String beforeCaret = lineBeforeCaret(wholeText, parameters.getOffset()); + final Matcher matcher = REMOTE_USES_TARGET_PATTERN.matcher(beforeCaret); + return matcher.matches() ? Optional.of(matcher.group(1)) : Optional.empty(); + } + + private static Map callableUsesCompletions(final PsiElement position, final String usesPrefix) { + final Map result = new LinkedHashMap<>(localUsesCompletions(position)); + knownRemoteUses(position).forEach((usesBase, description) -> result.putIfAbsent(usesBase, description)); + RemoteActionProviders.searchUses(usesPrefix, 10).forEach(result::putIfAbsent); + return result; + } + + private static Map localUsesCompletions(final PsiElement position) { + final Project project = getProject(position); + if (project == null) { + return Collections.emptyMap(); + } + final boolean reusableWorkflowUse = isReusableWorkflowUse(position); + final VirtualFile currentFile = position.getContainingFile().getVirtualFile(); + final Map result = new LinkedHashMap<>(); + ProjectFileIndex.getInstance(project).iterateContent(file -> { + toLocalUsesValue(project, currentFile, file, reusableWorkflowUse) + .ifPresent(value -> result.putIfAbsent(value, GitHubWorkflowBundle.message(reusableWorkflowUse + ? "completion.uses.local.workflow" + : "completion.uses.local.action"))); + return true; + }); + return result; + } + + private static Map localProjectPaths(final PsiElement position) { + final Project project = getProject(position); + if (project == null) { + return Collections.emptyMap(); + } + final Map result = new LinkedHashMap<>(); + ProjectFileIndex.getInstance(project).iterateContent(file -> { + if (!file.isDirectory() && !file.getPath().contains("/.git/")) { + final VirtualFile contentRoot = ProjectFileIndex.getInstance(project).getContentRootForFile(file); + if (contentRoot != null) { + final String path = Path.of(contentRoot.getPath()).relativize(Path.of(file.getPath())).toString().replace('\\', '/'); + if (!path.isBlank()) { + result.putIfAbsent(path, GitHubWorkflowBundle.message("completion.workflow.eventFilter.paths")); + } + } + } + return result.size() < 200; + }); + return result; + } + + private static Map localGitRefs(final PsiElement position, final String namespace, final String descriptionKey) { + final Map result = new LinkedHashMap<>(); + repositoryRoot(position) + .flatMap(CodeCompletion::gitDir) + .ifPresent(gitDir -> { + readLooseRefs(gitDir.resolve("refs").resolve(namespace), result, descriptionKey); + readPackedRefs(gitDir.resolve("packed-refs"), "refs/" + namespace + "/", result, descriptionKey); + }); + return result; + } + + private static void readLooseRefs(final Path refRoot, final Map result, final String descriptionKey) { + if (!Files.isDirectory(refRoot)) { + return; + } + try (Stream paths = Files.walk(refRoot)) { + paths.filter(Files::isRegularFile) + .map(refRoot::relativize) + .map(Path::toString) + .map(value -> value.replace('\\', '/')) + .filter(value -> !value.isBlank()) + .sorted() + .forEach(ref -> result.putIfAbsent(ref, GitHubWorkflowBundle.message(descriptionKey))); + } catch (final IOException ignored) { + // Local ref completion is opportunistic. + } + } + + private static void readPackedRefs(final Path packedRefs, final String prefix, final Map result, final String descriptionKey) { + if (!Files.isRegularFile(packedRefs)) { + return; + } + try (Stream lines = Files.lines(packedRefs)) { + lines.map(String::trim) + .filter(line -> !line.isBlank() && !line.startsWith("#") && !line.startsWith("^")) + .map(line -> line.split("\\s+", 2)) + .filter(parts -> parts.length == 2 && parts[1].startsWith(prefix)) + .map(parts -> parts[1].substring(prefix.length())) + .filter(value -> !value.isBlank()) + .forEach(ref -> result.putIfAbsent(ref, GitHubWorkflowBundle.message(descriptionKey))); + } catch (final IOException ignored) { + // Local ref completion is opportunistic. + } + } + + private static Optional repositoryRoot(final PsiElement position) { + Path current = ofNullable(position) + .map(PsiElement::getContainingFile) + .map(file -> file.getVirtualFile()) + .map(VirtualFile::getPath) + .map(Path::of) + .map(Path::getParent) + .orElse(null); + while (current != null) { + if (Files.isDirectory(current.resolve(".git")) || Files.isRegularFile(current.resolve(".git"))) { + return Optional.of(current); + } + current = current.getParent(); + } + return ofNullable(getProject(position)) + .map(Project::getBasePath) + .map(Path::of) + .filter(path -> Files.isDirectory(path.resolve(".git")) || Files.isRegularFile(path.resolve(".git"))); + } + + private static Optional gitDir(final Path projectDir) { + final Path dotGit = projectDir.resolve(".git"); + if (Files.isDirectory(dotGit)) { + return Optional.of(dotGit); + } + if (!Files.isRegularFile(dotGit)) { + return Optional.empty(); + } + try { + final String value = Files.readString(dotGit).trim(); + if (!value.startsWith("gitdir:")) { + return Optional.empty(); + } + final Path path = Path.of(value.substring("gitdir:".length()).trim()); + return Optional.of(path.isAbsolute() ? path : projectDir.resolve(path).normalize()); + } catch (final IOException ignored) { + return Optional.empty(); + } + } + + private static Map knownRemoteRefs(final PsiElement position, final String usesBase) { + final Map result = new LinkedHashMap<>(); + knownRemoteActions(position).stream() + .filter(action -> splitRemoteUses(action.usesValue()).map(uses -> usesBase.equals(uses.base())).orElse(false)) + .flatMap(action -> action.remoteRefs().stream()) + .forEach(ref -> result.putIfAbsent(ref, GitHubWorkflowBundle.message("completion.uses.ref.known"))); + knownRemoteUsesValues(position).stream() + .map(CodeCompletion::splitRemoteUses) + .flatMap(Optional::stream) + .filter(uses -> usesBase.equals(uses.base())) + .forEach(uses -> result.putIfAbsent(uses.ref(), GitHubWorkflowBundle.message("completion.uses.ref.known"))); + GitHubActionCache.getActionCache().remoteRefsFor(usesBase, 10) + .forEach(ref -> result.putIfAbsent(ref, GitHubWorkflowBundle.message("completion.uses.ref.remote"))); + return result; + } + + private static Map knownRemoteUses(final PsiElement position) { + final Map result = new LinkedHashMap<>(); + knownRemoteUsesValues(position).stream() + .map(CodeCompletion::splitRemoteUses) + .flatMap(Optional::stream) + .forEach(uses -> result.putIfAbsent(uses.base(), GitHubWorkflowBundle.message("completion.uses.remote.known"))); + return result; + } + + private static List knownRemoteUsesValues(final PsiElement position) { + return Stream.concat( + knownRemoteActions(position).stream().map(GitHubAction::usesValue), + getAllElements(position.getContainingFile(), FIELD_USES).stream() + .map(PsiElementHelper::getText) + .flatMap(Optional::stream) + ) + .filter(uses -> uses.contains("@") && !uses.startsWith(".")) + .distinct() + .toList(); + } + + private static List knownRemoteActions(final PsiElement position) { + return ofNullable(GitHubActionCache.getActionCache().getState()) + .stream() + .flatMap(state -> state.actions.values().stream()) + .filter(action -> action.usesValue().contains("@") && !action.usesValue().startsWith(".")) + .distinct() + .toList(); + } + + private static Optional splitRemoteUses(final String usesValue) { + final int refSeparator = usesValue.lastIndexOf('@'); + if (refSeparator < 1 || refSeparator == usesValue.length() - 1) { + return Optional.empty(); + } + return Optional.of(new RemoteUses( + usesValue.substring(0, refSeparator), + usesValue.substring(refSeparator + 1) + )); + } + + private static boolean isReusableWorkflowUse(final PsiElement position) { + return getParentStep(position).isEmpty() && currentJob(position).isPresent(); + } + + private static Optional toLocalUsesValue( + final Project project, + final VirtualFile currentFile, + final VirtualFile file, + final boolean reusableWorkflowUse + ) { + if (file == null || file.isDirectory() || file.equals(currentFile)) { + return Optional.empty(); + } + final VirtualFile contentRoot = ProjectFileIndex.getInstance(project).getContentRootForFile(file); + if (contentRoot == null) { + return Optional.empty(); + } + final Path basePath = Path.of(contentRoot.getPath()); + final Path path = Path.of(file.getPath()); + if (reusableWorkflowUse && isWorkflowFile(path)) { + return Optional.of("./" + basePath.relativize(path)); + } + if (!reusableWorkflowUse && isActionFile(path)) { + final Path actionDirectory = path.getParent(); + final Path relative = basePath.relativize(actionDirectory); + return Optional.of(relative.toString().isEmpty() ? "./" : "./" + relative); + } + return Optional.empty(); + } + + private static Optional currentCallableAction(final CompletionParameters parameters, final PsiElement position) { + return currentCallable(position) + .map(GitHubActionCache::getAction) + .filter(GitHubAction::isResolved) + .or(() -> nearestPreviousUsesValue(parameters) + .map(usesValue -> GitHubActionCache.getActionCache().get(getProject(position), usesValue))); + } + + private static Optional currentCallable(final PsiElement position) { + return getParent(position, FIELD_WITH) + .flatMap(with -> getParentStepOrJob(with) + .flatMap(callable -> PsiElementHelper.getChild(callable, FIELD_USES)) + ) + .or(() -> currentStep(position).flatMap(callable -> PsiElementHelper.getChild(callable, FIELD_USES))) + .or(() -> currentJob(position).flatMap(callable -> PsiElementHelper.getChild(callable, FIELD_USES))) + .or(() -> currentStepOrJob(position).flatMap(callable -> PsiElementHelper.getChild(callable, FIELD_USES))); + } + + private static Optional nearestPreviousUsesValue(final CompletionParameters parameters) { + final String wholeText = parameters.getOriginalFile().getText(); + final int offset = boundedOffset(wholeText, parameters.getOffset()); + final int lineStart = currentLineStart(wholeText, offset); + final String beforeCaret = wholeText.substring(0, Math.min(lineStart, wholeText.length())); + final String[] lines = beforeCaret.split("\\R"); + for (int index = lines.length - 1; index >= 0; index--) { + final String line = lines[index]; + final String trimmed = line.trim(); + if (trimmed.startsWith(FIELD_USES + ":")) { + return Optional.of(trimmed.substring((FIELD_USES + ":").length()).trim()) + .map(PsiElementHelper::removeQuotes) + .filter(PsiElementHelper::hasText); + } + } + return Optional.empty(); + } + private static void addCodeCompletionItems(final CompletionResultSet resultSet, final String[] cbi, final PsiElement position, final String[] prefix) { final Map> completionResultMap = new HashMap<>(); for (int i = 0; i < cbi.length; i++) { //DON'T AUTO COMPLETE WHEN PREVIOUS ITEM IS NOT VALID final List previousCompletions = ofNullable(completionResultMap.getOrDefault(i - 1, null)).orElseGet(ArrayList::new); final int index = i; - if (i != 0 && (previousCompletions.isEmpty() || previousCompletions.stream().noneMatch(item -> item.key().equals(cbi[index])))) { + if (i != 0 && !previousCompletions.isEmpty() && previousCompletions.stream().noneMatch(item -> item.key().equals(cbi[index]))) { return; } else { addCompletionItems(cbi, i, position, completionResultMap); @@ -125,33 +800,93 @@ private static void addCompletionItems(final String[] cbi, final int i, final Ps if (i == 0) { handleFirstItem(cbi, i, position, completionItemMap); } else if (i == 1) { - handleSecondItem(cbi, i, completionItemMap); + handleSecondItem(cbi, i, position, completionItemMap); } else if (i == 2) { handleThirdItem(cbi, i, position, completionItemMap); + } else if (i == 3) { + handleFourthItem(cbi, i, position, completionItemMap); } } + private static boolean isCompletingNeedsField(final CompletionParameters parameters, final PsiElement position) { + if (getParent(position, FIELD_NEEDS).isPresent()) { + return true; + } + final String wholeText = parameters.getOriginalFile().getText(); + final String beforeCaret = lineBeforeCaret(wholeText, parameters.getOffset()); + return beforeCaret.matches("\\s*" + FIELD_NEEDS + "\\s*:\\s*.*"); + } + + private static Optional currentStepOrJob(final PsiElement position) { + return getParentStepOrJob(position) + .or(() -> currentStep(position).map(PsiElement.class::cast)) + .or(() -> currentJob(position).map(PsiElement.class::cast)); + } + + private static Optional currentStep(final PsiElement position) { + return ofNullable(position) + .map(PsiElement::getContainingFile) + .map(file -> currentOrPrevious(position, getAllElements(file, FIELD_STEPS).stream().flatMap(steps -> getChildSteps(steps).stream())) + .findFirst() + .orElse(null)); + } + + private static Optional currentJob(final PsiElement position) { + return ofNullable(position) + .map(PsiElement::getContainingFile) + .map(file -> currentOrPrevious(position, getAllElements(file, FIELD_JOBS).stream().flatMap(jobs -> PsiElementHelper.getChildren(jobs, YAMLKeyValue.class).stream())) + .findFirst() + .orElse(null)); + } + + private static Stream currentOrPrevious(final PsiElement position, final Stream candidates) { + final int offset = ofNullable(position.getTextRange()).map(TextRange::getStartOffset).orElse(-1); + return candidates + .filter(candidate -> containsOffset(candidate, position) || candidate.getTextRange().getStartOffset() <= offset) + .reduce((previous, current) -> containsOffset(previous, position) ? previous : current) + .stream(); + } + + private static boolean containsOffset(final PsiElement candidate, final PsiElement position) { + final TextRange candidateRange = candidate.getTextRange(); + final TextRange positionRange = position.getTextRange(); + return candidateRange != null && positionRange != null && candidateRange.contains(positionRange.getStartOffset()); + } + private static void handleThirdItem(final String[] cbi, final int i, final PsiElement position, final Map> completionItemMap) { switch (cbi[0]) { case FIELD_JOBS -> completionItemMap.put(i, codeCompletionJobs(cbi[1], position)); case FIELD_NEEDS -> completionItemMap.put(i, codeCompletionNeeds(cbi[1], position)); case FIELD_STEPS -> completionItemMap.put(i, Steps.codeCompletionSteps(cbi[1], position)); + case FIELD_JOB -> completionItemMap.put(i, codeCompletionJob(cbi[1], cbi[2], position)); default -> { // ignored } } } - private static void handleSecondItem(final String[] cbi, final int i, final Map> completionItemMap) { + private static void handleFourthItem(final String[] cbi, final int i, final PsiElement position, final Map> completionItemMap) { + if (FIELD_JOB.equals(cbi[0])) { + completionItemMap.put(i, codeCompletionJob(cbi[1], cbi[2], cbi[3], position)); + } else if (FIELD_NEEDS.equals(cbi[0]) && FIELD_OUTPUTS.equals(cbi[2])) { + completionItemMap.put(i, codeCompletionNeeds(cbi[1], position)); + } else if (FIELD_JOBS.equals(cbi[0]) && FIELD_OUTPUTS.equals(cbi[2])) { + completionItemMap.put(i, codeCompletionJobs(cbi[1], position)); + } + } + + private static void handleSecondItem(final String[] cbi, final int i, final PsiElement position, final Map> completionItemMap) { switch (cbi[0]) { case FIELD_STEPS -> completionItemMap.put(i, List.of( - completionItemOf(FIELD_OUTPUTS, "The set of outputs defined for the step.", ICON_OUTPUT), - completionItemOf(FIELD_CONCLUSION, "The result of a completed step after continue-on-error is applied.", ICON_OUTPUT), - completionItemOf(FIELD_OUTCOME, "The result of a completed step before continue-on-error is applied.", ICON_OUTPUT) + completionItemOf(FIELD_OUTPUTS, GitHubWorkflowBundle.message("completion.steps.outputs"), ICON_OUTPUT), + completionItemOf(FIELD_CONCLUSION, GitHubWorkflowBundle.message("completion.steps.conclusion"), ICON_OUTPUT), + completionItemOf(FIELD_OUTCOME, GitHubWorkflowBundle.message("completion.steps.outcome"), ICON_OUTPUT) )); case FIELD_JOBS, FIELD_NEEDS -> completionItemMap.put(i, List.of( - completionItemOf(FIELD_OUTPUTS, "The set of outputs defined for the step.", ICON_OUTPUT) + completionItemOf(FIELD_OUTPUTS, GitHubWorkflowBundle.message("completion.jobs.outputs"), ICON_OUTPUT), + completionItemOf(FIELD_RESULT, GitHubWorkflowBundle.message("completion.jobs.result"), ICON_OUTPUT) )); + case FIELD_JOB -> completionItemMap.put(i, codeCompletionJob(cbi[1], position)); default -> { // ignored } @@ -164,7 +899,11 @@ private static void handleFirstItem(final String[] cbi, final int i, final PsiEl case FIELD_JOBS -> completionItemMap.put(i, codeCompletionJobs(position)); case FIELD_ENVS -> completionItemMap.put(i, listEnvs(position)); case FIELD_GITHUB -> completionItemMap.put(i, codeCompletionGithub()); + case FIELD_GITEA -> completionItemMap.put(i, codeCompletionGitea()); + case FIELD_JOB -> completionItemMap.put(i, codeCompletionJob()); + case FIELD_MATRIX -> completionItemMap.put(i, listMatrix(position)); case FIELD_RUNNER -> completionItemMap.put(i, codeCompletionRunner()); + case FIELD_STRATEGY -> completionItemMap.put(i, codeCompletionStrategy()); case FIELD_INPUTS -> completionItemMap.put(i, listInputs(position)); case FIELD_SECRETS -> completionItemMap.put(i, listSecrets(position)); case FIELD_NEEDS -> completionItemMap.put(i, codeCompletionNeeds(position)); @@ -190,6 +929,7 @@ private static void addDefaultCodeCompletionItems(final int i, final PsiElement Optional.of(listInputs(position)).filter(List::isEmpty).ifPresent(empty -> copyMap.remove(FIELD_INPUTS)); Optional.of(listSecrets(position)).filter(List::isEmpty).ifPresent(empty -> copyMap.remove(FIELD_SECRETS)); Optional.of(listJobs(position)).filter(List::isEmpty).ifPresent(empty -> copyMap.remove(FIELD_JOBS)); + Optional.of(listMatrix(position)).filter(List::isEmpty).ifPresent(empty -> copyMap.remove(FIELD_MATRIX)); Optional.of(listSteps(position)).filter(List::isEmpty).ifPresent(empty -> copyMap.remove(FIELD_STEPS)); Optional.of(listJobNeeds(position)).filter(List::isEmpty).ifPresent(empty -> copyMap.remove(FIELD_NEEDS)); return copyMap; @@ -201,8 +941,47 @@ private static void addDefaultCodeCompletionItems(final int i, final PsiElement private static String getDefaultPrefix(final CompletionParameters parameters) { final String wholeText = parameters.getOriginalFile().getText(); final int caretOffset = parameters.getOffset(); - final int indexStart = getStartIndex(wholeText, caretOffset - 1); - return wholeText.substring(indexStart, caretOffset); + return getDefaultPrefix(wholeText, caretOffset); + } + + private static String getDefaultPrefix(final CompletionPsi completionPsi) { + final String wholeText = completionPsi.position().getContainingFile().getText(); + return getDefaultPrefix(wholeText, completionPsi.offset()); + } + + private static String getDefaultPrefix(final String wholeText, final int caretOffset) { + final int offset = boundedOffset(wholeText, caretOffset); + if (offset < 1) { + return ""; + } + final int indexStart = getStartIndex(wholeText, offset - 1); + if (indexStart < 0 || indexStart > offset) { + return ""; + } + return wholeText.substring(indexStart, offset) + .replace("IntellijIdeaRulezzz", "") + .replaceFirst("^[\\s\\[,'\"]+", "") + .trim(); + } + + static String lineBeforeCaret(final String wholeText, final int rawOffset) { + final int offset = boundedOffset(wholeText, rawOffset); + final int lineStart = currentLineStart(wholeText, offset); + if (lineStart > offset) { + return ""; + } + return wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + } + + private static int currentLineStart(final String wholeText, final int offset) { + if (offset < 1) { + return 0; + } + return wholeText.lastIndexOf('\n', offset - 1) + 1; + } + + private static int boundedOffset(final String wholeText, final int rawOffset) { + return Math.max(0, Math.min(rawOffset, wholeText.length())); } private static void addLookupElements(final CompletionResultSet resultSet, final Map map, final NodeIcon icon, final char suffix) { @@ -219,4 +998,25 @@ private static List toLookupElements(final Map ma private static List toLookupItems(final List items) { return items.stream().map(SimpleElement::toLookupElement).toList(); } + + private record RemoteUses(String base, String ref) { + } + + private record RemoteUsesRef(String usesBase, String prefix) { + } + + private record StructureCompletion(Map items, char suffix) { + } + + private record YamlAncestor(int indent, String key) { + } + + private record YamlKeyContext(List path, String currentLine) { + } + + private record EventFilterContext(String event, String filter) { + } + + private record CompletionPsi(PsiElement position, int offset) { + } } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTarget.java b/src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTarget.java new file mode 100644 index 0000000..fe19fde --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTarget.java @@ -0,0 +1,7 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.model.SimpleElement; +import com.intellij.psi.PsiElement; + +record ExpressionReferenceTarget(String kind, SimpleElement source, SimpleElement segment, PsiElement target) { +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTargets.java b/src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTargets.java new file mode 100644 index 0000000..b67f324 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTargets.java @@ -0,0 +1,314 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; +import com.github.yunabraska.githubworkflow.model.SimpleElement; +import com.intellij.psi.PsiElement; +import org.jetbrains.yaml.psi.YAMLKeyValue; +import org.jetbrains.yaml.psi.YAMLSequenceItem; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_ENVS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_GITHUB; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_ID; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_INPUTS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_JOB; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_JOBS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_MATRIX; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_NEEDS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_ON; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_OUTPUTS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_PORTS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_RUN; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_SECRETS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_SERVICES; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_STEPS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_STRATEGY; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_USES; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChild; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getText; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getTextElements; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.removeQuotes; +import static com.github.yunabraska.githubworkflow.logic.Inputs.listInputsRaw; +import static com.github.yunabraska.githubworkflow.logic.JobContext.getService; +import static com.github.yunabraska.githubworkflow.logic.Jobs.listAllJobs; +import static com.github.yunabraska.githubworkflow.logic.Needs.getJobNeed; +import static com.github.yunabraska.githubworkflow.logic.Steps.listSteps; +import static com.github.yunabraska.githubworkflow.services.HighlightAnnotator.splitToElements; +import static com.github.yunabraska.githubworkflow.services.HighlightAnnotator.toSimpleElements; +import static java.util.Optional.ofNullable; + +final class ExpressionReferenceTargets { + + static List resolve(final PsiElement psiElement) { + return toSimpleElements(psiElement).stream() + .flatMap(source -> resolveSource(psiElement, source).stream()) + .toList(); + } + + static List resolveAt(final PsiElement psiElement, final int offsetInElement) { + return resolve(psiElement).stream() + .filter(target -> contains(target.segment(), offsetInElement)) + .toList(); + } + + static Optional segmentAt(final PsiElement psiElement, final int offsetInElement) { + return toSimpleElements(psiElement).stream() + .filter(source -> contains(source, offsetInElement)) + .flatMap(source -> Stream.of(splitToElements(source))) + .filter(segment -> contains(segment, offsetInElement)) + .findFirst(); + } + + private static boolean contains(final SimpleElement segment, final int offsetInElement) { + return segment.startIndexOffset() - 1 <= offsetInElement && offsetInElement <= segment.endIndexOffset(); + } + + private static List resolveSource(final PsiElement psiElement, final SimpleElement source) { + final SimpleElement[] parts = splitToElements(source); + if (parts.length < 2) { + return List.of(); + } + final List result = new ArrayList<>(); + switch (parts[0].text()) { + case FIELD_INPUTS -> resolveInput(psiElement, source, parts[1]).ifPresent(result::add); + case FIELD_SECRETS -> resolveSecret(psiElement, source, parts[1]).ifPresent(result::add); + case FIELD_ENVS -> resolveEnv(psiElement, source, parts[1]).ifPresent(result::add); + case FIELD_MATRIX -> resolveMatrix(psiElement, source, parts[1]).ifPresent(result::add); + case FIELD_JOB -> resolveJobContext(psiElement, source, parts).ifPresent(result::add); + case FIELD_STEPS -> { + resolveStep(psiElement, source, parts[1]).ifPresent(result::add); + resolveStepOutput(psiElement, source, parts).ifPresent(result::add); + } + case FIELD_NEEDS -> { + resolveNeed(psiElement, source, parts[1]).ifPresent(result::add); + resolveNeedOutput(psiElement, source, parts).ifPresent(result::add); + } + case FIELD_JOBS -> { + resolveJob(psiElement, source, parts[1]).ifPresent(result::add); + resolveJobOutput(psiElement, source, parts).ifPresent(result::add); + } + default -> { + // Built-in contexts without a local declaration stay validated by highlighters, but are not clickable. + } + } + return result; + } + + private static Optional resolveInput( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement inputId + ) { + return listInputsRaw(psiElement).stream() + .filter(input -> inputId.text().equals(input.getKeyText())) + .findFirst() + .map(input -> new ExpressionReferenceTarget("input", source, inputId, input)); + } + + private static Optional resolveSecret( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement secretId + ) { + return getChild(psiElement.getContainingFile(), FIELD_ON) + .stream() + .flatMap(on -> PsiElementHelper.getAllElements(on, FIELD_SECRETS).stream()) + .flatMap(secrets -> PsiElementHelper.getChildren(secrets).stream()) + .filter(secret -> secretId.text().equals(secret.getKeyText())) + .findFirst() + .map(secret -> new ExpressionReferenceTarget("secret", source, secretId, secret)); + } + + private static Optional resolveEnv( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement envId + ) { + return Stream.of(stepEnv(psiElement, envId), jobEnv(psiElement, envId), workflowEnv(psiElement, envId)) + .flatMap(Optional::stream) + .findFirst() + .map(env -> new ExpressionReferenceTarget("env", source, envId, env)); + } + + private static Optional stepEnv(final PsiElement psiElement, final SimpleElement envId) { + return PsiElementHelper.getParentStep(psiElement) + .flatMap(step -> getChild(step, FIELD_ENVS)) + .flatMap(env -> childByKey(env, envId.text())); + } + + private static Optional jobEnv(final PsiElement psiElement, final SimpleElement envId) { + return PsiElementHelper.getParentJob(psiElement) + .flatMap(job -> getChild(job, FIELD_ENVS)) + .flatMap(env -> childByKey(env, envId.text())); + } + + private static Optional workflowEnv(final PsiElement psiElement, final SimpleElement envId) { + return getChild(psiElement.getContainingFile(), FIELD_ENVS) + .flatMap(env -> childByKey(env, envId.text())); + } + + private static Optional resolveMatrix( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement matrixId + ) { + return PsiElementHelper.getParentJob(psiElement) + .flatMap(job -> getChild(job, FIELD_STRATEGY)) + .flatMap(strategy -> getChild(strategy, FIELD_MATRIX)) + .flatMap(matrix -> matrixProperty(matrix, matrixId.text())) + .map(matrix -> new ExpressionReferenceTarget("matrix", source, matrixId, matrix)); + } + + private static Optional resolveJobContext( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement[] parts + ) { + if (parts.length >= 3 && FIELD_SERVICES.equals(parts[1].text())) { + if (parts.length >= 5 && FIELD_PORTS.equals(parts[3].text())) { + return getService(psiElement, parts[2].text()) + .flatMap(service -> getChild(service, FIELD_PORTS)) + .map(ports -> new ExpressionReferenceTarget("service-port", source, parts[4], ports)); + } + return getService(psiElement, parts[2].text()) + .map(service -> new ExpressionReferenceTarget("service", source, parts[2], service)); + } + if (parts.length >= 3 && "container".equals(parts[1].text())) { + return PsiElementHelper.getParentJob(psiElement) + .flatMap(job -> getChild(job, "container")) + .map(container -> new ExpressionReferenceTarget("container", source, parts[2], container)); + } + return Optional.empty(); + } + + private static Optional matrixProperty(final YAMLKeyValue matrix, final String key) { + return Stream.concat( + PsiElementHelper.getChildren(matrix).stream() + .filter(ExpressionReferenceTargets::isDirectMatrixProperty), + getChild(matrix, "include") + .stream() + .flatMap(include -> PsiElementHelper.getChildren(include, YAMLSequenceItem.class).stream()) + .flatMap(item -> PsiElementHelper.getChildren(item).stream()) + ) + .filter(property -> key.equals(property.getKeyText())) + .findFirst(); + } + + private static boolean isDirectMatrixProperty(final YAMLKeyValue keyValue) { + final String key = keyValue.getKeyText(); + return !"include".equals(key) && !"exclude".equals(key); + } + + private static Optional resolveStep( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement stepId + ) { + return listSteps(psiElement).stream() + .map(step -> getChild(step, FIELD_ID).orElse(null)) + .filter(Objects::nonNull) + .filter(id -> getText(id).filter(stepId.text()::equals).isPresent()) + .findFirst() + .map(step -> new ExpressionReferenceTarget("step", source, stepId, step)); + } + + private static Optional resolveStepOutput( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement[] parts + ) { + if (parts.length < 4 || !FIELD_OUTPUTS.equals(parts[2].text())) { + return Optional.empty(); + } + return listSteps(psiElement).stream() + .filter(step -> getText(step, FIELD_ID).filter(parts[1].text()::equals).isPresent()) + .findFirst() + .flatMap(step -> stepOutputTarget(step, parts[3].text())) + .map(output -> new ExpressionReferenceTarget("step-output", source, parts[3], output)); + } + + private static Optional stepOutputTarget(final YAMLSequenceItem step, final String outputId) { + return getChild(step, FIELD_RUN) + .filter(run -> PsiElementHelper.parseOutputVariables(run).stream().anyMatch(output -> outputId.equals(output.key()))) + .map(PsiElement.class::cast) + .or(() -> getChild(step, FIELD_USES) + .filter(uses -> com.github.yunabraska.githubworkflow.logic.Action.listActionsOutputs(step).stream() + .anyMatch(output -> outputId.equals(output.key()))) + .map(PsiElement.class::cast)); + } + + private static Optional resolveNeed( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement needId + ) { + return getJobNeed(psiElement).stream() + .flatMap(need -> getTextElements(need).stream()) + .filter(need -> needId.text().equals(removeQuotes(need.getText()))) + .findFirst() + .map(need -> new ExpressionReferenceTarget("need", source, needId, need)); + } + + private static Optional resolveNeedOutput( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement[] parts + ) { + if (parts.length < 4 || !FIELD_OUTPUTS.equals(parts[2].text())) { + return Optional.empty(); + } + return jobById(psiElement, parts[1].text()) + .flatMap(job -> jobOutput(job, parts[3].text())) + .map(output -> new ExpressionReferenceTarget("need-output", source, parts[3], output)); + } + + private static Optional resolveJob( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement jobId + ) { + return jobById(psiElement, jobId.text()) + .map(job -> new ExpressionReferenceTarget("job", source, jobId, job)); + } + + private static Optional resolveJobOutput( + final PsiElement psiElement, + final SimpleElement source, + final SimpleElement[] parts + ) { + if (parts.length < 4 || !FIELD_OUTPUTS.equals(parts[2].text())) { + return Optional.empty(); + } + return jobById(psiElement, parts[1].text()) + .flatMap(job -> jobOutput(job, parts[3].text())) + .map(output -> new ExpressionReferenceTarget("job-output", source, parts[3], output)); + } + + private static Optional jobById(final PsiElement psiElement, final String jobId) { + return listAllJobs(psiElement).stream() + .filter(job -> jobId.equals(job.getKeyText())) + .findFirst(); + } + + private static Optional jobOutput(final YAMLKeyValue job, final String outputId) { + return getChild(job, FIELD_OUTPUTS) + .flatMap(outputs -> childByKey(outputs, outputId)); + } + + private static Optional childByKey(final PsiElement parent, final String key) { + return ofNullable(parent) + .stream() + .flatMap(element -> PsiElementHelper.getChildren(element).stream()) + .filter(child -> key.equals(child.getKeyText())) + .findFirst(); + } + + private ExpressionReferenceTargets() { + // static helper class + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java index 5258a99..89b1cfc 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java @@ -4,6 +4,7 @@ import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; import com.github.yunabraska.githubworkflow.model.GitHubAction; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; @@ -23,15 +24,30 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.yaml.psi.YAMLKeyValue; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.Base64; import java.util.Collection; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.CACHE_ONE_DAY; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_USES; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getProject; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.toPath; @@ -44,11 +60,29 @@ @State(name = "GitHubActionCache", storages = {@Storage("githubActionCache.xml")}) public class GitHubActionCache implements PersistentStateComponent { + private static final String DEFAULT_REMOTE_REF = "main"; + public static class State { public final Map actions = new ConcurrentHashMap<>(); } private final State state = new State(); + private final java.util.Set inFlightResolutions = ConcurrentHashMap.newKeySet(); + private final AtomicReference actionResolver = new AtomicReference<>(GitHubAction::resolve); + + /** + * Strategy used by cache refresh operations to resolve action metadata. + */ + @FunctionalInterface + public interface ActionResolver { + /** + * Resolves remote or local action metadata for cache refresh operations. + * + * @param action action metadata object to resolve + * @return the resolved action object, usually the same instance + */ + GitHubAction resolve(GitHubAction action); + } public static GitHubActionCache getActionCache() { return ApplicationManager.getApplication().getService(GitHubActionCache.class); @@ -80,12 +114,37 @@ public void cleanUp() { protected GitHubAction get(final Project project, final String usesValue) { final String usesCleaned = usesValue.replace("IntellijIdeaRulezzz", ""); - final boolean isLocal = !usesCleaned.contains("@"); - final String path = getAbsolutePath(isLocal, usesCleaned, project); + final boolean isLocal = isLocalUses(usesCleaned); + final String normalizedUses = normalizeUsesValue(usesCleaned, isLocal); + final String path = getAbsolutePath(isLocal, normalizedUses, project); return ofNullable(path) - .map(state.actions::get) - .map(action -> System.currentTimeMillis() < action.expiryTime() ? action : saveNewAction(usesCleaned, path, isLocal, action)) - .orElseGet(() -> saveNewAction(usesCleaned, path, isLocal, null)); + .map(absolutePath -> ofNullable(state.actions.get(absolutePath)) + .or(() -> ofNullable(state.actions.get(normalizedUses))) + .or(() -> ofNullable(state.actions.get(usesCleaned))) + .orElse(null)) + .map(action -> cachedOrRefresh(normalizedUses, path, isLocal, action)) + .orElseGet(() -> createdOrQueuedRemote(normalizedUses, path, isLocal, null)); + } + + private GitHubAction cachedOrRefresh(final String usesValue, final String path, final boolean isLocal, final GitHubAction action) { + if (!isLocal && !action.isResolved()) { + return queueRefresh(action); + } + if (System.currentTimeMillis() < action.expiryTime()) { + return action; + } + if (action.isResolved() && !isLocal) { + queueRefresh(action); + return action; + } + return createdOrQueuedRemote(usesValue, path, isLocal, action); + } + + private GitHubAction queueRefresh(final GitHubAction action) { + if (action != null) { + resolveInBackground(List.of(action)); + } + return action; } public String remove(final String usesValue) { @@ -93,13 +152,171 @@ public String remove(final String usesValue) { return usesValue; } + public CacheSummary summary() { + final List actions = state.actions.values().stream().distinct().toList(); + final long resolved = actions.stream().filter(GitHubAction::isResolved).count(); + final long remote = actions.stream().filter(action -> !action.isLocal()).count(); + final long expired = actions.stream().filter(action -> System.currentTimeMillis() >= action.expiryTime()).count(); + final long suppressed = actions.stream().filter(GitHubAction::hasSuppressedWarnings).count(); + return new CacheSummary(actions.size(), resolved, remote, expired, suppressed); + } + + public List entries() { + final long now = System.currentTimeMillis(); + return state.actions.entrySet().stream() + .map(entry -> CacheEntry.from(entry.getKey(), entry.getValue(), now)) + .sorted((left, right) -> left.key().compareToIgnoreCase(right.key())) + .toList(); + } + + public CacheSummary removeAll(final Collection keys) { + ofNullable(keys).stream() + .flatMap(Collection::stream) + .filter(PsiElementHelper::hasText) + .forEach(state.actions::remove); + triggerSyntaxHighlightingForActiveFiles(); + return summary(); + } + + public long estimatedSizeBytes() { + return state.actions.entrySet().stream() + .mapToLong(entry -> estimate(entry.getKey()) + estimate(entry.getValue())) + .sum(); + } + + /** + * Exports cache metadata to a portable line-based file. + * + * @param output target file path + * @return current cache summary after export + * @throws IOException when the file cannot be written + */ + public CacheSummary exportCache(final Path output) throws IOException { + try (BufferedWriter writer = Files.newBufferedWriter(output, StandardCharsets.UTF_8)) { + writer.write("github-workflow-cache-v1"); + writer.newLine(); + for (final Map.Entry entry : new LinkedHashMap<>(state.actions).entrySet()) { + writer.write(encode(entry.getKey())); + writer.write('\t'); + writer.write(encode(entry.getValue().getMetaData())); + writer.write('\t'); + writer.write(encode(entry.getValue().getInputs())); + writer.write('\t'); + writer.write(encode(entry.getValue().getOutputs())); + writer.write('\t'); + writer.write(encode(entry.getValue().getSecrets())); + writer.newLine(); + } + } + return summary(); + } + + /** + * Imports cache metadata from a file produced by {@link #exportCache(Path)}. + * + * @param input source file path + * @return cache summary after import + * @throws IOException when the file cannot be read or decoded + */ + public CacheSummary importCache(final Path input) throws IOException { + try (BufferedReader reader = Files.newBufferedReader(input, StandardCharsets.UTF_8)) { + final String header = reader.readLine(); + if (!"github-workflow-cache-v1".equals(header)) { + throw new IOException(GitHubWorkflowBundle.message("settings.cache.import.unsupported")); + } + String line; + while ((line = reader.readLine()) != null) { + final String[] parts = line.split("\\t", -1); + if (parts.length != 5) { + throw new IOException(GitHubWorkflowBundle.message("settings.cache.import.brokenLine")); + } + final GitHubAction action = new GitHubAction() + .setMetaData(decode(parts[1])) + .setInputs(decode(parts[2])) + .setOutputs(decode(parts[3])) + .setSecrets(decode(parts[4])); + final String key = decode(parts[0]).getOrDefault("key", ""); + if (key.isBlank()) { + throw new IOException(GitHubWorkflowBundle.message("settings.cache.import.brokenKey")); + } + state.actions.put(key, action); + } + } + triggerSyntaxHighlightingForActiveFiles(); + return summary(); + } + + public CacheSummary clear() { + state.actions.clear(); + inFlightResolutions.clear(); + triggerSyntaxHighlightingForActiveFiles(); + return summary(); + } + + public CacheSummary refreshResolvedRemoteActions() { + final List actions = state.actions.values().stream() + .distinct() + .filter(GitHubAction::isResolved) + .filter(action -> !action.isLocal()) + .peek(action -> action.expiryTime(0)) + .toList(); + resolveAsync(actions); + return summary(); + } + + public long restoreWarnings() { + final List suppressedActions = state.actions.values().stream() + .distinct() + .filter(GitHubAction::hasSuppressedWarnings) + .toList(); + suppressedActions.forEach(GitHubAction::restoreWarnings); + if (!suppressedActions.isEmpty()) { + triggerSyntaxHighlightingForActiveFiles(); + } + return suppressedActions.size(); + } + + public List remoteRefsFor(final String usesBase, final int limit) { + if (usesBase == null || usesBase.isBlank() || limit < 1) { + return List.of(); + } + final List cachedRefs = cachedRemoteRefsFor(usesBase, limit); + if (!cachedRefs.isEmpty()) { + return cachedRefs; + } + final List refs = RemoteActionProviders.latestRefs(usesBase, limit); + if (!refs.isEmpty()) { + final GitHubAction action = createGithubAction(false, usesBase + "@" + refs.get(0), usesBase + "@" + refs.get(0)) + .remoteRefs(refs) + .expiryTime(System.currentTimeMillis() + CACHE_ONE_DAY); + state.actions.put("refs:" + usesBase, action); + } + return refs; + } + + private List cachedRemoteRefsFor(final String usesBase, final int limit) { + return state.actions.values().stream() + .distinct() + .filter(action -> !action.isLocal()) + .filter(action -> remoteBase(action.usesValue()).filter(usesBase::equals).isPresent()) + .flatMap(action -> action.remoteRefs().stream()) + .distinct() + .limit(limit) + .toList(); + } + + private static Optional remoteBase(final String usesValue) { + final int refSeparator = ofNullable(usesValue).orElse("").lastIndexOf('@'); + return refSeparator > 0 ? Optional.of(usesValue.substring(0, refSeparator)) : Optional.empty(); + } + public GitHubAction reloadAsync(final Project project, final String usesValue) { return project == null ? null : ofNullable(usesValue) .map(state.actions::get) .map(oldAction -> saveNewAction(project, oldAction)) .map(action -> { threadPoolExec(project, () -> { - action.resolve(); + actionResolver.get().resolve(action); triggerSyntaxHighlightingForActiveFiles(); }); return action; @@ -112,23 +329,43 @@ public void resolveAsync(final Collection actions) { if (actions == null || actions.isEmpty()) { return; } - new Task.Backgroundable(null, "Resolving github actions", false) { + final List queuedActions = actions.stream() + .filter(Objects::nonNull) + .filter(action -> inFlightResolutions.add(action.usesValue())) + .toList(); + if (queuedActions.isEmpty()) { + return; + } + new Task.Backgroundable(null, GitHubWorkflowBundle.message("workflow.cache.progress.title"), false) { @Override public void run(@NotNull final ProgressIndicator indicator) { try { final AtomicInteger index = new AtomicInteger(0); - final double totalActions = actions.size(); + final double totalActions = queuedActions.size(); indicator.setIndeterminate(false); - actions.forEach(action -> { + queuedActions.forEach(action -> { final int i = index.incrementAndGet(); - action.resolve(); - indicator.setFraction(i / totalActions); - indicator.setText("Resolving " + (action.isAction() ? "action" : "workflow") + action.name()); + try { + indicator.setText(GitHubWorkflowBundle.message( + "workflow.cache.progress.text", + GitHubWorkflowBundle.message(action.isAction() ? "workflow.cache.kind.action" : "workflow.cache.kind.workflow"), + action.name() + )); + jitterBeforeRemoteRequest(action); + actionResolver.get().resolve(action); + if (action.isResolved()) { + action.expiryTime(System.currentTimeMillis() + (CACHE_ONE_DAY * 14)); + } + } catch (final Exception ignored) { + // Keep the cache stable when a remote action fails to answer. + } finally { + inFlightResolutions.remove(action.usesValue()); + indicator.setFraction(i / totalActions); + } }); triggerSyntaxHighlightingForActiveFiles(); } catch (final Exception e) { - // Proceed action even on issues within the progress bar - state.actions.values().forEach(GitHubActionCache::removeAction); + queuedActions.forEach(action -> inFlightResolutions.remove(action.usesValue())); triggerSyntaxHighlightingForActiveFiles(); throw e; } @@ -136,24 +373,76 @@ public void run(@NotNull final ProgressIndicator indicator) { }.queue(); } + private void resolveInBackground(final Collection actions) { + if (actions == null || actions.isEmpty()) { + return; + } + final List queuedActions = actions.stream() + .filter(Objects::nonNull) + .filter(action -> inFlightResolutions.add(action.usesValue())) + .toList(); + if (queuedActions.isEmpty()) { + return; + } + ApplicationManager.getApplication().executeOnPooledThread(() -> { + queuedActions.forEach(action -> { + try { + jitterBeforeRemoteRequest(action); + actionResolver.get().resolve(action); + if (action.isResolved()) { + action.expiryTime(System.currentTimeMillis() + (CACHE_ONE_DAY * 14)); + } + } catch (final Exception ignored) { + // Automatic refresh must never block editing because a network target misbehaved. + } finally { + inFlightResolutions.remove(action.usesValue()); + } + }); + triggerSyntaxHighlightingForActiveFiles(); + }); + } + + ActionResolver useActionResolverForTests(final ActionResolver resolver) { + return actionResolver.getAndSet(ofNullable(resolver).orElse(GitHubAction::resolve)); + } + + private static void jitterBeforeRemoteRequest(final GitHubAction action) { + if (action == null || action.isLocal()) { + return; + } + try { + Thread.sleep(ThreadLocalRandom.current().nextLong(75, 251)); + } catch (final InterruptedException interrupted) { + Thread.currentThread().interrupt(); + } + } + public static void triggerSyntaxHighlightingForActiveFiles() { - ApplicationManager.getApplication().invokeLater(() -> - Stream.of(ProjectManager.getInstance().getOpenProjects()).forEach(project -> Stream.of(FileEditorManager.getInstance(project).getSelectedFiles()).filter(VirtualFile::isValid) - .filter(virtualFile -> toPath(virtualFile).map(GitHubWorkflowHelper::isWorkflowPath).orElse(false)) - .forEach(virtualFile -> ofNullable(PsiManager.getInstance(project).findFile(virtualFile)) - .filter(PsiFile::isValid) - .ifPresent(psiFile -> { - if (DaemonCodeAnalyzer.getInstance(project).isHighlightingAvailable(psiFile)) { - DaemonCodeAnalyzer.getInstance(project).restart(psiFile); - } - }) - ) - ) + final Application application = ApplicationManager.getApplication(); + if (application.isUnitTestMode()) { + return; + } + application.invokeLater(() -> + Stream.of(ProjectManager.getInstance().getOpenProjects()).forEach(GitHubActionCache::triggerSyntaxHighlightingForActiveFiles) ); } + private static void triggerSyntaxHighlightingForActiveFiles(final Project project) { + final DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(project); + final boolean hasActiveWorkflowFile = Stream.of(FileEditorManager.getInstance(project).getSelectedFiles()) + .filter(VirtualFile::isValid) + .filter(virtualFile -> toPath(virtualFile).map(GitHubWorkflowHelper::isWorkflowPath).orElse(false)) + .map(virtualFile -> PsiManager.getInstance(project).findFile(virtualFile)) + .filter(Objects::nonNull) + .filter(PsiFile::isValid) + .anyMatch(daemonCodeAnalyzer::isHighlightingAvailable); + if (hasActiveWorkflowFile) { + daemonCodeAnalyzer.settingsChanged(); + } + } + public static void resolveActionsAsync(final Collection actions) { - threadPoolExec(ProjectManager.getInstance().getDefaultProject(), () -> getActionCache().resolveAsync(actions)); + getActionCache().resolveInBackground(actions); } public static GitHubAction reloadActionAsync(final Project project, final String usesValue) { @@ -189,8 +478,17 @@ public static Optional isUseElement(final PsiElement psiElement) { } private GitHubAction saveNewAction(final Project project, final GitHubAction oldAction) { - final boolean isLocal = !oldAction.usesValue().contains("@"); - return saveNewAction(oldAction.usesValue(), getAbsolutePath(isLocal, oldAction.usesValue(), project), isLocal, oldAction); + final boolean isLocal = isLocalUses(oldAction.usesValue()); + final String normalizedUses = normalizeUsesValue(oldAction.usesValue(), isLocal); + return saveNewAction(normalizedUses, getAbsolutePath(isLocal, normalizedUses, project), isLocal, oldAction); + } + + private GitHubAction createdOrQueuedRemote(final String usesValue, final String path, final boolean isLocal, final GitHubAction oldAction) { + final GitHubAction action = saveNewAction(usesValue, path, isLocal, oldAction); + if (!isLocal && action != null && !action.isResolved()) { + queueRefresh(action); + } + return action; } private GitHubAction saveNewAction(final String usesValue, final String path, final boolean isLocal, final GitHubAction oldAction) { @@ -217,6 +515,22 @@ private String getAbsolutePath(final boolean isLocal, final String subPath, fina .orElse(subPath); } + private static boolean isLocalUses(final String usesValue) { + final String normalized = ofNullable(usesValue).orElse("").replace('\\', '/').trim(); + return normalized.startsWith("./") + || normalized.startsWith("../") + || normalized.startsWith("/") + || normalized.matches("^[A-Za-z]:/.*"); + } + + private static String normalizeUsesValue(final String usesValue, final boolean isLocal) { + final String normalized = ofNullable(usesValue).orElse("").trim(); + if (isLocal || normalized.contains("@") || normalized.startsWith("docker://") || normalized.isBlank()) { + return normalized; + } + return normalized + "@" + DEFAULT_REMOTE_REF; + } + private static Optional getUsesString(final PsiElement psiElement) { return ofNullable(psiElement).filter(PsiElement::isValid) .flatMap(GitHubActionCache::getUsesValue) @@ -231,4 +545,64 @@ private static Optional getChildWithUsesValue(final PsiElement psiElemen return ofNullable(psiElement).filter(PsiElement::isValid).flatMap(element -> PsiElementHelper.getChild(element, FIELD_USES)).flatMap(PsiElementHelper::getText); } + private static long estimate(final String value) { + return value == null ? 0 : value.length() * 2L; + } + + private static long estimate(final GitHubAction action) { + return estimate(action.getMetaData()) + estimate(action.getInputs()) + estimate(action.getOutputs()) + estimate(action.getSecrets()); + } + + private static long estimate(final Map values) { + return values.entrySet().stream() + .mapToLong(entry -> estimate(entry.getKey()) + estimate(entry.getValue())) + .sum(); + } + + private static String encode(final String key) throws IOException { + return encode(Map.of("key", key)); + } + + private static String encode(final Map values) throws IOException { + final Properties properties = new Properties(); + properties.putAll(values); + final StringWriter writer = new StringWriter(); + properties.store(writer, null); + return Base64.getEncoder().encodeToString(writer.toString().getBytes(StandardCharsets.UTF_8)); + } + + private static Map decode(final String value) throws IOException { + final Properties properties = new Properties(); + properties.load(new StringReader(new String(Base64.getDecoder().decode(value), StandardCharsets.UTF_8))); + final Map result = new LinkedHashMap<>(); + properties.stringPropertyNames().forEach(key -> result.put(key, properties.getProperty(key))); + return result; + } + + public record CacheSummary(long total, long resolved, long remote, long expired, long suppressed) { + } + + public record CacheEntry( + String key, + String name, + String usesValue, + boolean local, + boolean resolved, + boolean expired, + boolean suppressed, + long expiryTime + ) { + static CacheEntry from(final String key, final GitHubAction action, final long now) { + return new CacheEntry( + key, + action.displayName().isBlank() ? action.name() : action.displayName(), + action.usesValue(), + action.isLocal(), + action.isResolved(), + now >= action.expiryTime(), + action.hasSuppressedWarnings(), + action.expiryTime() + ); + } + } } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizations.java b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizations.java new file mode 100644 index 0000000..034997e --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizations.java @@ -0,0 +1,143 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import org.jetbrains.plugins.github.authentication.GHAccountsUtil; +import org.jetbrains.plugins.github.authentication.accounts.GithubAccount; +import org.jetbrains.plugins.github.util.GHCompatibilityUtil; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Builds authorization candidates for GitHub REST calls. + */ +final class GitHubRequestAuthorizations { + + private static final List DEFAULT_ENV_TOKENS = List.of("GITHUB_TOKEN", "GH_TOKEN", "GITHUB_PAT"); + + static List forApiUrl(final String apiUrl, final String tokenEnvVar, final Project project) { + return forApiUrl(apiUrl, tokenEnvVar, project, System.getenv()); + } + + static List forApiUrl( + final String apiUrl, + final String tokenEnvVar, + final Project project, + final Map environment + ) { + final LinkedHashMap result = new LinkedHashMap<>(); + orderedAccountsFor(apiUrl).stream() + .map(account -> authorization(account, project)) + .flatMap(Optional::stream) + .forEach(authorization -> result.putIfAbsent(authorization.key(), authorization)); + envAuthorizations(tokenEnvVar, environment) + .forEach(authorization -> result.putIfAbsent(authorization.key(), authorization)); + result.putIfAbsent(Authorization.anonymous().key(), Authorization.anonymous()); + return List.copyOf(result.values()); + } + + static String settingsHint() { + return GitHubWorkflowBundle.message("workflow.run.auth.settings"); + } + + private static List orderedAccountsFor(final String apiUrl) { + return accounts().stream() + .sorted(Comparator + .comparingInt((GithubAccount account) -> accountPriority(account, apiUrl)) + .thenComparing(account -> account.getServer().toApiUrl()) + .thenComparing(GithubAccount::getName)) + .toList(); + } + + private static int accountPriority(final GithubAccount account, final String apiUrl) { + if (sameHost(account.getServer().toApiUrl(), apiUrl)) { + return 0; + } + return account.getServer().isGithubDotCom() ? 1 : 2; + } + + private static List accounts() { + try { + return new ArrayList<>(GHAccountsUtil.getAccounts()); + } catch (final RuntimeException ignored) { + return List.of(); + } + } + + private static Optional authorization(final GithubAccount account, final Project project) { + try { + return Optional.ofNullable(GHCompatibilityUtil.getOrRequestToken(account, project(project))) + .filter(GitHubRequestAuthorizations::hasText) + .map(token -> new Authorization(account.getName(), "Bearer " + token)); + } catch (final RuntimeException ignored) { + return Optional.empty(); + } + } + + private static List envAuthorizations(final String tokenEnvVar, final Map environment) { + final LinkedHashMap result = new LinkedHashMap<>(); + envAuthorization(tokenEnvVar, environment).ifPresent(authorization -> result.putIfAbsent(authorization.key(), authorization)); + DEFAULT_ENV_TOKENS.stream() + .filter(name -> !name.equals(tokenEnvVar)) + .map(name -> envAuthorization(name, environment)) + .flatMap(Optional::stream) + .forEach(authorization -> result.putIfAbsent(authorization.key(), authorization)); + return List.copyOf(result.values()); + } + + private static Optional envAuthorization(final String tokenEnvVar, final Map environment) { + return Optional.ofNullable(tokenEnvVar) + .map(String::trim) + .filter(GitHubRequestAuthorizations::hasText) + .flatMap(name -> Optional.ofNullable(environment.get(name)) + .filter(GitHubRequestAuthorizations::hasText) + .map(token -> new Authorization(name, "Bearer " + token))); + } + + private static Project project(final Project project) { + return Optional.ofNullable(project) + .or(() -> Optional.ofNullable(ProjectUtil.getActiveProject())) + .orElseGet(() -> ProjectManager.getInstance().getDefaultProject()); + } + + private static boolean sameHost(final String left, final String right) { + final Optional leftHost = host(left); + final Optional rightHost = host(right); + return leftHost.isPresent() && leftHost.equals(rightHost); + } + + private static Optional host(final String value) { + try { + return Optional.ofNullable(URI.create(value).getHost()) + .map(String::toLowerCase); + } catch (final RuntimeException ignored) { + return Optional.empty(); + } + } + + private static boolean hasText(final String value) { + return value != null && !value.isBlank(); + } + + record Authorization(String source, String authorizationHeader) { + + static Authorization anonymous() { + return new Authorization("anonymous", ""); + } + + boolean authenticated() { + return hasText(authorizationHeader); + } + + String key() { + return source + "|" + authorizationHeader; + } + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowBundle.java b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowBundle.java new file mode 100644 index 0000000..0b0efcb --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowBundle.java @@ -0,0 +1,39 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.DynamicBundle; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.PropertyKey; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +public final class GitHubWorkflowBundle { + + @NonNls + private static final String BUNDLE = "messages.GitHubWorkflowBundle"; + private static final DynamicBundle INSTANCE = new DynamicBundle(GitHubWorkflowBundle.class, BUNDLE); + + public static String message(@PropertyKey(resourceBundle = BUNDLE) final String key, final Object... params) { + final var locale = PluginSettings.maybeInstance().flatMap(PluginSettings::localeOverride); + if (locale.isPresent()) { + return messageFor(locale.get(), key, params); + } + return INSTANCE.getMessage(key, params); + } + + static String messageFor(final Locale locale, final @PropertyKey(resourceBundle = BUNDLE) String key, final Object... params) { + try { + final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE, locale); + final String pattern = bundle.getString(key); + return new MessageFormat(pattern, locale).format(params); + } catch (final MissingResourceException ignored) { + return INSTANCE.getMessage(key, params); + } + } + + private GitHubWorkflowBundle() { + // static bundle + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowSettingsConfigurable.java b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowSettingsConfigurable.java new file mode 100644 index 0000000..fe16e46 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowSettingsConfigurable.java @@ -0,0 +1,295 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.ide.BrowserUtil; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SearchableConfigurable; +import com.intellij.openapi.ui.Messages; +import com.intellij.ui.table.JBTable; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.DefaultTableModel; +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.nio.file.Path; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Settings UI for locale override and GitHub Action cache maintenance. + */ +public final class GitHubWorkflowSettingsConfigurable implements SearchableConfigurable { + + private static final String SUPPORT_URL = "https://github.com/sponsors/YunaBraska"; + private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(ZoneId.systemDefault()); + private static final List LOCALES = List.of( + new LocaleOption(PluginSettings.SYSTEM_LANGUAGE, "settings.language.system", true), + new LocaleOption("ar", "Arabic"), + new LocaleOption("cs", "Czech"), + new LocaleOption("de", "Deutsch"), + new LocaleOption("es", "Espaรฑol"), + new LocaleOption("fr", "Franรงais"), + new LocaleOption("hi", "Hindi"), + new LocaleOption("id", "Indonesia"), + new LocaleOption("it", "Italiano"), + new LocaleOption("ja", "ๆ—ฅๆœฌ่ชž"), + new LocaleOption("ko", "ํ•œ๊ตญ์–ด"), + new LocaleOption("nl", "Nederlands"), + new LocaleOption("pl", "Polski"), + new LocaleOption("pt-BR", "Portuguรชs (Brasil)"), + new LocaleOption("ru", "ะ ัƒััะบะธะน"), + new LocaleOption("sv", "Svenska"), + new LocaleOption("th", "เน„เธ—เธข"), + new LocaleOption("tr", "Tรผrkรงe"), + new LocaleOption("uk", "ะฃะบั€ะฐั—ะฝััŒะบะฐ"), + new LocaleOption("vi", "Tiแบฟng Viแป‡t"), + new LocaleOption("zh-CN", "็ฎ€ไฝ“ไธญๆ–‡") + ); + + private final PluginSettings settings = PluginSettings.getInstance(); + private final GitHubActionCache cache = GitHubActionCache.getActionCache(); + private final JComboBox language = new JComboBox<>(LOCALES.toArray(LocaleOption[]::new)); + private final DefaultTableModel tableModel = new DefaultTableModel(); + private final JTable table = new JBTable(tableModel); + private final JLabel summary = new JLabel(); + private @Nullable JPanel panel; + + @Override + public @NotNull String getId() { + return "github.workflow.settings"; + } + + @Override + public @Nls String getDisplayName() { + return GitHubWorkflowBundle.message("settings.displayName"); + } + + @Override + public @Nullable JComponent createComponent() { + panel = new JPanel(new BorderLayout(8, 8)); + panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + panel.add(topPanel(), BorderLayout.NORTH); + panel.add(cachePanel(), BorderLayout.CENTER); + reset(); + return panel; + } + + @Override + public boolean isModified() { + final LocaleOption option = (LocaleOption) language.getSelectedItem(); + return option != null && !Objects.equals(option.tag(), settings.languageTag()); + } + + @Override + public void apply() throws ConfigurationException { + final LocaleOption option = (LocaleOption) language.getSelectedItem(); + settings.languageTag(option == null ? PluginSettings.SYSTEM_LANGUAGE : option.tag()); + reloadTable(); + GitHubActionCache.triggerSyntaxHighlightingForActiveFiles(); + } + + @Override + public void reset() { + selectLanguage(settings.languageTag()); + reloadTable(); + } + + @Override + public void disposeUIResources() { + panel = null; + } + + private JPanel topPanel() { + final JPanel result = new JPanel(new GridBagLayout()); + final GridBagConstraints label = new GridBagConstraints(); + label.gridx = 0; + label.gridy = 0; + label.anchor = GridBagConstraints.WEST; + label.insets = new Insets(0, 0, 0, 8); + result.add(new JLabel(GitHubWorkflowBundle.message("settings.language.label")), label); + + final GridBagConstraints combo = new GridBagConstraints(); + combo.gridx = 1; + combo.gridy = 0; + combo.weightx = 1; + combo.fill = GridBagConstraints.HORIZONTAL; + result.add(language, combo); + + final JButton support = new JButton(randomSupportLine()); + support.setToolTipText(GitHubWorkflowBundle.message("settings.support.tooltip")); + support.addActionListener(event -> BrowserUtil.browse(SUPPORT_URL)); + final GridBagConstraints supportConstraints = new GridBagConstraints(); + supportConstraints.gridx = 2; + supportConstraints.gridy = 0; + supportConstraints.insets = new Insets(0, 8, 0, 0); + result.add(support, supportConstraints); + return result; + } + + private JPanel cachePanel() { + tableModel.setColumnIdentifiers(new Object[]{ + GitHubWorkflowBundle.message("settings.cache.column.key"), + GitHubWorkflowBundle.message("settings.cache.column.name"), + GitHubWorkflowBundle.message("settings.cache.column.kind"), + GitHubWorkflowBundle.message("settings.cache.column.state"), + GitHubWorkflowBundle.message("settings.cache.column.expires") + }); + table.setSelectionMode(javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + final JPanel result = new JPanel(new BorderLayout(4, 4)); + result.setBorder(BorderFactory.createTitledBorder(GitHubWorkflowBundle.message("settings.cache.title"))); + result.add(summary, BorderLayout.NORTH); + result.add(new JScrollPane(table), BorderLayout.CENTER); + result.add(cacheButtons(), BorderLayout.SOUTH); + return result; + } + + private JPanel cacheButtons() { + final JPanel result = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0)); + addButton(result, "settings.cache.refresh", this::reloadTable); + addButton(result, "settings.cache.deleteSelected", this::deleteSelected); + addButton(result, "settings.cache.deleteAll", this::deleteAll); + addButton(result, "settings.cache.export", this::exportCache); + addButton(result, "settings.cache.import", this::importCache); + return result; + } + + private void addButton(final JPanel panel, final String key, final Runnable action) { + final JButton button = new JButton(GitHubWorkflowBundle.message(key)); + button.addActionListener(event -> action.run()); + panel.add(button); + } + + private void reloadTable() { + tableModel.setRowCount(0); + cache.entries().forEach(entry -> tableModel.addRow(new Object[]{ + entry.key(), + entry.name(), + entry.local() ? GitHubWorkflowBundle.message("settings.cache.kind.local") : GitHubWorkflowBundle.message("settings.cache.kind.remote"), + stateText(entry), + entry.expiryTime() <= 0 ? "" : DATE_TIME.format(Instant.ofEpochMilli(entry.expiryTime())) + })); + final GitHubActionCache.CacheSummary cacheSummary = cache.summary(); + final long cacheKb = Math.max(0, (cache.estimatedSizeBytes() + 1023) / 1024); + summary.setText(GitHubWorkflowBundle.message( + "settings.cache.summary", + cacheSummary.total(), + cacheSummary.resolved(), + cacheSummary.remote(), + cacheSummary.expired(), + cacheSummary.suppressed(), + cacheKb + )); + summary.setToolTipText(null); + } + + private String stateText(final GitHubActionCache.CacheEntry entry) { + if (entry.suppressed()) { + return GitHubWorkflowBundle.message("settings.cache.state.suppressed"); + } + if (entry.expired()) { + return GitHubWorkflowBundle.message("settings.cache.state.expired"); + } + return entry.resolved() + ? GitHubWorkflowBundle.message("settings.cache.state.resolved") + : GitHubWorkflowBundle.message("settings.cache.state.pending"); + } + + private void deleteSelected() { + final int[] rows = table.getSelectedRows(); + if (rows.length == 0) { + Messages.showInfoMessage(panel, GitHubWorkflowBundle.message("settings.cache.noneSelected"), getDisplayName()); + return; + } + final List keys = Arrays.stream(rows) + .map(table::convertRowIndexToModel) + .mapToObj(row -> String.valueOf(tableModel.getValueAt(row, 0))) + .toList(); + cache.removeAll(keys); + reloadTable(); + Messages.showInfoMessage(panel, GitHubWorkflowBundle.message("settings.cache.deleteSelected.done", keys.size()), getDisplayName()); + } + + private void deleteAll() { + final int choice = Messages.showYesNoDialog(panel, GitHubWorkflowBundle.message("settings.cache.deleteAll.confirm"), getDisplayName(), null); + if (choice != Messages.YES) { + return; + } + cache.clear(); + reloadTable(); + Messages.showInfoMessage(panel, GitHubWorkflowBundle.message("settings.cache.deleteAll.done"), getDisplayName()); + } + + private void exportCache() { + final JFileChooser chooser = new JFileChooser(); + chooser.setSelectedFile(new java.io.File("github-workflow-cache.txt")); + if (chooser.showSaveDialog(panel) != JFileChooser.APPROVE_OPTION) { + return; + } + try { + final GitHubActionCache.CacheSummary exported = cache.exportCache(chooser.getSelectedFile().toPath()); + Messages.showInfoMessage(panel, GitHubWorkflowBundle.message("settings.cache.export.done", exported.total()), getDisplayName()); + } catch (final Exception exception) { + Messages.showErrorDialog(panel, exception.getMessage(), getDisplayName()); + } + } + + private void importCache() { + final JFileChooser chooser = new JFileChooser(); + if (chooser.showOpenDialog(panel) != JFileChooser.APPROVE_OPTION) { + return; + } + try { + cache.importCache(Path.of(chooser.getSelectedFile().getPath())); + reloadTable(); + Messages.showInfoMessage(panel, GitHubWorkflowBundle.message("settings.cache.import.done"), getDisplayName()); + } catch (final Exception exception) { + Messages.showErrorDialog(panel, exception.getMessage(), getDisplayName()); + } + } + + private void selectLanguage(final String tag) { + for (final LocaleOption option : LOCALES) { + if (Objects.equals(option.tag(), tag)) { + language.setSelectedItem(option); + return; + } + } + language.setSelectedIndex(0); + } + + private static String randomSupportLine() { + final int index = ThreadLocalRandom.current().nextInt(3); + return GitHubWorkflowBundle.message("settings.support.line." + index); + } + + private record LocaleOption(String tag, String label, boolean bundleKey) { + private LocaleOption(final String tag, final String label) { + this(tag, label, false); + } + + @Override + public String toString() { + return bundleKey ? GitHubWorkflowBundle.message(label) : label; + } + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java b/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java index 7461eef..1ab6319 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java @@ -1,14 +1,17 @@ package com.github.yunabraska.githubworkflow.services; import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; +import com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper; import com.github.yunabraska.githubworkflow.model.IconRenderer; +import com.github.yunabraska.githubworkflow.model.NodeIcon; import com.github.yunabraska.githubworkflow.model.SimpleElement; import com.github.yunabraska.githubworkflow.model.SyntaxAnnotation; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.openapi.util.Key; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.impl.source.tree.LeafPsiElement; @@ -16,10 +19,11 @@ import org.jetbrains.yaml.psi.YAMLKeyValue; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; @@ -29,24 +33,36 @@ import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.*; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.deleteElementAction; import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.getFirstChild; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.replaceAction; +import static com.github.yunabraska.githubworkflow.helper.HighlightAnnotatorHelper.simpleTextRange; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getAllElements; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.goToDeclarationString; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParent; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentJob; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentStep; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getText; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getTextElement; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.parseEnvVariables; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.parseOutputVariables; import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.toYAMLKeyValue; import static com.github.yunabraska.githubworkflow.logic.Action.highLightAction; import static com.github.yunabraska.githubworkflow.logic.Action.highlightActionInput; import static com.github.yunabraska.githubworkflow.logic.Envs.highLightEnvs; +import static com.github.yunabraska.githubworkflow.logic.GitHub.highLightGitea; import static com.github.yunabraska.githubworkflow.logic.GitHub.highLightGitHub; import static com.github.yunabraska.githubworkflow.logic.Inputs.highLightInputs; +import static com.github.yunabraska.githubworkflow.logic.JobContext.highlightJob; import static com.github.yunabraska.githubworkflow.logic.Jobs.highLightJobs; +import static com.github.yunabraska.githubworkflow.logic.Matrix.highlightMatrix; import static com.github.yunabraska.githubworkflow.logic.Needs.highlightNeeds; import static com.github.yunabraska.githubworkflow.logic.Runner.highlightRunner; import static com.github.yunabraska.githubworkflow.logic.Secrets.highLightSecrets; import static com.github.yunabraska.githubworkflow.logic.Steps.highlightSteps; +import static com.github.yunabraska.githubworkflow.logic.Strategy.highlightStrategy; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_ENV; import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_TEXT_VARIABLE; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.RELOAD; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.SUPPRESS_ON; import static com.intellij.lang.annotation.HighlightSeverity.INFORMATION; import static java.util.Optional.ofNullable; @@ -58,14 +74,18 @@ public void annotate(@NotNull final PsiElement psiElement, @NotNull final Annota if (psiElement.isValid()) { processPsiElement(holder, psiElement); variableElementHandler(holder, psiElement); + highlightVariableReferences(holder, psiElement); + highlightDeclarations(holder, psiElement); highlightRunOutputs(holder, psiElement); + highlightRunnerVariables(holder, psiElement); + highlightScalarLiterals(holder, psiElement); + validateWorkflowSyntax(holder, psiElement); // HIGHLIGHT ACTION INPUTS highlightActionInput(holder, psiElement); highlightNeeds(holder, psiElement); } } - //TODO: handle single elements instead of bulk updates for more reliability public static void processPsiElement(final AnnotationHolder holder, final PsiElement psiElement) { toYAMLKeyValue(psiElement).ifPresent(element -> { switch (element.getKeyText()) { @@ -85,16 +105,310 @@ private static void highlightRunOutputs(final AnnotationHolder holder, final Psi .map(LeafPsiElement.class::cast) .filter(element -> PsiElementHelper.getParent(element, FIELD_RUN).isPresent()) .ifPresent(element -> Stream.of( - parseEnvVariables(element), - parseOutputVariables(element) + parseEnvVariables(element).stream().map(variable -> withIcon(variable, ICON_ENV)).toList(), + parseOutputVariables(element).stream().map(variable -> withIcon(variable, ICON_TEXT_VARIABLE)).toList() ).flatMap(Collection::stream).collect(Collectors.groupingBy(SimpleElement::startIndexOffset)).forEach((integer, elements) -> ofNullable(getFirstChild(elements)).ifPresent(lineElement -> holder .newSilentAnnotation(INFORMATION) .range(lineElement.range()) - .gutterIconRenderer(new IconRenderer(null, element, ICON_TEXT_VARIABLE)) + .textAttributes(WorkflowTextAttributes.DECLARATION) + .gutterIconRenderer(new IconRenderer(null, element, lineElement.icon())) .create() ))); } + private static SimpleElement withIcon(final SimpleElement element, final NodeIcon icon) { + return new SimpleElement(element.key(), element.text(), element.range(), icon); + } + + private static void highlightRunnerVariables(final AnnotationHolder holder, final PsiElement psiElement) { + Optional.of(psiElement) + .filter(LeafPsiElement.class::isInstance) + .map(LeafPsiElement.class::cast) + .filter(element -> getParent(element, FIELD_RUN).isPresent()) + .ifPresent(element -> DEFAULT_VALUE_MAP.get(FIELD_ENVS).get().keySet().forEach(name -> highlightWord(holder, element, name, WorkflowTextAttributes.RUNNER_VARIABLE))); + } + + private static void highlightWord( + final AnnotationHolder holder, + final PsiElement element, + final String word, + final com.intellij.openapi.editor.colors.TextAttributesKey attributes + ) { + final String text = element.getText(); + int index = text.indexOf(word); + while (index >= 0) { + final int end = index + word.length(); + final boolean before = index == 0 || !isIdentifierChar(text.charAt(index - 1)); + final boolean after = end >= text.length() || !isIdentifierChar(text.charAt(end)); + if (before && after) { + holder.newSilentAnnotation(INFORMATION) + .range(new TextRange(element.getTextRange().getStartOffset() + index, element.getTextRange().getStartOffset() + end)) + .textAttributes(attributes) + .create(); + } + index = text.indexOf(word, end); + } + } + + private static void highlightScalarLiterals(final AnnotationHolder holder, final PsiElement psiElement) { + toYAMLKeyValue(psiElement) + .flatMap(PsiElementHelper::getTextElement) + .filter(text -> text.getText().matches("true|false|-?\\d+(?:\\.\\d+)?")) + .ifPresent(text -> holder.newSilentAnnotation(INFORMATION) + .range(text) + .textAttributes(WorkflowTextAttributes.SCALAR_LITERAL) + .create()); + } + + private static void validateWorkflowSyntax(final AnnotationHolder holder, final PsiElement psiElement) { + toYAMLKeyValue(psiElement) + .filter(HighlightAnnotator::shouldValidateWorkflowSyntax) + .ifPresent(element -> validateWorkflowKeyValue(holder, element)); + } + + private static boolean shouldValidateWorkflowSyntax(final YAMLKeyValue element) { + return GitHubWorkflowHelper.getWorkflowFile(element) + .filter(path -> GitHubWorkflowHelper.isWorkflowFile(path) || isUnitTestWorkflowFile(element)) + .isPresent(); + } + + private static boolean isUnitTestWorkflowFile(final YAMLKeyValue element) { + return ApplicationManager.getApplication().isUnitTestMode() + && PsiElementHelper.getChild(element.getContainingFile(), "runs").isEmpty(); + } + + private static void validateWorkflowKeyValue(final AnnotationHolder holder, final YAMLKeyValue element) { + final String key = element.getKeyText(); + final List path = yamlPath(element); + if (path.isEmpty()) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.topLevelKeys(), "inspection.workflow.syntax.unknownTopLevelKey"); + return; + } + if (pathMatches(path, FIELD_ON)) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.eventKeys(), "inspection.workflow.syntax.unknownEventKey"); + return; + } + if (pathMatches(path, FIELD_ON, "workflow_dispatch")) { + validateKnownKey(holder, element, mapOf(FIELD_INPUTS), "inspection.workflow.syntax.unknownTriggerKey"); + return; + } + if (pathMatches(path, FIELD_ON, "workflow_call")) { + validateKnownKey(holder, element, mapOf(FIELD_INPUTS, FIELD_OUTPUTS, FIELD_SECRETS), "inspection.workflow.syntax.unknownTriggerKey"); + return; + } + if (isChildOf(path, FIELD_ON, "workflow_dispatch", FIELD_INPUTS) + || isChildOf(path, FIELD_ON, "workflow_call", FIELD_INPUTS)) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.workflowInputPropertyKeys(), "inspection.workflow.syntax.unknownTriggerKey"); + validateWorkflowInputPropertyValue(holder, element, path); + return; + } + if (isChildOf(path, FIELD_ON, "workflow_call", FIELD_OUTPUTS)) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.workflowOutputPropertyKeys(), "inspection.workflow.syntax.unknownTriggerKey"); + return; + } + if (isChildOf(path, FIELD_ON, "workflow_call", FIELD_SECRETS)) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.workflowSecretPropertyKeys(), "inspection.workflow.syntax.unknownTriggerKey"); + if ("required".equals(key)) { + validateKnownValue(holder, element, WorkflowSyntaxSchema.booleanValues(), "inspection.workflow.syntax.unknownTriggerValue"); + } + return; + } + if (pathMatches(path, FIELD_ON, "*")) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.eventFilterKeysFor(path.get(path.size() - 1)), "inspection.workflow.syntax.unknownTriggerFilter"); + if ("types".equals(key)) { + validateKnownValue(holder, element, WorkflowSyntaxSchema.eventActivityTypesFor(path.get(1)), "inspection.workflow.syntax.unknownTriggerValue"); + } + return; + } + if (pathEndsWith(path, "permissions")) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.permissionScopes(), "inspection.workflow.syntax.unknownPermission"); + validateKnownValue(holder, element, WorkflowSyntaxSchema.permissionValuesFor(element.getKeyText()), "inspection.workflow.syntax.unknownPermissionValue"); + return; + } + if (pathMatches(path, "defaults", FIELD_RUN) || pathMatches(path, FIELD_JOBS, "*", "defaults", FIELD_RUN)) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.defaultsRunKeys(), "inspection.workflow.syntax.unknownTopLevelKey"); + return; + } + if (pathMatches(path, "concurrency") || pathMatches(path, FIELD_JOBS, "*", "concurrency")) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.concurrencyKeys(), "inspection.workflow.syntax.unknownTopLevelKey"); + return; + } + if (pathMatches(path, FIELD_JOBS, "*", FIELD_STRATEGY)) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.strategyKeys(), "inspection.workflow.syntax.unknownTopLevelKey"); + return; + } + if (pathMatches(path, FIELD_JOBS, "*", "environment")) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.environmentKeys(), "inspection.workflow.syntax.unknownTopLevelKey"); + return; + } + if (pathMatches(path, FIELD_JOBS, "*", "container")) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.containerKeys(), "inspection.workflow.syntax.unknownTopLevelKey"); + return; + } + if (pathMatches(path, FIELD_JOBS, "*", "container", "credentials")) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.credentialsKeys(), "inspection.workflow.syntax.unknownTopLevelKey"); + return; + } + if (pathMatches(path, FIELD_JOBS, "*", FIELD_SERVICES, "*")) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.serviceKeys(), "inspection.workflow.syntax.unknownTopLevelKey"); + return; + } + if (pathMatches(path, FIELD_JOBS, "*", FIELD_SERVICES, "*", "credentials")) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.credentialsKeys(), "inspection.workflow.syntax.unknownTopLevelKey"); + return; + } + if (pathMatches(path, FIELD_JOBS, "*")) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.jobKeys(), "inspection.workflow.syntax.unknownJobKey"); + return; + } + if (pathMatches(path, FIELD_JOBS, "*", FIELD_STEPS)) { + validateKnownKey(holder, element, WorkflowSyntaxSchema.stepKeys(), "inspection.workflow.syntax.unknownStepKey"); + } + } + + private static Map mapOf(final String... keys) { + final Map result = new LinkedHashMap<>(); + for (final String key : keys) { + result.put(key, key); + } + return result; + } + + private static void validateWorkflowInputPropertyValue( + final AnnotationHolder holder, + final YAMLKeyValue element, + final List path + ) { + if ("type".equals(element.getKeyText())) { + final Map allowedTypes = "workflow_call".equals(path.get(1)) + ? WorkflowSyntaxSchema.reusableWorkflowInputTypes() + : WorkflowSyntaxSchema.workflowInputTypes(); + validateKnownValue(holder, element, allowedTypes, "inspection.workflow.syntax.unknownTriggerValue"); + } + if ("required".equals(element.getKeyText())) { + validateKnownValue(holder, element, WorkflowSyntaxSchema.booleanValues(), "inspection.workflow.syntax.unknownTriggerValue"); + } + } + + private static void validateKnownKey( + final AnnotationHolder holder, + final YAMLKeyValue element, + final Map allowed, + final String messageKey + ) { + if (allowed.containsKey(element.getKeyText()) || element.getKeyText().isBlank()) { + return; + } + final TextRange range = Optional.ofNullable(element.getKey()) + .map(PsiElement::getTextRange) + .orElseGet(element::getTextRange); + final List fixes = new ArrayList<>(); + fixes.add(new SyntaxAnnotation( + GitHubWorkflowBundle.message(messageKey, element.getKeyText()), + null, + HighlightSeverity.WEAK_WARNING, + ProblemHighlightType.WEAK_WARNING, + null + )); + allowed.keySet().stream() + .map(candidate -> new SyntaxAnnotation( + GitHubWorkflowBundle.message("inspection.replace.with", candidate), + RELOAD, + HighlightSeverity.WEAK_WARNING, + ProblemHighlightType.WEAK_WARNING, + replaceAction(range, candidate) + )) + .forEach(fixes::add); + SyntaxAnnotation.createAnnotation( + element, + range, + holder, + fixes + ); + } + + private static void validateKnownValue( + final AnnotationHolder holder, + final YAMLKeyValue element, + final Map allowed, + final String messageKey + ) { + final String value = PsiElementHelper.getText(element).orElse(""); + if (allowed.isEmpty() + || value.isBlank() + || value.startsWith("${{") + || !value.matches("[A-Za-z0-9_-]+") + || allowed.containsKey(value)) { + return; + } + PsiElementHelper.getTextElement(element).ifPresent(valueElement -> { + final TextRange range = valueElement.getTextRange(); + final List fixes = new ArrayList<>(); + fixes.add(new SyntaxAnnotation( + GitHubWorkflowBundle.message(messageKey, value), + null, + HighlightSeverity.WEAK_WARNING, + ProblemHighlightType.WEAK_WARNING, + null + )); + allowed.keySet().stream() + .map(candidate -> new SyntaxAnnotation( + GitHubWorkflowBundle.message("inspection.replace.with", candidate), + RELOAD, + HighlightSeverity.WEAK_WARNING, + ProblemHighlightType.WEAK_WARNING, + replaceAction(range, candidate) + )) + .forEach(fixes::add); + SyntaxAnnotation.createAnnotation( + element, + range, + holder, + fixes + ); + }); + } + + private static List yamlPath(final YAMLKeyValue element) { + final List result = new ArrayList<>(); + PsiElement current = element.getParent(); + while (current != null && current != element.getContainingFile()) { + if (current instanceof YAMLKeyValue keyValue) { + result.add(0, keyValue.getKeyText()); + } + current = current.getParent(); + } + return result; + } + + private static boolean isChildOf(final List path, final String... expectedParent) { + if (path.size() != expectedParent.length + 1) { + return false; + } + for (int index = 0; index < expectedParent.length; index++) { + if (!expectedParent[index].equals(path.get(index))) { + return false; + } + } + return true; + } + + private static boolean pathMatches(final List path, final String... pattern) { + if (path.size() != pattern.length) { + return false; + } + for (int index = 0; index < pattern.length; index++) { + if (!"*".equals(pattern[index]) && !pattern[index].equals(path.get(index))) { + return false; + } + } + return true; + } + + private static boolean pathEndsWith(final List path, final String expected) { + return !path.isEmpty() && expected.equals(path.get(path.size() - 1)); + } + private static void outputsHandler(final AnnotationHolder holder, final PsiElement psiElement) { getParentJob(psiElement).ifPresent(job -> { final List outputs = PsiElementHelper.getChildren(psiElement).stream().toList(); @@ -105,12 +419,13 @@ private static void outputsHandler(final AnnotationHolder holder, final PsiEleme .orElseGet(Collections::emptyList); outputs.stream().filter(output -> { final String outputKey = output.getKeyText(); - return workflowOutputs.stream().noneMatch( - wo -> wo.contains(FIELD_JOBS + "." + job.getKeyText() + "." + FIELD_OUTPUTS + "." + outputKey + " ") || wo.contains(FIELD_JOBS + "." + job.getKeyText() + "." + FIELD_OUTPUTS + "." + outputKey + "}") - ) && !workflowText.contains(FIELD_NEEDS + "." + job.getKeyText() + "." + FIELD_OUTPUTS + "." + outputKey + " ") && !workflowText.contains(FIELD_NEEDS + "." + job.getKeyText() + "." + FIELD_OUTPUTS + "." + outputKey + "}"); + final String reusableOutputReference = FIELD_JOBS + "." + job.getKeyText() + "." + FIELD_OUTPUTS + "." + outputKey; + final String needsOutputReference = FIELD_NEEDS + "." + job.getKeyText() + "." + FIELD_OUTPUTS + "." + outputKey; + return workflowOutputs.stream().noneMatch(value -> containsOutputReference(value, reusableOutputReference)) + && !containsOutputReference(workflowText, needsOutputReference); }).forEach(output -> new SyntaxAnnotation( - "Unused [" + output.getKeyText() + "]", - null, + GitHubWorkflowBundle.message("inspection.output.unused", output.getKeyText()), + SUPPRESS_ON, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.LIKE_UNUSED_SYMBOL, deleteElementAction(output.getTextRange()), @@ -120,12 +435,40 @@ private static void outputsHandler(final AnnotationHolder holder, final PsiEleme }); } + private static boolean containsOutputReference(final String text, final String reference) { + int index = ofNullable(text).orElse("").indexOf(reference); + while (index >= 0) { + final int end = index + reference.length(); + if (end >= text.length() || !isIdentifierChar(text.charAt(end))) { + return true; + } + index = text.indexOf(reference, end); + } + return false; + } + @NotNull public static Predicate isElementWithVariables(final YAMLKeyValue parentIf) { return element -> ofNullable(parentIf) .or(() -> getParent(element, FIELD_RUN)) .or(() -> getParent(element, FIELD_ID)) .or(() -> getParent(element, "name")) + .or(() -> getParent(element, "run-name")) + .or(() -> getParent(element, "runs-on")) + .or(() -> getParent(element, "concurrency")) + .or(() -> getParent(element, "group").filter(group -> getParent(group, "concurrency").isPresent())) + .or(() -> getParent(element, "default").filter(defaultValue -> getParent(defaultValue, FIELD_INPUTS).isPresent())) + .or(() -> getParent(element, "credentials")) + .or(() -> getParent(element, "environment")) + .or(() -> getParent(element, "fail-fast").filter(failFast -> getParent(failFast, FIELD_STRATEGY).isPresent())) + .or(() -> getParent(element, "max-parallel").filter(maxParallel -> getParent(maxParallel, FIELD_STRATEGY).isPresent())) + .or(() -> getParent(element, "shell").filter(shell -> getParent(shell, "defaults").isPresent())) + .or(() -> getParent(element, "container").filter(container -> getParent(container, "jobs").isPresent())) + .or(() -> getParent(element, "url").filter(url -> getParent(url, "environment").isPresent())) + .or(() -> getParent(element, "timeout-minutes")) + .or(() -> getParent(element, "continue-on-error")) + .or(() -> getParent(element, "working-directory")) + .or(() -> getParent(element, "image").filter(image -> getParent(image, "container").isPresent() || getParent(image, "services").isPresent())) .or(() -> getParent(element, "value").isPresent() ? getParent(element, FIELD_OUTPUTS) : Optional.empty()) .or(() -> getParent(element, FIELD_WITH)) .or(() -> getParent(element, FIELD_ENVS)) @@ -133,63 +476,115 @@ public static Predicate isElementWithVariables(final YAMLKeyValue pa .isPresent(); } - public static final Key VARIABLE_ELEMENTS = new Key<>("com.github.yunabraska.githubworkflow.VariableElements"); - @NotNull public static List toSimpleElements(final PsiElement element) { - return Arrays.stream(element.getText().split("\\R")) - .map(String::trim).filter(PsiElementHelper::hasText) - // EXCLUDE COMMENT LINES - .filter(s -> !s.startsWith("#")) - .flatMap(s -> findDottedExpressions(s).stream()) - .toList(); + if (getParent(element, FIELD_RUN).isPresent()) { + return toSimpleElementsInExpressions(element); + } + final List result = new ArrayList<>(); + final String text = element.getText(); + int lineStart = 0; + while (lineStart <= text.length()) { + int lineEnd = text.indexOf('\n', lineStart); + if (lineEnd < 0) { + lineEnd = text.length(); + } + final String line = text.substring(lineStart, lineEnd); + if (PsiElementHelper.hasText(line) && !line.trim().startsWith("#")) { + final int currentLineStart = lineStart; + findDottedExpressions(line).stream() + .map(expression -> new SimpleElement( + expression.text(), + new TextRange( + currentLineStart + expression.range().getStartOffset(), + currentLineStart + expression.range().getEndOffset() + ) + )) + .forEach(result::add); + } + if (lineEnd == text.length()) { + break; + } + lineStart = lineEnd + 1; + } + return result; + } + + @NotNull + private static List toSimpleElementsInExpressions(final PsiElement element) { + final List result = new ArrayList<>(); + final String text = element.getText(); + int index = 0; + while (index < text.length()) { + final int expressionStart = text.indexOf("${{", index); + if (expressionStart < 0) { + break; + } + final int bodyStart = expressionStart + 3; + final int expressionEnd = text.indexOf("}}", bodyStart); + if (expressionEnd < 0) { + break; + } + final String body = text.substring(bodyStart, expressionEnd); + findDottedExpressions(body).stream() + .map(expression -> new SimpleElement( + expression.text(), + new TextRange( + bodyStart + expression.range().getStartOffset(), + bodyStart + expression.range().getEndOffset() + ) + )) + .forEach(result::add); + index = expressionEnd + 2; + } + return result; } @NotNull public static SimpleElement[] splitToElements(final SimpleElement simpleElement) { - final AtomicInteger start = new AtomicInteger(simpleElement.range().getStartOffset()); - return Arrays.stream(simpleElement.text().split("\\.")) - .map(s -> s.replace("{", "")) - .map(s -> s.replace("}", "")) - .map(String::trim) - .filter(PsiElementHelper::hasText) - .map(part -> { - final int length = part.length(); - final TextRange range = new TextRange(start.get(), start.get() + length); - start.addAndGet(length + 1); - return new SimpleElement(part, range); - }) - .toArray(SimpleElement[]::new); + final List result = new ArrayList<>(); + final AtomicInteger index = new AtomicInteger(0); + while (index.get() < simpleElement.text().length()) { + if (isIdentifierChar(simpleElement.text().charAt(index.get()))) { + result.add(readIdentifier(simpleElement, index)); + } else { + index.incrementAndGet(); + } + } + return result.toArray(SimpleElement[]::new); } public static List findDottedExpressions(final String text) { final List elements = new ArrayList<>(); - final StringBuilder currentElement = new StringBuilder(); - int elementStart = -1; - char previousChar = ' '; - - for (int i = 0; i < text.length(); i++) { - final char ch = text.charAt(i); - final boolean letterOrDigit = Character.isLetterOrDigit(ch); - - - if (elementStart == -1 && letterOrDigit && (Character.isWhitespace(previousChar) || previousChar != '{')) { - // START - elementStart = i; - currentElement.setLength(0); - currentElement.append(ch); - } else if (elementStart != -1 && (letterOrDigit || ch == '_' || ch == '-' || ch == '.')) { - // MIDDLE - currentElement.append(ch); - // LAST ITEM - if (i + 1 == text.length()) { - elementStart = validateAndAddElement(currentElement, elements, elementStart, i); + int index = 0; + while (index < text.length()) { + if (!isContextStart(text, index)) { + index++; + continue; + } + final int start = index; + boolean hasSeparator = false; + index = readIdentifierEnd(text, index); + while (index < text.length()) { + final char current = text.charAt(index); + if (current == '.') { + hasSeparator = true; + index++; + index = readIdentifierEnd(text, index); + } else if (current == '[') { + final int closingBracket = findClosingBracket(text, index); + if (closingBracket < 0) { + break; + } + hasSeparator = true; + index = closingBracket + 1; + } else { + break; } - } else if (elementStart != -1 && (i + 1 == text.length() || Character.isWhitespace(text.charAt(i)) || text.charAt(i + 1) == '}')) { - // END - elementStart = validateAndAddElement(currentElement, elements, elementStart, i); } - previousChar = ch; + if (hasSeparator && start < index) { + elements.add(new SimpleElement(text.substring(start, index), new TextRange(start, index))); + } } return elements; } @@ -208,7 +603,11 @@ private static void variableElementHandler(final AnnotationHolder holder, final highLightSecrets(holder, psiElement, element, simpleElement, parts, parentIf.orElse(null)); case FIELD_ENVS -> highLightEnvs(holder, element, parts); case FIELD_GITHUB -> highLightGitHub(holder, element, parts); + case FIELD_GITEA -> highLightGitea(holder, element, parts); + case FIELD_JOB -> highlightJob(holder, element, parts); case FIELD_RUNNER -> highlightRunner(holder, element, parts); + case FIELD_MATRIX -> highlightMatrix(holder, element, parts); + case FIELD_STRATEGY -> highlightStrategy(holder, element, parts); case FIELD_STEPS -> highlightSteps(holder, element, parts); case FIELD_JOBS -> highLightJobs(holder, element, parts); case FIELD_NEEDS -> highlightNeeds(holder, element, parts); @@ -220,13 +619,108 @@ private static void variableElementHandler(final AnnotationHolder holder, final ); } - private static int validateAndAddElement(final StringBuilder currentElement, final List elements, int elementStart, final int i) { - if (!currentElement.isEmpty() && currentElement.length() > 1 && currentElement.toString().contains(".")) { - elements.add(new SimpleElement(currentElement.toString(), new TextRange(elementStart, i))); + private static void highlightVariableReferences(final AnnotationHolder holder, final PsiElement psiElement) { + Optional.of(psiElement) + .filter(PsiElementHelper::isTextElement) + .ifPresent(element -> { + toSimpleElements(element).stream() + .flatMap(source -> Stream.of(splitToElements(source))) + .forEach(segment -> holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(simpleTextRange(element, segment)) + .textAttributes(WorkflowTextAttributes.VARIABLE_REFERENCE) + .create()); + ExpressionReferenceTargets.resolve(element).forEach(target -> { + final String tooltip = goToDeclarationString(); + holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(simpleTextRange(element, target.segment())) + .textAttributes(WorkflowTextAttributes.VARIABLE_REFERENCE) + .create(); + holder.newAnnotation(HighlightSeverity.INFORMATION, tooltip) + .range(simpleTextRange(element, target.segment())) + .textAttributes(DefaultLanguageHighlighterColors.HIGHLIGHTED_REFERENCE) + .tooltip(tooltip) + .create(); + }); + }); + } + + private static void highlightDeclarations(final AnnotationHolder holder, final PsiElement psiElement) { + toYAMLKeyValue(psiElement).ifPresent(element -> { + highlightJobDeclaration(holder, element); + highlightStepDeclaration(holder, element); + }); + } + + private static void highlightJobDeclaration(final AnnotationHolder holder, final YAMLKeyValue element) { + getParent(element, FIELD_JOBS) + .filter(jobs -> isDirectChildOf(element, jobs)) + .flatMap(job -> ofNullable(element.getKey())) + .ifPresent(key -> holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(key) + .textAttributes(WorkflowTextAttributes.DECLARATION) + .create()); + } + + private static boolean isDirectChildOf(final YAMLKeyValue child, final YAMLKeyValue parent) { + PsiElement current = child.getParent(); + while (current != null && current != parent) { + if (current instanceof YAMLKeyValue) { + return false; + } + current = current.getParent(); + } + return current == parent; + } + + private static void highlightStepDeclaration(final AnnotationHolder holder, final YAMLKeyValue element) { + if (FIELD_ID.equals(element.getKeyText()) && getParentStep(element).isPresent()) { + getTextElement(element).ifPresent(text -> holder.newSilentAnnotation(HighlightSeverity.INFORMATION) + .range(text) + .textAttributes(WorkflowTextAttributes.DECLARATION) + .create()); } - elementStart = -1; - currentElement.setLength(0); - return elementStart; + } + + private static SimpleElement readIdentifier(final SimpleElement simpleElement, final AtomicInteger index) { + final int start = index.get(); + index.set(readIdentifierEnd(simpleElement.text(), start)); + return new SimpleElement( + simpleElement.text().substring(start, index.get()), + new TextRange(simpleElement.range().getStartOffset() + start, simpleElement.range().getStartOffset() + index.get()) + ); + } + + private static int readIdentifierEnd(final String text, final int start) { + int index = start; + while (index < text.length() && isIdentifierChar(text.charAt(index))) { + index++; + } + return index; + } + + private static int findClosingBracket(final String text, final int start) { + int index = start + 1; + while (index < text.length()) { + if (text.charAt(index) == ']') { + return index; + } + index++; + } + return -1; + } + + private static boolean isContextStart(final String text, final int start) { + return List.of(FIELD_INPUTS, FIELD_SECRETS, FIELD_ENVS, FIELD_GITHUB, FIELD_GITEA, FIELD_JOB, FIELD_RUNNER, FIELD_MATRIX, FIELD_STRATEGY, FIELD_STEPS, FIELD_JOBS, FIELD_NEEDS, FIELD_VARS) + .stream() + .anyMatch(context -> text.startsWith(context, start) && hasContextSeparator(text, start + context.length())); + } + + private static boolean hasContextSeparator(final String text, final int index) { + return index < text.length() && (text.charAt(index) == '.' || text.charAt(index) == '['); + } + + private static boolean isIdentifierChar(final char character) { + return Character.isLetterOrDigit(character) || character == '_' || character == '-'; } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitter.java b/src/main/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitter.java index 0a05de2..f632092 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitter.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitter.java @@ -1,12 +1,11 @@ package com.github.yunabraska.githubworkflow.services; import com.intellij.ide.BrowserUtil; -import com.intellij.ide.plugins.IdeaPluginDescriptor; -import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.diagnostic.ErrorReportSubmitter; import com.intellij.openapi.diagnostic.IdeaLoggingEvent; import com.intellij.openapi.diagnostic.SubmittedReportInfo; +import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Consumer; @@ -29,7 +28,7 @@ final class PluginErrorReportSubmitter extends ErrorReportSubmitter { @NotNull @Override public String getReportActionText() { - return "Report Exception"; + return GitHubWorkflowBundle.message("error.report.action"); } @Override @@ -37,6 +36,11 @@ public boolean submit(final IdeaLoggingEvent @NotNull [] events, @Nullable final String additionalInfo, @NotNull final Component parentComponent, @NotNull final Consumer consumer) { + if (events.length == 0) { + consumer.consume(new SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.FAILED)); + return false; + } + final IdeaLoggingEvent event = events[0]; final String throwableText = event.getThrowableText(); @@ -50,24 +54,24 @@ public boolean submit(final IdeaLoggingEvent @NotNull [] events, .ifPresent(sb::append); sb.append("&body="); - sb.append(URLEncoder.encode("\n\n### Description\n", UTF_8)); + sb.append(URLEncoder.encode("\n\n### " + GitHubWorkflowBundle.message("error.report.description") + "\n", UTF_8)); sb.append(URLEncoder.encode(StringUtil.defaultIfEmpty(additionalInfo, ""), UTF_8)); - sb.append(URLEncoder.encode("\n\n### Steps to Reproduce\n", UTF_8)); - sb.append(URLEncoder.encode("Please provide code sample if applicable", UTF_8)); + sb.append(URLEncoder.encode("\n\n### " + GitHubWorkflowBundle.message("error.report.steps") + "\n", UTF_8)); + sb.append(URLEncoder.encode(GitHubWorkflowBundle.message("error.report.sample"), UTF_8)); - sb.append(URLEncoder.encode("\n\n### Message\n", UTF_8)); + sb.append(URLEncoder.encode("\n\n### " + GitHubWorkflowBundle.message("error.report.message") + "\n", UTF_8)); sb.append(URLEncoder.encode(StringUtil.defaultIfEmpty(event.getMessage(), ""), UTF_8)); - sb.append(URLEncoder.encode("\n\n### Runtime Information\n", UTF_8)); - final IdeaPluginDescriptor descriptor = PluginManagerCore.getPlugin(getPluginDescriptor().getPluginId()); - assert descriptor != null; - sb.append(URLEncoder.encode("Plugin version : " + descriptor.getVersion() + "\n", UTF_8)); - sb.append(URLEncoder.encode("IDE: " + ApplicationInfo.getInstance().getFullApplicationName() + - " (" + ApplicationInfo.getInstance().getBuild().asString() + ")\n", UTF_8)); - sb.append(URLEncoder.encode("OS: " + SystemInfo.getOsNameAndVersion(), UTF_8)); + sb.append(URLEncoder.encode("\n\n### " + GitHubWorkflowBundle.message("error.report.runtime") + "\n", UTF_8)); + final PluginDescriptor descriptor = getPluginDescriptor(); + sb.append(URLEncoder.encode(GitHubWorkflowBundle.message("error.report.pluginVersion", descriptor.getVersion()) + "\n", UTF_8)); + final String ideInfo = ApplicationInfo.getInstance().getFullApplicationName() + + " (" + ApplicationInfo.getInstance().getBuild().asString() + ")"; + sb.append(URLEncoder.encode(GitHubWorkflowBundle.message("error.report.ide", ideInfo) + "\n", UTF_8)); + sb.append(URLEncoder.encode(GitHubWorkflowBundle.message("error.report.os", SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION), UTF_8)); - sb.append(URLEncoder.encode("\n\n### Stacktrace\n", UTF_8)); + sb.append(URLEncoder.encode("\n\n### " + GitHubWorkflowBundle.message("error.report.stacktrace") + "\n", UTF_8)); sb.append(URLEncoder.encode("```\n", UTF_8)); sb.append(URLEncoder.encode(throwableText, UTF_8)); sb.append(URLEncoder.encode("```\n", UTF_8)); diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/PluginSettings.java b/src/main/java/com/github/yunabraska/githubworkflow/services/PluginSettings.java new file mode 100644 index 0000000..d638dde --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/PluginSettings.java @@ -0,0 +1,64 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; +import java.util.Optional; + +/** + * Persistent user settings for the GitHub Workflow plugin. + */ +@State(name = "GitHubWorkflowPluginSettings", storages = {@Storage("githubWorkflowPluginSettings.xml")}) +public final class PluginSettings implements PersistentStateComponent { + + public static final String SYSTEM_LANGUAGE = ""; + + public static final class StateData { + public String languageTag = SYSTEM_LANGUAGE; + } + + private final StateData state = new StateData(); + + public static PluginSettings getInstance() { + return ApplicationManager.getApplication().getService(PluginSettings.class); + } + + public static Optional maybeInstance() { + try { + return Optional.ofNullable(ApplicationManager.getApplication()) + .map(application -> application.getService(PluginSettings.class)); + } catch (final RuntimeException ignored) { + return Optional.empty(); + } + } + + @Override + public @Nullable StateData getState() { + return state; + } + + @Override + public void loadState(@NotNull final StateData state) { + XmlSerializerUtil.copyBean(state, this.state); + } + + public String languageTag() { + return state.languageTag == null ? SYSTEM_LANGUAGE : state.languageTag; + } + + public PluginSettings languageTag(final String languageTag) { + state.languageTag = languageTag == null ? SYSTEM_LANGUAGE : languageTag.trim(); + return this; + } + + public Optional localeOverride() { + final String languageTag = languageTag(); + return languageTag.isBlank() ? Optional.empty() : Optional.of(Locale.forLanguageTag(languageTag.replace('_', '-'))); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/ProjectStartup.java b/src/main/java/com/github/yunabraska/githubworkflow/services/ProjectStartup.java index 87c4c09..3a59a74 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/ProjectStartup.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/ProjectStartup.java @@ -6,8 +6,7 @@ import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; import com.github.yunabraska.githubworkflow.model.GitHubAction; import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.ex.ActionManagerEx; -import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.project.DumbService; @@ -16,6 +15,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiManager; +import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.messages.MessageBusConnection; import kotlin.Unit; import kotlin.coroutines.Continuation; @@ -24,9 +24,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_USES; @@ -51,7 +51,7 @@ public Object execute(@NotNull final Project project, @NotNull final Continuatio asyncInitAllActions(project, openedFile); } - final MessageBusConnection connection = project.getMessageBus().connect(); + final MessageBusConnection connection = project.getMessageBus().connect(listenerDisposable); connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { @Override public void fileOpened(@NotNull final FileEditorManager source, @NotNull final VirtualFile file) { @@ -61,48 +61,48 @@ public void fileOpened(@NotNull final FileEditorManager source, @NotNull final V // CLEANUP ACTION CACHE SCHEDULER - final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - executorService.scheduleAtFixedRate(() -> getActionCache().cleanUp(), 0, 30, TimeUnit.MINUTES); + final ScheduledFuture cleanupTask = AppExecutorUtil.getAppScheduledExecutorService() + .scheduleWithFixedDelay(() -> getActionCache().cleanUp(), 0, 30, TimeUnit.MINUTES); // Ensure the executor is shut down when the project is disposed Disposer.register(ListenerService.getInstance(project), () -> { - executorService.shutdown(); - unregisterAction(project); + cleanupTask.cancel(false); }); return null; } - private void unregisterAction(final Project project) { - final ActionManagerEx actionManager = ActionManagerEx.getInstanceEx(); - for (final String oldId : actionManager.getActionIdList("GHWP_" + project.getLocationHash())) { - actionManager.unregisterAction(oldId); - } - } - private static void asyncInitAllActions(final Project project, final VirtualFile virtualFile) { final Runnable task = () -> { - if (virtualFile != null && (GitHubWorkflowHelper.isWorkflowPath(toPath(virtualFile.getPath())))) { - final List actions = new ArrayList<>(); - // READ CONTEXT - ApplicationManager.getApplication().runReadAction(() -> Optional.of(PsiManager.getInstance(project)) - .map(psiManager -> psiManager.findFile(virtualFile)) - .map(psiFile -> PsiElementHelper.getAllElements(psiFile, FIELD_USES)) - .ifPresent(usesList -> usesList.stream().map(GitHubActionCache::getAction).filter(action -> !action.isSuppressed()).filter(action -> !action.isResolved()).forEach(actions::add)) - ); - - // ASYNC HTTP CONTEXT - GitHubActionCache.resolveActionsAsync(actions); + if (virtualFile != null && virtualFile.isValid() && (GitHubWorkflowHelper.isWorkflowPath(toPath(virtualFile.getPath())))) { + ReadAction.nonBlocking(() -> unresolvedActions(project, virtualFile)) + .inSmartMode(project) + .submit(AppExecutorUtil.getAppExecutorService()) + .onSuccess(GitHubActionCache::resolveActionsAsync); } }; threadPoolExec(project, task); } + private static List unresolvedActions(final Project project, final VirtualFile virtualFile) { + final List actions = new ArrayList<>(); + Optional.of(PsiManager.getInstance(project)) + .map(psiManager -> psiManager.findFile(virtualFile)) + .map(psiFile -> PsiElementHelper.getAllElements(psiFile, FIELD_USES)) + .ifPresent(usesList -> usesList.stream() + .map(GitHubActionCache::getAction) + .filter(Objects::nonNull) + .filter(action -> !action.isSuppressed()) + .filter(action -> !action.isResolved()) + .forEach(actions::add)); + return actions; + } + public static void threadPoolExec(final Project project, final Runnable task) { if (!DumbService.isDumb(project)) { - ApplicationManager.getApplication().executeOnPooledThread(task); + AppExecutorUtil.getAppExecutorService().execute(task); } else { - DumbService.getInstance(project).runWhenSmart(() -> ApplicationManager.getApplication().executeOnPooledThread(task)); + DumbService.getInstance(project).runWhenSmart(() -> AppExecutorUtil.getAppExecutorService().execute(task)); } } } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java b/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java index 12af833..55f91b5 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java @@ -2,7 +2,9 @@ import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.github.yunabraska.githubworkflow.model.VariableReferenceResolver; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TextRange; import com.intellij.patterns.PlatformPatterns; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; @@ -15,6 +17,7 @@ import java.util.Optional; import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.getWorkflowFile; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.removeQuotes; import static com.github.yunabraska.githubworkflow.logic.Action.referenceGithubAction; import static com.github.yunabraska.githubworkflow.logic.Needs.referenceNeeds; @@ -33,13 +36,12 @@ public void registerReferenceProviders(@NotNull final PsiReferenceRegistrar regi @NotNull final PsiElement psiElement, @NotNull final ProcessingContext context ) { - return getWorkflowFile(psiElement).isEmpty() ? PsiReference.EMPTY_ARRAY : Optional.of(psiElement) - .filter(PsiElementHelper::isTextElement) + return getWorkflowFile(psiElement).isEmpty() ? PsiReference.EMPTY_ARRAY : textElement(psiElement) .flatMap(element -> { - final String text = element.getText().replace("IntellijIdeaRulezzz ", "").replace("IntellijIdeaRulezzz", ""); + final String text = removeQuotes(element.getText().replace("IntellijIdeaRulezzz ", "").replace("IntellijIdeaRulezzz", "")); return referenceGithubAction(element) .or(() -> referenceNeeds(element, text)) - ; + .or(() -> referenceVariables(element)); } ) .orElse(PsiReference.EMPTY_ARRAY); @@ -47,4 +49,29 @@ public void registerReferenceProviders(@NotNull final PsiReferenceRegistrar regi } ); } + + private static Optional textElement(final PsiElement psiElement) { + PsiElement current = psiElement; + while (current != null && current.getParent() != current) { + if (PsiElementHelper.isTextElement(current)) { + return Optional.of(current); + } + current = current.getParent(); + } + return Optional.empty(); + } + + private static Optional referenceVariables(final PsiElement psiElement) { + final PsiReference[] references = ExpressionReferenceTargets.resolve(psiElement).stream() + .map(target -> new VariableReferenceResolver( + psiElement, + new TextRange(target.segment().startIndexOffset(), target.segment().endIndexOffset()), + target.target() + )) + .toArray(PsiReference[]::new); + if (references.length == 0) { + return Optional.empty(); + } + return Optional.of(references); + } } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java b/src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java new file mode 100644 index 0000000..1e97e39 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java @@ -0,0 +1,43 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.notification.NotificationGroupManager; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.ActionUpdateThread; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.DumbAwareAction; +import org.jetbrains.annotations.NotNull; + +public final class RefreshActionCacheAction extends DumbAwareAction { + + @Override + public void actionPerformed(@NotNull final AnActionEvent event) { + final GitHubActionCache.CacheSummary before = GitHubActionCache.getActionCache().summary(); + GitHubActionCache.getActionCache().refreshResolvedRemoteActions(); + notify(event, GitHubWorkflowBundle.message("notification.cache.refresh.started", before.remote())); + } + + @Override + public void update(@NotNull final AnActionEvent event) { + localize(event.getPresentation()); + final GitHubActionCache.CacheSummary summary = GitHubActionCache.getActionCache().summary(); + event.getPresentation().setEnabled(summary.remote() > 0); + } + + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.BGT; + } + + private static void notify(final AnActionEvent event, final String content) { + NotificationGroupManager.getInstance() + .getNotificationGroup("GitHub Workflow") + .createNotification(content, NotificationType.INFORMATION) + .notify(event.getProject()); + } + + private static void localize(final Presentation presentation) { + presentation.setText(GitHubWorkflowBundle.message("action.GitHubWorkflow.RefreshActionCache.text")); + presentation.setDescription(GitHubWorkflowBundle.message("action.GitHubWorkflow.RefreshActionCache.description")); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionProviders.java b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionProviders.java new file mode 100644 index 0000000..a0851c9 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionProviders.java @@ -0,0 +1,337 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.intellij.openapi.diagnostic.Logger; + +import java.io.IOException; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Base64; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public final class RemoteActionProviders { + + private static final Logger LOG = Logger.getInstance(RemoteActionProviders.class); + private static final HttpClient CLIENT = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(2)) + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); + + public static Optional resolve(final String usesValue) { + return RemoteServerSettings.getInstance().enabledServers().stream() + .map(server -> resolve(server, usesValue)) + .flatMap(Optional::stream) + .findFirst(); + } + + public static List latestRefs(final String usesBase, final int limit) { + if (limit < 1) { + return List.of(); + } + return RemoteServerSettings.getInstance().enabledServers().stream() + .map(server -> RemoteUses.parseBase(server, usesBase) + .map(uses -> latestRefs(server, uses, limit)) + .orElseGet(List::of)) + .filter(refs -> !refs.isEmpty()) + .findFirst() + .orElseGet(List::of); + } + + public static Map searchUses(final String usesPrefix, final int limit) { + if (limit < 1) { + return Map.of(); + } + return RemoteServerSettings.getInstance().enabledServers().stream() + .map(server -> RemoteUsesPrefix.parse(server, usesPrefix) + .map(prefix -> searchUses(server, prefix, limit)) + .orElseGet(Map::of)) + .filter(items -> !items.isEmpty()) + .findFirst() + .orElseGet(Map::of); + } + + private static Optional resolve(final RemoteServerSettings.Server server, final String usesValue) { + return RemoteUses.parse(server, usesValue).flatMap(remoteUses -> resolve(server, remoteUses)); + } + + private static Optional resolve(final RemoteServerSettings.Server server, final RemoteUses uses) { + for (final String metadataPath : metadataPaths(server, uses)) { + final Optional content = getContent(server, uses.owner(), uses.repo(), metadataPath, uses.ref()); + if (content.isPresent()) { + final List refs = listRefs(server, uses.owner(), uses.repo()); + return Optional.of(new RemoteActionResolution( + uses.usesValue(), + uses.owner() + "/" + uses.repo(), + content.get().downloadUrl(), + htmlUrl(server, uses, metadataPath), + content.get().content(), + !isWorkflowPath(metadataPath), + refs + )); + } + } + return Optional.empty(); + } + + private static List metadataPaths(final RemoteServerSettings.Server server, final RemoteUses uses) { + if (isWorkflowPath(uses.path())) { + return List.of(uses.path()); + } + final String base = uses.path().isBlank() ? "" : uses.path() + "/"; + return List.of(base + "action.yml", base + "action.yaml"); + } + + private static boolean isWorkflowPath(final String path) { + final String normalized = path.replace('\\', '/'); + return normalized.contains(".github/workflows/") + && (normalized.endsWith(".yml") || normalized.endsWith(".yaml")); + } + + private static Optional getContent( + final RemoteServerSettings.Server server, + final String owner, + final String repo, + final String path, + final String ref + ) { + final String url = server.apiUrl + "/repos/" + encode(owner) + "/" + encode(repo) + "/contents/" + encodePath(path) + "?ref=" + encode(ref); + return getJson(server, url).flatMap(json -> contentFromJson(json, url)); + } + + private static List listRefs(final RemoteServerSettings.Server server, final String owner, final String repo) { + final LinkedHashSet result = new LinkedHashSet<>(); + for (final String endpoint : List.of("branches", "tags")) { + final String url = server.apiUrl + "/repos/" + encode(owner) + "/" + encode(repo) + "/" + endpoint; + getJson(server, url).ifPresent(json -> namesFromJson(json).forEach(result::add)); + } + return List.copyOf(result); + } + + private static List latestRefs(final RemoteServerSettings.Server server, final RemoteUses uses, final int limit) { + final LinkedHashSet result = new LinkedHashSet<>(); + for (final String endpoint : List.of("tags", "branches")) { + final String url = server.apiUrl + "/repos/" + encode(uses.owner()) + "/" + encode(uses.repo()) + "/" + endpoint + "?per_page=" + limit; + getJson(server, url).ifPresent(json -> namesFromJson(json).forEach(result::add)); + if (result.size() >= limit) { + break; + } + } + return result.stream().limit(limit).toList(); + } + + private static Map searchUses(final RemoteServerSettings.Server server, final RemoteUsesPrefix prefix, final int limit) { + final Map result = new LinkedHashMap<>(); + for (final String endpoint : List.of("users", "orgs")) { + final String url = server.apiUrl + "/" + endpoint + "/" + encode(prefix.owner()) + "/repos?per_page=" + limit; + getJson(server, url).ifPresent(json -> repoCompletionsFromJson(json, prefix, limit).forEach(result::putIfAbsent)); + if (result.size() >= limit) { + break; + } + } + return result.entrySet().stream() + .limit(limit) + .collect(LinkedHashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), LinkedHashMap::putAll); + } + + private static Optional getJson(final RemoteServerSettings.Server server, final String url) { + for (final GitHubRequestAuthorizations.Authorization authorization : GitHubRequestAuthorizations.forApiUrl(server.apiUrl, server.tokenEnvVar, null)) { + try { + final HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(url)) + .timeout(Duration.ofSeconds(3)) + .header("Accept", "application/json") + .header("User-Agent", "GitHub-Workflow-Plugin"); + if (authorization.authenticated()) { + builder.header("Authorization", authorization.authorizationHeader()); + } + final HttpResponse response = CLIENT.send(builder.GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + if (response.statusCode() / 100 == 2) { + return Optional.of(JsonParser.parseString(response.body())); + } + if (!shouldTryNextAuthorization(response.statusCode())) { + return Optional.empty(); + } + } catch (final IOException exception) { + LOG.warn("Remote request failed [" + url + "]", exception); + return Optional.empty(); + } catch (final InterruptedException exception) { + Thread.currentThread().interrupt(); + return Optional.empty(); + } catch (final RuntimeException exception) { + LOG.warn("Remote response failed [" + url + "]", exception); + return Optional.empty(); + } + } + return Optional.empty(); + } + + private static boolean shouldTryNextAuthorization(final int statusCode) { + return statusCode == 401 || statusCode == 403 || statusCode == 404 || statusCode == 429; + } + + private static Optional contentFromJson(final JsonElement json, final String fallbackDownloadUrl) { + if (!json.isJsonObject()) { + return Optional.empty(); + } + final JsonObject object = json.getAsJsonObject(); + final Optional rawContent = stringValue(object, "content"); + if (rawContent.isEmpty()) { + return Optional.empty(); + } + final String content = new String(Base64.getMimeDecoder().decode(rawContent.get()), StandardCharsets.UTF_8); + final String downloadUrl = stringValue(object, "download_url").orElse(fallbackDownloadUrl); + return Optional.of(new ContentResponse(content, downloadUrl)); + } + + private static List namesFromJson(final JsonElement json) { + final List result = new ArrayList<>(); + if (json.isJsonArray()) { + final JsonArray array = json.getAsJsonArray(); + for (final JsonElement element : array) { + if (element.isJsonObject()) { + stringValue(element.getAsJsonObject(), "name").ifPresent(result::add); + } + } + } + return result; + } + + private static Map repoCompletionsFromJson(final JsonElement json, final RemoteUsesPrefix prefix, final int limit) { + final Map result = new LinkedHashMap<>(); + if (json.isJsonArray()) { + final JsonArray array = json.getAsJsonArray(); + for (final JsonElement element : array) { + if (element.isJsonObject()) { + final JsonObject object = element.getAsJsonObject(); + final Optional name = stringValue(object, "name"); + final Optional fullName = stringValue(object, "full_name"); + if (name.filter(value -> value.startsWith(prefix.repoPrefix())).isPresent()) { + result.putIfAbsent( + fullName.orElse(prefix.owner() + "/" + name.get()), + stringValue(object, "description").orElse(GitHubWorkflowBundle.message("completion.remote.repository")) + ); + } + } + if (result.size() >= limit) { + break; + } + } + } + return result; + } + + private static Optional stringValue(final JsonObject object, final String name) { + return Optional.ofNullable(object.get(name)) + .filter(JsonElement::isJsonPrimitive) + .map(JsonElement::getAsString) + .filter(value -> !value.isBlank()); + } + + private static String htmlUrl(final RemoteServerSettings.Server server, final RemoteUses uses, final String metadataPath) { + final String base = server.webUrl + "/" + uses.owner() + "/" + uses.repo(); + if (isWorkflowPath(metadataPath)) { + return base + "/blob/" + uses.ref() + "/" + metadataPath; + } + final String actionPath = metadataPath.endsWith("/action.yml") + ? metadataPath.substring(0, metadataPath.length() - "/action.yml".length()) + : metadataPath.endsWith("/action.yaml") + ? metadataPath.substring(0, metadataPath.length() - "/action.yaml".length()) + : ""; + final String suffix = actionPath.isBlank() ? "" : "/" + actionPath; + return base + "/tree/" + uses.ref() + suffix + "#readme"; + } + + private static String encodePath(final String path) { + return List.of(path.split("/")).stream().map(RemoteActionProviders::encode).reduce((left, right) -> left + "/" + right).orElse(""); + } + + private static String encode(final String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8).replace("+", "%20"); + } + + private record ContentResponse(String content, String downloadUrl) { + } + + private record RemoteUses(String usesValue, String owner, String repo, String path, String ref) { + + static Optional parse(final RemoteServerSettings.Server server, final String value) { + if (value == null || value.isBlank() || value.startsWith(".")) { + return Optional.empty(); + } + final String stripped = stripServerPrefix(server, value.trim()).orElse(null); + if (stripped == null) { + return Optional.empty(); + } + final int atIndex = stripped.lastIndexOf('@'); + if (atIndex < 0 || atIndex == stripped.length() - 1) { + return Optional.empty(); + } + final String path = stripped.substring(0, atIndex); + final String ref = stripped.substring(atIndex + 1); + final String[] parts = path.split("/", 3); + if (parts.length < 2 || parts[0].isBlank() || parts[1].isBlank()) { + return Optional.empty(); + } + return Optional.of(new RemoteUses(value.trim(), parts[0], parts[1], parts.length == 3 ? parts[2] : "", ref)); + } + + static Optional parseBase(final RemoteServerSettings.Server server, final String value) { + if (value == null || value.isBlank() || value.startsWith(".")) { + return Optional.empty(); + } + final String stripped = stripServerPrefix(server, value.trim()).orElse(null); + if (stripped == null) { + return Optional.empty(); + } + final int atIndex = stripped.lastIndexOf('@'); + final String path = atIndex < 0 ? stripped : stripped.substring(0, atIndex); + final String[] parts = path.split("/", 3); + if (parts.length < 2 || parts[0].isBlank() || parts[1].isBlank()) { + return Optional.empty(); + } + return Optional.of(new RemoteUses(value.trim(), parts[0], parts[1], parts.length == 3 ? parts[2] : "", "")); + } + + private static Optional stripServerPrefix(final RemoteServerSettings.Server server, final String value) { + if (value.startsWith("http://") || value.startsWith("https://")) { + final String prefix = server.webUrl + "/"; + return value.startsWith(prefix) ? Optional.of(value.substring(prefix.length())) : Optional.empty(); + } + return Optional.of(value); + } + } + + private record RemoteUsesPrefix(String owner, String repoPrefix) { + + static Optional parse(final RemoteServerSettings.Server server, final String value) { + if (value == null || value.isBlank() || value.startsWith(".") || value.contains("@")) { + return Optional.empty(); + } + final String stripped = RemoteUses.stripServerPrefix(server, value.trim()).orElse(null); + if (stripped == null) { + return Optional.empty(); + } + final String[] parts = stripped.split("/", 3); + if (parts.length < 2 || parts[0].isBlank()) { + return Optional.empty(); + } + return Optional.of(new RemoteUsesPrefix(parts[0], parts[1])); + } + } + + private RemoteActionProviders() { + // static helper + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionResolution.java b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionResolution.java new file mode 100644 index 0000000..548e89e --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionResolution.java @@ -0,0 +1,14 @@ +package com.github.yunabraska.githubworkflow.services; + +import java.util.List; + +public record RemoteActionResolution( + String usesValue, + String name, + String downloadUrl, + String githubUrl, + String content, + boolean action, + List refs +) { +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java new file mode 100644 index 0000000..6252db9 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java @@ -0,0 +1,137 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.application.ApplicationManager; +import org.jetbrains.plugins.github.authentication.GHAccountsUtil; +import org.jetbrains.plugins.github.authentication.accounts.GithubAccount; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; + +public final class RemoteServerSettings { + + public static final String TYPE_GITHUB = "github"; + + private final CopyOnWriteArrayList testServers = new CopyOnWriteArrayList<>(); + + public static RemoteServerSettings getInstance() { + return ApplicationManager.getApplication().getService(RemoteServerSettings.class); + } + + public List enabledServers() { + final Map result = new LinkedHashMap<>(); + testServers.stream() + .map(Server::normalized) + .filter(Server::isValid) + .forEach(server -> result.put(server.key(), server)); + jetBrainsGithubServers().forEach(server -> result.putIfAbsent(server.key(), server)); + final Server defaultGitHub = defaultGitHub(); + result.putIfAbsent(defaultGitHub.key(), defaultGitHub); + return List.copyOf(result.values()); + } + + void setCustomServers(final List servers) { + testServers.clear(); + Optional.ofNullable(servers).orElseGet(List::of).stream() + .map(Server::normalized) + .filter(Server::isValid) + .forEach(testServers::add); + } + + public static Server defaultGitHub() { + return new Server("GitHub", "https://github.com", "https://api.github.com", "", true); + } + + private static List jetBrainsGithubServers() { + try { + return GHAccountsUtil.getAccounts().stream() + .sorted((left, right) -> { + final int order = Integer.compare(accountOrder(left), accountOrder(right)); + return order == 0 ? left.getName().compareTo(right.getName()) : order; + }) + .map(account -> new Server( + account.getName(), + account.getServer().toUrl(), + account.getServer().toApiUrl(), + "", + true + )) + .map(Server::normalized) + .filter(Server::isValid) + .toList(); + } catch (final RuntimeException ignored) { + return List.of(); + } + } + + private static int accountOrder(final GithubAccount account) { + return account.getServer().isGithubDotCom() ? 0 : 1; + } + + public static final class Server { + public final String type; + public final String name; + public final String webUrl; + public final String apiUrl; + public final String tokenEnvVar; + public final boolean enabled; + + public Server( + final String name, + final String webUrl, + final String apiUrl, + final String tokenEnvVar, + final boolean enabled + ) { + this.type = TYPE_GITHUB; + this.name = name; + this.webUrl = webUrl; + this.apiUrl = apiUrl; + this.tokenEnvVar = tokenEnvVar; + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isValid() { + return isEnabled() && hasText(webUrl) && hasText(apiUrl); + } + + public String authorizationHeader() { + return Optional.ofNullable(tokenEnvVar) + .filter(RemoteServerSettings::hasText) + .map(System::getenv) + .filter(RemoteServerSettings::hasText) + .map(token -> "Bearer " + token) + .orElse(""); + } + + public Server normalized() { + return new Server( + hasText(name) ? name.trim() : webUrl, + trimTrailingSlash(webUrl), + trimTrailingSlash(apiUrl), + Optional.ofNullable(tokenEnvVar).map(String::trim).orElse(""), + enabled + ); + } + + private String key() { + final Server normalized = normalized(); + return normalized.type + "|" + normalized.webUrl + "|" + normalized.apiUrl; + } + } + + private static String trimTrailingSlash(final String value) { + final String trimmed = Optional.ofNullable(value).map(String::trim).orElse(""); + return trimmed.endsWith("/") ? trimmed.substring(0, trimmed.length() - 1) : trimmed; + } + + private static boolean hasText(final String value) { + return value != null && !value.isBlank(); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java b/src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java new file mode 100644 index 0000000..8a89844 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java @@ -0,0 +1,42 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.notification.NotificationGroupManager; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.ActionUpdateThread; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.DumbAwareAction; +import org.jetbrains.annotations.NotNull; + +public final class RestoreActionWarningsAction extends DumbAwareAction { + + @Override + public void actionPerformed(@NotNull final AnActionEvent event) { + final long restored = GitHubActionCache.getActionCache().restoreWarnings(); + notify(event, GitHubWorkflowBundle.message("notification.warnings.restored", restored)); + } + + @Override + public void update(@NotNull final AnActionEvent event) { + localize(event.getPresentation()); + final GitHubActionCache.CacheSummary summary = GitHubActionCache.getActionCache().summary(); + event.getPresentation().setEnabled(summary.suppressed() > 0); + } + + @Override + public @NotNull ActionUpdateThread getActionUpdateThread() { + return ActionUpdateThread.BGT; + } + + private static void notify(final AnActionEvent event, final String content) { + NotificationGroupManager.getInstance() + .getNotificationGroup("GitHub Workflow") + .createNotification(content, NotificationType.INFORMATION) + .notify(event.getProject()); + } + + private static void localize(final Presentation presentation) { + presentation.setText(GitHubWorkflowBundle.message("action.GitHubWorkflow.RestoreActionWarnings.text")); + presentation.setDescription(GitHubWorkflowBundle.message("action.GitHubWorkflow.RestoreActionWarnings.description")); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupEnterHandler.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupEnterHandler.java new file mode 100644 index 0000000..44614d2 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupEnterHandler.java @@ -0,0 +1,44 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.getWorkflowFile; + +/** + * Opens workflow key completion after pressing Enter below YAML mapping keys. + */ +public final class WorkflowAutoPopupEnterHandler extends EnterHandlerDelegateAdapter { + + @Override + public @NotNull Result postProcessEnter( + @NotNull final PsiFile file, + @NotNull final Editor editor, + @NotNull final DataContext dataContext + ) { + if (shouldAutoPopupAfterEnter(editor, file)) { + WorkflowAutoPopupTypedHandler.scheduleWorkflowPopup(file.getProject(), editor); + } + return Result.Continue; + } + + static boolean shouldAutoPopupAfterEnter(final Editor editor, final PsiFile file) { + if (editor == null || file == null || getWorkflowFile(file).isEmpty()) { + return false; + } + final String textBeforeCaret = editor.getDocument() + .getImmutableCharSequence() + .subSequence(0, Math.min(editor.getCaretModel().getOffset(), editor.getDocument().getTextLength())) + .toString(); + final int currentLineStart = textBeforeCaret.lastIndexOf('\n'); + if (currentLineStart <= 0) { + return false; + } + final int previousLineStart = textBeforeCaret.lastIndexOf('\n', currentLineStart - 1) + 1; + final String previousLine = textBeforeCaret.substring(previousLineStart, currentLineStart).trim(); + return !previousLine.startsWith("#") && previousLine.endsWith(":"); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupTypedHandler.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupTypedHandler.java new file mode 100644 index 0000000..1ef4d6e --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupTypedHandler.java @@ -0,0 +1,72 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.codeInsight.AutoPopupController; +import com.intellij.codeInsight.editorActions.TypedHandlerDelegate; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.getWorkflowFile; + +/** + * Opens workflow completion while the user types YAML structure and expression separators. + */ +public final class WorkflowAutoPopupTypedHandler extends TypedHandlerDelegate { + + @Override + public @NotNull Result checkAutoPopup( + final char typeChar, + @NotNull final Project project, + @NotNull final Editor editor, + @NotNull final PsiFile file + ) { + // Structural workflow completion is scheduled after the typed character lands in the document. + return Result.CONTINUE; + } + + @Override + public @NotNull Result charTyped( + final char typeChar, + @NotNull final Project project, + @NotNull final Editor editor, + @NotNull final PsiFile file + ) { + if (shouldAutoPopup(typeChar, editor, file)) { + scheduleWorkflowPopup(project, editor); + } + return Result.CONTINUE; + } + + static void scheduleWorkflowPopup(final Project project, final Editor editor) { + ApplicationManager.getApplication().invokeLater(() -> { + if (project.isDisposed() || editor.isDisposed()) { + return; + } + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + documentManager.commitDocument(editor.getDocument()); + final PsiFile file = documentManager.getPsiFile(editor.getDocument()); + if (file != null && getWorkflowFile(file).isPresent()) { + AutoPopupController.getInstance(project).scheduleAutoPopup(editor); + } + }); + } + + static boolean shouldAutoPopup(final char typeChar, final Editor editor, final PsiFile file) { + if (!CodeCompletion.workflowCompletionTrigger(typeChar) || editor == null || file == null) { + return false; + } + final int textLength = file.getTextLength(); + if (textLength <= 0) { + return getWorkflowFile(file).isPresent(); + } + final int offset = Math.max(0, Math.min(editor.getCaretModel().getOffset(), textLength - 1)); + final PsiElement element = Optional.ofNullable(file.findElementAt(offset)).orElse(file); + return getWorkflowFile(element).isPresent(); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionConfidence.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionConfidence.java new file mode 100644 index 0000000..29122ef --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionConfidence.java @@ -0,0 +1,28 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.codeInsight.completion.CompletionConfidence; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.util.ThreeState; +import org.jetbrains.annotations.NotNull; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper.getWorkflowFile; + +/** + * Keeps workflow auto-popup completion available in sparse YAML positions, such as the line after {@code on:}. + */ +public final class WorkflowCompletionConfidence extends CompletionConfidence { + + @Override + public @NotNull ThreeState shouldSkipAutopopup( + final Editor editor, + final PsiElement contextElement, + final PsiFile psiFile, + final int offset + ) { + return getWorkflowFile(psiFile).isPresent() || getWorkflowFile(contextElement).isPresent() + ? ThreeState.NO + : ThreeState.UNSURE; + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolver.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolver.java new file mode 100644 index 0000000..1dc30dc --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolver.java @@ -0,0 +1,86 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectUtil; +import com.intellij.openapi.vfs.VirtualFile; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +/** + * Resolves the checked-out Git branch for workflow_dispatch run configurations. + */ +final class WorkflowCurrentBranchResolver { + + Optional resolve(final Project project) { + return Optional.ofNullable(project) + .map(ProjectUtil::guessProjectDir) + .map(VirtualFile::getPath) + .map(Path::of) + .flatMap(this::resolve); + } + + Optional resolve(final Project project, final VirtualFile file) { + return repositoryRoot(file) + .flatMap(this::resolve) + .or(() -> resolve(project)); + } + + Optional resolve(final Path projectDir) { + return gitDir(projectDir) + .map(dir -> dir.resolve("HEAD")) + .flatMap(WorkflowCurrentBranchResolver::readString) + .flatMap(WorkflowCurrentBranchResolver::branchName); + } + + static Optional branchName(final String head) { + final String prefix = "ref: refs/heads/"; + return Optional.ofNullable(head) + .map(String::trim) + .filter(value -> value.startsWith(prefix)) + .map(value -> value.substring(prefix.length())) + .filter(value -> !value.isBlank()); + } + + private static Optional gitDir(final Path projectDir) { + final Path dotGit = projectDir.resolve(".git"); + if (Files.isDirectory(dotGit)) { + return Optional.of(dotGit); + } + if (!Files.isRegularFile(dotGit)) { + return Optional.empty(); + } + return readString(dotGit) + .map(String::trim) + .filter(value -> value.startsWith("gitdir:")) + .map(value -> value.substring("gitdir:".length()).trim()) + .filter(value -> !value.isBlank()) + .map(Path::of) + .map(path -> path.isAbsolute() ? path : projectDir.resolve(path).normalize()); + } + + private static Optional readString(final Path path) { + try { + return Optional.of(Files.readString(path)); + } catch (final IOException ignored) { + return Optional.empty(); + } + } + + private static Optional repositoryRoot(final VirtualFile file) { + Path current = Optional.ofNullable(file) + .map(VirtualFile::getPath) + .map(Path::of) + .map(Path::getParent) + .orElse(null); + while (current != null) { + if (Files.isDirectory(current.resolve(".git")) || Files.isRegularFile(current.resolve(".git"))) { + return Optional.of(current); + } + current = current.getParent(); + } + return Optional.empty(); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java new file mode 100644 index 0000000..e6108f5 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java @@ -0,0 +1,226 @@ +package com.github.yunabraska.githubworkflow.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Lightweight reader for workflow_dispatch inputs used by run configuration defaults. + */ +public final class WorkflowDispatchInputs { + + public List parse(final String yaml) { + final List lines = lines(yaml); + final Optional workflowDispatchIndex = workflowDispatchIndex(lines); + if (workflowDispatchIndex.isEmpty()) { + return List.of(); + } + final int workflowDispatchIndent = lines.get(workflowDispatchIndex.get()).indent(); + final Optional inputsIndex = childIndex(lines, workflowDispatchIndex.get() + 1, workflowDispatchIndent, "inputs"); + if (inputsIndex.isEmpty()) { + return List.of(); + } + final int inputsIndent = lines.get(inputsIndex.get()).indent(); + final List result = new ArrayList<>(); + for (int index = inputsIndex.get() + 1; index < lines.size(); index++) { + final Line line = lines.get(index); + if (line.indent() <= inputsIndent) { + break; + } + if (line.indent() == inputsIndent + 2 && line.keyValue().isPresent()) { + result.add(readInput(lines, index, inputsIndent + 2)); + } + } + return List.copyOf(result); + } + + public boolean hasWorkflowDispatch(final String yaml) { + return workflowDispatchIndex(lines(yaml)).isPresent(); + } + + public String defaultsText(final String yaml) { + final StringBuilder result = new StringBuilder(); + for (final Input input : parse(yaml)) { + result.append(input.name()).append("=").append(input.defaultValue()).append("\n"); + } + return result.toString(); + } + + public static java.util.Map parseKeyValueText(final String text) { + final java.util.LinkedHashMap result = new java.util.LinkedHashMap<>(); + Optional.ofNullable(text).orElse("").lines() + .map(String::trim) + .filter(line -> !line.isBlank()) + .filter(line -> !line.startsWith("#")) + .forEach(line -> { + final int separator = line.indexOf('='); + if (separator > 0) { + result.put(line.substring(0, separator).trim(), line.substring(separator + 1).trim()); + } + }); + return java.util.Map.copyOf(result); + } + + private static Input readInput(final List lines, final int inputIndex, final int inputIndent) { + final String name = lines.get(inputIndex).keyValue().orElse(""); + String type = "string"; + String required = "false"; + String defaultValue = ""; + String description = ""; + final List options = new ArrayList<>(); + for (int index = inputIndex + 1; index < lines.size(); index++) { + final Line line = lines.get(index); + if (line.indent() <= inputIndent) { + break; + } + if (line.indent() == inputIndent + 2) { + if ("type".equals(line.keyValue().orElse(""))) { + type = line.value(); + } else if ("required".equals(line.keyValue().orElse(""))) { + required = line.value(); + } else if ("default".equals(line.keyValue().orElse(""))) { + defaultValue = line.value(); + } else if ("description".equals(line.keyValue().orElse(""))) { + description = line.value(); + } else if ("options".equals(line.keyValue().orElse(""))) { + options.addAll(readOptions(lines, index, inputIndent + 2)); + } + } + } + return new Input(name, type, Boolean.parseBoolean(required), defaultValue, description, List.copyOf(options)); + } + + private static List readOptions(final List lines, final int optionsIndex, final int optionsIndent) { + final List result = new ArrayList<>(inlineOptions(lines.get(optionsIndex).value())); + for (int index = optionsIndex + 1; index < lines.size(); index++) { + final Line line = lines.get(index); + if (line.indent() <= optionsIndent) { + break; + } + if (line.content().startsWith("- ")) { + result.add(stripQuotes(line.content().substring(2).trim())); + } + } + return List.copyOf(result); + } + + private static List inlineOptions(final String value) { + final String trimmed = value == null ? "" : value.trim(); + if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) { + return List.of(); + } + final String body = trimmed.substring(1, trimmed.length() - 1); + if (body.isBlank()) { + return List.of(); + } + return splitInlineList(body).stream() + .filter(option -> !option.isBlank()) + .map(WorkflowDispatchInputs::stripQuotes) + .toList(); + } + + private static List splitInlineList(final String body) { + final List result = new ArrayList<>(); + final StringBuilder current = new StringBuilder(); + char quote = 0; + for (int index = 0; index < body.length(); index++) { + final char character = body.charAt(index); + if (quote != 0) { + current.append(character); + if (character == quote) { + quote = 0; + } + } else if (character == '\'' || character == '"') { + quote = character; + current.append(character); + } else if (character == ',') { + result.add(current.toString().trim()); + current.setLength(0); + } else { + current.append(character); + } + } + result.add(current.toString().trim()); + return List.copyOf(result); + } + + private static Optional workflowDispatchIndex(final List lines) { + for (int index = 0; index < lines.size(); index++) { + final Line line = lines.get(index); + if ("workflow_dispatch".equals(line.keyValue().orElse("")) || "on".equals(line.keyValue().orElse("")) && "workflow_dispatch".equals(line.value())) { + return Optional.of(index); + } + if (line.content().equals("- workflow_dispatch")) { + return Optional.of(index); + } + } + return Optional.empty(); + } + + private static Optional childIndex(final List lines, final int start, final int parentIndent, final String key) { + for (int index = start; index < lines.size(); index++) { + final Line line = lines.get(index); + if (line.indent() <= parentIndent) { + break; + } + if (key.equals(line.keyValue().orElse(""))) { + return Optional.of(index); + } + } + return Optional.empty(); + } + + private static List lines(final String yaml) { + final List result = new ArrayList<>(); + Optional.ofNullable(yaml).orElse("").lines() + .map(WorkflowDispatchInputs::line) + .filter(line -> !line.content().isBlank()) + .filter(line -> !line.content().startsWith("#")) + .forEach(result::add); + return result; + } + + private static Line line(final String raw) { + int indent = 0; + while (indent < raw.length() && raw.charAt(indent) == ' ') { + indent++; + } + final String content = raw.substring(indent).trim(); + final int separator = content.indexOf(':'); + if (separator < 0) { + return new Line(indent, content, "", ""); + } + final String key = content.substring(0, separator).trim(); + final String value = stripQuotes(content.substring(separator + 1).trim()); + return new Line(indent, content, key, value); + } + + private static String stripQuotes(final String value) { + if (value.length() >= 2 && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) { + return value.substring(1, value.length() - 1); + } + return value; + } + + public record Input(String name, String type, boolean required, String defaultValue, String description, List options) { + public Input( + final String name, + final String type, + final boolean required, + final String defaultValue, + final String description + ) { + this(name, type, required, defaultValue, description, List.of()); + } + + public Input { + options = options == null ? List.of() : List.copyOf(options); + } + } + + private record Line(int indent, String content, String key, String value) { + Optional keyValue() { + return key.isBlank() ? Optional.empty() : Optional.of(key); + } + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationProvider.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationProvider.java new file mode 100644 index 0000000..c562639 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationProvider.java @@ -0,0 +1,552 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; +import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.intellij.lang.documentation.AbstractDocumentationProvider; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.FakePsiElement; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.psi.YAMLKeyValue; +import org.jetbrains.yaml.psi.YAMLScalar; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_SECRETS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_ON; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_OUTPUTS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_USES; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_WITH; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChild; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParent; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentStepOrJob; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getText; +import static com.github.yunabraska.githubworkflow.logic.Steps.listStepOutputs; +import static java.util.Optional.ofNullable; + +public final class WorkflowDocumentationProvider extends AbstractDocumentationProvider { + + @Override + public @Nullable PsiElement getCustomDocumentationElement( + final Editor editor, + final PsiFile file, + final PsiElement contextElement, + final int targetOffset + ) { + if (contextElement == null || targetOffset < 0 || targetOffset > file.getTextLength()) { + return null; + } + return documentationCandidates(file, contextElement, targetOffset).stream() + .flatMap(candidate -> documentationAt(candidate, targetOffset).stream()) + .findFirst() + .map(payload -> new WorkflowDocumentationElement(contextElement, payload)) + .orElse(null); + } + + @Override + public @Nullable String getQuickNavigateInfo(final PsiElement element, final PsiElement originalElement) { + return element instanceof WorkflowDocumentationElement workflowElement + ? workflowElement.payload().hint() + : null; + } + + @Override + public @Nullable String generateHoverDoc(final PsiElement element, final PsiElement originalElement) { + return generateDoc(element, originalElement); + } + + @Override + public @Nullable String generateDoc(final PsiElement element, final PsiElement originalElement) { + return element instanceof WorkflowDocumentationElement workflowElement + ? workflowElement.payload().html() + : null; + } + + static Optional documentationAt(final PsiElement element, final int absoluteOffset) { + return declaredOutputDoc(element) + .or(() -> actionParameterDoc(element)) + .or(() -> textElement(element).flatMap(WorkflowDocumentationProvider::actionUseDoc)) + .or(() -> textElement(element).flatMap(text -> variableDoc(text, absoluteOffset))); + } + + private static List documentationCandidates(final PsiFile file, final PsiElement contextElement, final int targetOffset) { + final Set result = new LinkedHashSet<>(); + addCandidate(result, contextElement); + addCandidate(result, file.findElementAt(Math.min(targetOffset, Math.max(0, file.getTextLength() - 1)))); + if (targetOffset > 0) { + addCandidate(result, file.findElementAt(targetOffset - 1)); + } + if (targetOffset + 1 < file.getTextLength()) { + addCandidate(result, file.findElementAt(targetOffset + 1)); + } + return List.copyOf(result); + } + + private static void addCandidate(final Set result, final PsiElement element) { + PsiElement current = element; + while (current != null && current.getParent() != current) { + result.add(current); + if (current instanceof YAMLKeyValue) { + return; + } + current = current.getParent(); + } + } + + private static Optional actionUseDoc(final PsiElement textElement) { + return getParent(textElement, FIELD_USES) + .map(GitHubActionCache::getAction) + .map(WorkflowDocumentationProvider::actionDoc); + } + + private static Optional actionParameterDoc(final PsiElement element) { + return keyValueAt(element) + .filter(item -> getParent(item.getParent(), FIELD_WITH).isPresent() + || getParent(item.getParent(), FIELD_SECRETS).isPresent()) + .flatMap(item -> getParentStepOrJob(item) + .map(GitHubActionCache::getAction) + .flatMap(action -> parameterDoc(item, action))); + } + + private static Optional parameterDoc(final YAMLKeyValue item, final GitHubAction action) { + if (action == null || !action.isResolved()) { + return Optional.empty(); + } + final boolean secret = getParent(item.getParent(), FIELD_SECRETS).isPresent(); + final String name = item.getKeyText(); + final String details = secret ? action.freshSecrets().get(name) : action.freshInputs().get(name); + final String label = secret ? message("documentation.secret.label") : message("documentation.input.label"); + return ofNullable(details) + .map(text -> new DocPayload( + label + " " + name, + renderParameter(label, name, text, action.githubUrl()), + plainParameter(label, name, text) + )); + } + + private static Optional variableDoc(final PsiElement textElement, final int absoluteOffset) { + final int offsetInElement = absoluteOffset - textElement.getTextRange().getStartOffset(); + final Optional target = ExpressionReferenceTargets.resolveAt(textElement, offsetInElement).stream().findFirst(); + if (target.isPresent()) { + return Optional.of(referenceDoc(target.get())); + } + return ExpressionReferenceTargets.segmentAt(textElement, offsetInElement) + .flatMap(WorkflowDocumentationProvider::contextDoc); + } + + private static DocPayload referenceDoc(final ExpressionReferenceTarget target) { + return switch (target.kind()) { + case "input" -> yamlParameterDoc(message("documentation.input.label"), true, target.segment().text(), target.target()); + case "secret" -> yamlParameterDoc(message("documentation.secret.label"), false, target.segment().text(), target.target()); + case "env" -> yamlValueDoc(message("documentation.env.label"), target.segment().text(), target.target()); + case "matrix" -> yamlValueDoc(message("documentation.matrix.label"), target.segment().text(), target.target()); + case "step" -> stepDoc(target.target()); + case "step-output" -> stepOutputDoc(target.segment().text(), target.target()); + case "need" -> simpleDoc(message("documentation.need.label"), target.segment().text(), message("documentation.need.description")); + case "need-output" -> outputDoc(message("documentation.needOutput.label"), target.segment().text(), target.target()); + case "job" -> simpleDoc(message("documentation.reusableJob.label"), target.segment().text(), message("documentation.reusableJob.description")); + case "job-output" -> outputDoc(message("documentation.reusableJobOutput.label"), target.segment().text(), target.target()); + case "service" -> yamlValueDoc(message("documentation.service.label"), target.segment().text(), target.target()); + case "service-port" -> yamlValueDoc(message("documentation.servicePort.label"), target.segment().text(), target.target()); + case "container" -> yamlValueDoc(message("documentation.container.label"), target.segment().text(), target.target()); + default -> simpleDoc(message("documentation.symbol.label"), target.segment().text(), message("documentation.symbol.description")); + }; + } + + private static Optional declaredOutputDoc(final PsiElement element) { + return keyValueAt(element) + .filter(output -> getParent(output.getParent(), FIELD_OUTPUTS).isPresent()) + .filter(WorkflowDocumentationProvider::isDirectOutput) + .filter(output -> getParent(output, FIELD_WITH).isEmpty()) + .map(output -> outputDoc(outputLabel(output), output.getKeyText(), output)); + } + + private static boolean isDirectOutput(final YAMLKeyValue output) { + return output.getParent() != null + && output.getParent().getParent() instanceof YAMLKeyValue parent + && FIELD_OUTPUTS.equals(parent.getKeyText()); + } + + private static String outputLabel(final YAMLKeyValue output) { + return getParent(output, FIELD_ON).isPresent() + ? message("documentation.workflowOutput.label") + : message("documentation.jobOutput.label"); + } + + private static DocPayload actionDoc(final GitHubAction action) { + final String title = action.displayName(); + final StringBuilder html = new StringBuilder(); + html.append("

").append(escape(title)).append("

"); + html.append("

").append(action.isAction() + ? message("documentation.action.label") + : message("documentation.reusableWorkflow.label")).append(""); + if (action.isResolved()) { + html.append(" ").append(message("documentation.resolvedFrom", "" + escape(action.usesValue()) + "")); + } else { + html.append(" ").append(message("documentation.notResolved")); + } + html.append("

"); + appendParagraph(html, action.description()); + appendLink(html, action.githubUrl()); + appendMap(html, message("documentation.inputs.title"), action.freshInputs()); + appendMap(html, message("documentation.outputs.title"), action.freshOutputs()); + appendMap(html, message("documentation.secrets.title"), action.freshSecrets()); + return new DocPayload(title, html.toString(), title + "\n" + action.usesValue()); + } + + private static DocPayload yamlParameterDoc(final String label, final boolean input, final String name, final PsiElement target) { + final String details = target instanceof YAMLKeyValue keyValue + ? PsiElementHelper.getDescription(keyValue, input) + : ""; + return new DocPayload( + label + " " + name, + renderParameter(label, name, details, ""), + plainParameter(label, name, details) + ); + } + + private static DocPayload yamlValueDoc(final String label, final String name, final PsiElement target) { + final String value = target instanceof YAMLKeyValue keyValue ? getText(keyValue).orElse("") : target.getText(); + final String html = "

" + escape(label) + " " + escape(name) + "

" + + (value.isBlank() ? "" : "

" + escape(message("documentation.value.label")) + ": " + escape(value) + "

"); + return new DocPayload(label + " " + name, html, label + " " + name); + } + + private static DocPayload stepDoc(final PsiElement target) { + final YAMLKeyValue id = target instanceof YAMLKeyValue keyValue ? keyValue : null; + final Optional stepItem = PsiElementHelper.getParentStep(target); + final String name = id == null ? target.getText() : getText(id).orElse(id.getKeyText()); + final String title = message("documentation.step.title", name); + final StringBuilder html = new StringBuilder("

").append(escape(title)).append("

"); + stepItem.flatMap(step -> getChild(step, "name")).flatMap(PsiElementHelper::getText).ifPresent(value -> appendDetail(html, message("documentation.name.label"), value)); + stepItem.flatMap(step -> getChild(step, FIELD_USES)).flatMap(PsiElementHelper::getText).ifPresent(value -> appendDetail(html, message("documentation.uses.label"), value)); + stepItem.flatMap(step -> getChild(step, FIELD_USES)) + .map(GitHubActionCache::getAction) + .filter(action -> action != null && action.isResolved()) + .ifPresent(action -> { + appendDetail(html, action.isAction() + ? message("documentation.action.label") + : message("documentation.reusableWorkflow.label"), action.displayName()); + appendParagraph(html, action.description()); + }); + stepItem.flatMap(step -> getChild(step, "run")).flatMap(PsiElementHelper::getText).ifPresent(value -> appendDetail(html, message("documentation.run.label"), value)); + stepItem.ifPresent(step -> appendList(html, message("documentation.outputs.title"), listStepOutputs(step).stream().map(output -> output.key()).toList())); + return new DocPayload(title, html.toString(), title); + } + + private static DocPayload stepOutputDoc(final String outputName, final PsiElement target) { + final Optional source = stepOutputSource(target); + final StringBuilder details = new StringBuilder(); + source.flatMap(value -> value.outputDescription(outputName)).ifPresent(value -> appendLine(details, message("documentation.description.label"), value)); + source.ifPresent(value -> { + appendLine(details, message("documentation.step.label"), value.stepLabel()); + appendLine(details, message("documentation.uses.label"), value.usesValue()); + appendLine(details, message("documentation.source.label"), value.sourceLabel()); + }); + final String plainDetails = details.toString(); + final String label = message("documentation.stepOutput.label"); + final StringBuilder html = new StringBuilder(renderParameter(label, outputName, plainDetails, "")); + source.flatMap(StepOutputSource::url).ifPresent(url -> appendLink(html, url, source.map(StepOutputSource::usesValue).orElse(url))); + return new DocPayload( + label + " " + outputName, + html.toString(), + plainParameter(label, outputName, plainDetails) + ); + } + + private static Optional stepOutputSource(final PsiElement target) { + final Optional stepItem = PsiElementHelper.getParentStep(target); + return stepItem.map(step -> { + final String stepId = getText(step, "id").orElse(""); + final String stepName = getText(step, "name").orElse(""); + final Optional uses = getChild(step, FIELD_USES); + final GitHubAction action = uses.map(GitHubActionCache::getAction) + .filter(candidate -> candidate != null && candidate.isResolved()) + .orElse(null); + return new StepOutputSource(stepId, stepName, uses.flatMap(PsiElementHelper::getText).orElse(""), action); + }); + } + + private static DocPayload outputDoc(final String label, final String name, final PsiElement target) { + final StringBuilder details = new StringBuilder(); + keyValueAt(target).ifPresent(output -> { + appendLine(details, message("documentation.description.label"), getText(output, "description").orElse("")); + appendLine(details, message("documentation.value.label"), getText(output, "value").or(() -> getText(output)).orElse("")); + outputSourceDetails(output).ifPresent(source -> appendLine(details, message("documentation.source.label"), source)); + }); + return new DocPayload( + label + " " + name, + renderParameter(label, name, details.toString(), ""), + plainParameter(label, name, details.toString()) + ); + } + + private static Optional outputSourceDetails(final YAMLKeyValue output) { + return PsiElementHelper.getTextElement(output) + .stream() + .flatMap(text -> ExpressionReferenceTargets.resolve(text).stream()) + .filter(target -> "step-output".equals(target.kind())) + .findFirst() + .map(target -> target.target() instanceof YAMLKeyValue uses && FIELD_USES.equals(uses.getKeyText()) + ? GitHubActionCache.getAction(uses) + : null) + .filter(action -> action != null && action.isResolved()) + .map(action -> action.displayName() + (action.description().isBlank() ? "" : " - " + action.description())); + } + + private static void appendLine(final StringBuilder builder, final String name, final String value) { + if (value != null && !value.isBlank()) { + if (!builder.isEmpty()) { + builder.append('\n'); + } + builder.append(name).append(": ").append(value); + } + } + + private static Optional contextDoc(final com.github.yunabraska.githubworkflow.model.SimpleElement segment) { + final String text = segment.text(); + return switch (text) { + case "github" -> Optional.of(simpleDoc(message("documentation.context.github"), "github", message("documentation.context.github.description"))); + case "gitea" -> Optional.of(simpleDoc(message("documentation.context.gitea"), "gitea", message("documentation.context.gitea.description"))); + case "inputs" -> Optional.of(simpleDoc(message("documentation.context.inputs"), "inputs", message("documentation.context.inputs.description"))); + case "secrets" -> Optional.of(simpleDoc(message("documentation.context.secrets"), "secrets", message("documentation.context.secrets.description"))); + case "env" -> Optional.of(simpleDoc(message("documentation.context.env"), "env", message("documentation.context.env.description"))); + case "matrix" -> Optional.of(simpleDoc(message("documentation.context.matrix"), "matrix", message("documentation.context.matrix.description"))); + case "steps" -> Optional.of(simpleDoc(message("documentation.context.steps"), "steps", message("documentation.context.steps.description"))); + case "needs" -> Optional.of(simpleDoc(message("documentation.context.needs"), "needs", message("documentation.context.needs.description"))); + case "jobs" -> Optional.of(simpleDoc(message("documentation.context.jobs"), "jobs", message("documentation.context.jobs.description"))); + case "outputs" -> Optional.of(simpleDoc(message("documentation.context.outputs"), "outputs", message("documentation.context.outputs.description"))); + case "result" -> Optional.of(simpleDoc(message("documentation.context.result"), "result", message("documentation.context.result.description"))); + case "outcome" -> Optional.of(simpleDoc(message("documentation.context.outcome"), "outcome", message("documentation.context.outcome.description"))); + case "conclusion" -> Optional.of(simpleDoc(message("documentation.context.conclusion"), "conclusion", message("documentation.context.conclusion.description"))); + default -> Optional.empty(); + }; + } + + private static String message(final String key, final Object... params) { + return GitHubWorkflowBundle.message(key, params); + } + + private static DocPayload simpleDoc(final String label, final String name, final String description) { + final String html = "

" + escape(label) + " " + escape(name) + "

" + escape(description) + "

"; + return new DocPayload(label + " " + name, html, label + " " + name + "\n" + description); + } + + private static String renderParameter(final String label, final String name, final String details, final String url) { + final StringBuilder html = new StringBuilder("

") + .append(escape(label)) + .append(" ") + .append(escape(name)) + .append("

"); + for (final String line : detailLines(details)) { + final int separator = line.indexOf(':'); + if (separator > 0) { + appendDetail(html, line.substring(0, separator), line.substring(separator + 1).trim()); + } else { + appendParagraph(html, line); + } + } + appendLink(html, url); + return html.toString(); + } + + private static String plainParameter(final String label, final String name, final String details) { + return label + " " + name + (details.isBlank() ? "" : "\n" + details); + } + + private static List detailLines(final String details) { + if (details == null || details.isBlank()) { + return List.of(); + } + return List.of(details.split("\\R")); + } + + private static void appendMap(final StringBuilder html, final String title, final java.util.Map values) { + if (values.isEmpty()) { + return; + } + html.append("

").append(escape(title)).append("

    "); + values.forEach((name, details) -> html.append("
  • ") + .append(escape(name)) + .append("") + .append(details.isBlank() ? "" : " - " + escape(firstLine(details))) + .append("
  • ")); + html.append("
"); + } + + private static void appendList(final StringBuilder html, final String title, final List values) { + if (values.isEmpty()) { + return; + } + html.append("

").append(escape(title)).append("

    "); + values.forEach(value -> html.append("
  • ").append(escape(value)).append("
  • ")); + html.append("
"); + } + + private static void appendDetail(final StringBuilder html, final String name, final String value) { + if (!value.isBlank()) { + html.append("

").append(escape(name)).append(": ").append(escape(value)).append("

"); + } + } + + private static void appendParagraph(final StringBuilder html, final String value) { + if (value != null && !value.isBlank()) { + html.append("

").append(escape(value)).append("

"); + } + } + + private static void appendLink(final StringBuilder html, final String url) { + appendLink(html, url, url); + } + + private static void appendLink(final StringBuilder html, final String url, final String label) { + if (url != null && !url.isBlank()) { + html.append("

").append(escape(label)).append("

"); + } + } + + private static String firstLine(final String text) { + return detailLines(text).stream().findFirst().orElse(""); + } + + private static Optional textElement(final PsiElement element) { + PsiElement current = element; + while (current != null && current.getParent() != current) { + if (PsiElementHelper.isTextElement(current) || current instanceof YAMLScalar) { + return Optional.of(current); + } + current = current.getParent(); + } + return Optional.empty(); + } + + private static Optional keyValueAt(final PsiElement element) { + PsiElement current = element; + while (current != null && current.getParent() != current) { + if (current instanceof final YAMLKeyValue keyValue) { + return Optional.of(keyValue); + } + current = current.getParent(); + } + return Optional.empty(); + } + + private static String escape(final String value) { + return ofNullable(value).orElse("") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """); + } + + record DocPayload(String title, String html, String hint) { + } + + private record StepOutputSource(String stepId, String stepName, String usesValue, GitHubAction action) { + + private String stepLabel() { + if (!stepName.isBlank() && !stepId.isBlank()) { + return stepName + " (" + stepId + ")"; + } + if (!stepName.isBlank()) { + return stepName; + } + return stepId; + } + + private String sourceLabel() { + if (action == null) { + return ""; + } + final String kind = action.isAction() + ? message("documentation.externalAction.label") + : message("documentation.reusableWorkflow.label"); + final String displayName = action.displayName(); + final String description = action.description(); + return kind + (displayName.isBlank() ? "" : ": " + displayName) + + (description.isBlank() ? "" : " - " + description); + } + + private Optional outputDescription(final String outputName) { + return ofNullable(action) + .filter(GitHubAction::isResolved) + .map(GitHubAction::freshOutputs) + .map(outputs -> outputs.get(outputName)); + } + + private Optional url() { + return ofNullable(action) + .map(GitHubAction::githubUrl) + .filter(url -> !url.isBlank()) + .or(() -> ofNullable(action) + .filter(GitHubAction::isLocal) + .map(GitHubAction::downloadUrl) + .filter(url -> !url.isBlank())); + } + } + + private static final class WorkflowDocumentationElement extends FakePsiElement { + private final PsiElement delegate; + private final DocPayload payload; + + private WorkflowDocumentationElement(final PsiElement delegate, final DocPayload payload) { + this.delegate = delegate; + this.payload = payload; + } + + private DocPayload payload() { + return payload; + } + + @Override + public PsiElement getParent() { + return delegate.getParent(); + } + + @Override + public PsiFile getContainingFile() { + return delegate.getContainingFile(); + } + + @Override + public TextRange getTextRange() { + return delegate.getTextRange(); + } + + @Override + public int getTextOffset() { + return delegate.getTextOffset(); + } + + @Override + public PsiManager getManager() { + return delegate.getManager(); + } + + @Override + public boolean isValid() { + return delegate.isValid(); + } + + @Override + public String getName() { + return payload.title(); + } + + @Override + public String getText() { + return payload.title(); + } + + @Override + public String toString() { + return "GitHub workflow documentation"; + } + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepository.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepository.java new file mode 100644 index 0000000..f21de2d --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepository.java @@ -0,0 +1,12 @@ +package com.github.yunabraska.githubworkflow.services; + +/** + * Resolved GitHub repository endpoint for workflow execution. + * + * @param webUrl browser base URL + * @param apiUrl REST API base URL + * @param owner repository owner + * @param repo repository name + */ +public record WorkflowRepository(String webUrl, String apiUrl, String owner, String repo) { +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolver.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolver.java new file mode 100644 index 0000000..daa2bf0 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolver.java @@ -0,0 +1,102 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectUtil; +import com.intellij.openapi.vfs.VirtualFile; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Resolves the GitHub repository for the current project from `.git/config`. + */ +public final class WorkflowRepositoryResolver { + + private static final Pattern HTTPS_REMOTE = Pattern.compile("https?://([^/]+)/([^/]+)/([^/]+?)(?:[.]git)?/?$"); + private static final Pattern SSH_REMOTE = Pattern.compile("(?:git@|ssh://git@)([^:/]+)[:/]([^/]+)/([^/]+?)(?:[.]git)?/?$"); + + public Optional resolve(final Project project) { + return Optional.ofNullable(project) + .map(ProjectUtil::guessProjectDir) + .map(VirtualFile::getPath) + .map(Path::of) + .flatMap(this::resolve); + } + + public Optional resolve(final Project project, final VirtualFile file) { + return repositoryRoot(file) + .flatMap(this::resolve) + .or(() -> resolve(project)); + } + + Optional resolve(final Path projectDir) { + return readGitConfig(projectDir) + .flatMap(WorkflowRepositoryResolver::firstOriginUrl) + .flatMap(WorkflowRepositoryResolver::fromRemoteUrl); + } + + static Optional fromRemoteUrl(final String remoteUrl) { + return match(HTTPS_REMOTE, remoteUrl).or(() -> match(SSH_REMOTE, remoteUrl)); + } + + private static Optional match(final Pattern pattern, final String remoteUrl) { + final Matcher matcher = pattern.matcher(Optional.ofNullable(remoteUrl).orElse("").trim()); + if (!matcher.matches()) { + return Optional.empty(); + } + final String host = matcher.group(1); + final String owner = matcher.group(2); + final String repo = matcher.group(3); + final String webUrl = "https://" + host; + final String apiUrl = "github.com".equalsIgnoreCase(host) + ? "https://api.github.com" + : webUrl + "/api/v3"; + return Optional.of(new WorkflowRepository(webUrl, apiUrl, owner, repo)); + } + + private static Optional readGitConfig(final Path projectDir) { + final Path config = projectDir.resolve(".git").resolve("config"); + if (!Files.isRegularFile(config)) { + return Optional.empty(); + } + try { + return Optional.of(Files.readString(config)); + } catch (final IOException ignored) { + return Optional.empty(); + } + } + + private static Optional repositoryRoot(final VirtualFile file) { + Path current = Optional.ofNullable(file) + .map(VirtualFile::getPath) + .map(Path::of) + .map(Path::getParent) + .orElse(null); + while (current != null) { + if (Files.isRegularFile(current.resolve(".git").resolve("config"))) { + return Optional.of(current); + } + current = current.getParent(); + } + return Optional.empty(); + } + + private static Optional firstOriginUrl(final String config) { + boolean inOrigin = false; + for (final String line : config.split("\\R")) { + final String trimmed = line.trim(); + if (trimmed.startsWith("[remote ")) { + inOrigin = trimmed.equals("[remote \"origin\"]"); + continue; + } + if (inOrigin && trimmed.startsWith("url =")) { + return Optional.of(trimmed.substring("url =".length()).trim()).filter(value -> !value.isBlank()); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java new file mode 100644 index 0000000..076d004 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java @@ -0,0 +1,658 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.intellij.openapi.project.Project; + +import java.io.IOException; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Small GitHub Actions REST client for workflow dispatch, status polling, cancellation, and logs. + */ +public final class WorkflowRunClient { + + private static final String API_VERSION = "2026-03-10"; + private static final Duration TIMEOUT = Duration.ofSeconds(20); + + private final HttpTransport transport; + private final AuthorizationProvider authorizationProvider; + private final ConcurrentMap successfulAuthorizations = new ConcurrentHashMap<>(); + + public WorkflowRunClient() { + this((Project) null); + } + + public WorkflowRunClient(final Project project) { + this(new JdkHttpTransport(HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(5)) + .followRedirects(HttpClient.Redirect.NORMAL) + .build()), request -> GitHubRequestAuthorizations.forApiUrl(request.apiUrl(), request.tokenEnvVar(), project)); + } + + WorkflowRunClient(final HttpTransport transport) { + this(transport, request -> GitHubRequestAuthorizations.forApiUrl(request.apiUrl(), request.tokenEnvVar(), null)); + } + + WorkflowRunClient(final HttpTransport transport, final AuthorizationProvider authorizationProvider) { + this.transport = transport; + this.authorizationProvider = authorizationProvider; + } + + public DispatchResult dispatch(final WorkflowRunRequest request) throws IOException, InterruptedException { + final HttpResponse response = send( + request, + "POST", + workflowUrl(request) + "/dispatches", + dispatchBody(request), + "GitHub workflow dispatch" + ); + final JsonObject json = parseObject(response.body()); + return new DispatchResult( + longValue(json, "workflow_run_id").orElse(-1L), + stringValue(json, "run_url").orElse(""), + stringValue(json, "html_url").orElse("") + ); + } + + public RunStatus status(final WorkflowRunRequest request, final long runId) throws IOException, InterruptedException { + final HttpResponse response = send( + request, + "GET", + runUrl(request, runId), + "", + "GitHub workflow status" + ); + final JsonObject json = parseObject(response.body()); + return new RunStatus( + longValue(json, "id").orElse(runId), + stringValue(json, "status").orElse("unknown"), + stringValue(json, "conclusion").orElse(""), + stringValue(json, "html_url").orElse("") + ); + } + + public CancelResult cancel(final WorkflowRunRequest request, final long runId) throws IOException, InterruptedException { + final HttpResponse response = send( + request, + "POST", + runUrl(request, runId) + "/cancel", + "", + "GitHub workflow cancel" + ); + return new CancelResult(response.statusCode(), response.statusCode() / 100 == 2); + } + + /** + * Requests GitHub to re-run a completed workflow run. + * + * @param request workflow repository and authorization context + * @param runId GitHub Actions run id + * @param failedOnly whether only failed jobs should be re-run + * @return HTTP status and whether GitHub accepted the re-run + * @throws IOException when GitHub rejects the request or the network call fails + * @throws InterruptedException when the IDE cancels the remote call + */ + public RerunResult rerun( + final WorkflowRunRequest request, + final long runId, + final boolean failedOnly + ) throws IOException, InterruptedException { + final HttpResponse response = send( + request, + "POST", + runUrl(request, runId) + (failedOnly ? "/rerun-failed-jobs" : "/rerun"), + "", + failedOnly ? "GitHub workflow failed jobs rerun" : "GitHub workflow rerun" + ); + return new RerunResult(response.statusCode(), response.statusCode() / 100 == 2); + } + + /** + * Deletes one completed workflow run from the remote repository. + * + * @param request workflow repository and authorization context + * @param runId GitHub Actions run id + * @return HTTP status and whether GitHub accepted the deletion + * @throws IOException when GitHub rejects the request or the network call fails + * @throws InterruptedException when the IDE cancels the remote call + */ + public DeleteResult delete(final WorkflowRunRequest request, final long runId) throws IOException, InterruptedException { + final HttpResponse response = send( + request, + "DELETE", + runUrl(request, runId), + "", + "GitHub workflow run delete" + ); + return new DeleteResult(response.statusCode(), response.statusCode() / 100 == 2); + } + + public Optional latestRun(final WorkflowRunRequest request) throws IOException, InterruptedException { + final HttpResponse response = send( + request, + "GET", + workflowUrl(request) + "/runs?branch=" + encode(request.ref()) + "&event=workflow_dispatch&per_page=1", + "", + "GitHub workflow run discovery" + ); + final JsonObject json = parseObject(response.body()); + return Optional.ofNullable(json.get("workflow_runs")) + .filter(JsonElement::isJsonArray) + .map(JsonElement::getAsJsonArray) + .filter(runs -> !runs.isEmpty()) + .map(runs -> runs.get(0)) + .filter(JsonElement::isJsonObject) + .map(JsonElement::getAsJsonObject) + .map(run -> new RunStatus( + longValue(run, "id").orElse(-1L), + stringValue(run, "status").orElse("unknown"), + stringValue(run, "conclusion").orElse(""), + stringValue(run, "html_url").orElse("") + )) + .filter(run -> run.runId() >= 0); + } + + public String logs(final WorkflowRunRequest request, final long runId) throws IOException, InterruptedException { + final StringBuilder result = new StringBuilder(); + for (final JobStatus job : jobs(request, runId)) { + result.append("== ").append(job.name()).append(" [").append(job.status()).append(resultSuffix(job.conclusion())).append("]\n"); + final String logs = jobLogs(request, job.id()); + if (hasText(logs)) { + result.append(logs.stripTrailing()).append("\n"); + } + } + return result.toString(); + } + + /** + * Lists the artifacts produced by one workflow run. + * + * @param request workflow repository and authorization context + * @param runId GitHub Actions run id + * @return immutable list of artifacts known to GitHub for the run + * @throws IOException when GitHub rejects the request or the network call fails + * @throws InterruptedException when the IDE cancels the remote call + */ + public List artifacts(final WorkflowRunRequest request, final long runId) throws IOException, InterruptedException { + final HttpResponse response = send( + request, + "GET", + runUrl(request, runId) + "/artifacts?per_page=100", + "", + "GitHub workflow artifacts" + ); + final JsonObject json = parseObject(response.body()); + final List result = new ArrayList<>(); + Optional.ofNullable(json.get("artifacts")) + .filter(JsonElement::isJsonArray) + .map(JsonElement::getAsJsonArray) + .ifPresent(artifacts -> artifacts.forEach(artifact -> { + if (artifact.isJsonObject()) { + final JsonObject object = artifact.getAsJsonObject(); + result.add(new ArtifactStatus( + longValue(object, "id").orElse(-1L), + stringValue(object, "name").orElse("artifact"), + longValue(object, "size_in_bytes").orElse(0L), + booleanValue(object, "expired").orElse(false), + stringValue(object, "archive_download_url").orElse("") + )); + } + })); + return result.stream().filter(artifact -> artifact.id() >= 0).toList(); + } + + /** + * Downloads one workflow artifact archive as bytes. + * + * @param request workflow repository and authorization context + * @param artifactId GitHub Actions artifact id + * @return zip archive bytes + * @throws IOException when GitHub rejects the request or the network call fails + * @throws InterruptedException when the IDE cancels the remote call + */ + public byte[] artifactZip(final WorkflowRunRequest request, final long artifactId) throws IOException, InterruptedException { + final HttpResponse response = sendBytes( + request, + "GET", + request.apiUrl() + "/repos/" + encode(request.owner()) + "/" + encode(request.repo()) + "/actions/artifacts/" + artifactId + "/zip", + "", + "GitHub workflow artifact download" + ); + return response.body(); + } + + public List jobs(final WorkflowRunRequest request, final long runId) throws IOException, InterruptedException { + final HttpResponse response = send( + request, + "GET", + runUrl(request, runId) + "/jobs", + "", + "GitHub workflow jobs" + ); + final JsonObject json = parseObject(response.body()); + final List result = new ArrayList<>(); + Optional.ofNullable(json.get("jobs")) + .filter(JsonElement::isJsonArray) + .map(JsonElement::getAsJsonArray) + .ifPresent(jobs -> jobs.forEach(job -> { + if (job.isJsonObject()) { + final JsonObject object = job.getAsJsonObject(); + result.add(new JobStatus( + longValue(object, "id").orElse(-1L), + stringValue(object, "name").orElse("job"), + stringValue(object, "status").orElse("unknown"), + stringValue(object, "conclusion").orElse(""), + stringValue(object, "html_url").orElse("") + )); + } + })); + return result.stream().filter(job -> job.id() >= 0).toList(); + } + + public String jobLogs(final WorkflowRunRequest request, final long jobId) throws IOException, InterruptedException { + final HttpResponse response = send( + request, + "GET", + request.apiUrl() + "/repos/" + encode(request.owner()) + "/" + encode(request.repo()) + "/actions/jobs/" + jobId + "/logs", + "", + "GitHub workflow job logs" + ); + return response.body(); + } + + private HttpResponse send( + final WorkflowRunRequest workflow, + final String method, + final String url, + final String body, + final String operation + ) throws IOException, InterruptedException { + WorkflowRunHttpException lastFailure = null; + boolean authenticatedRateLimitFailure = false; + final String authorizationCacheKey = authorizationCacheKey(workflow); + for (final GitHubRequestAuthorizations.Authorization authorization : authorizations(workflow, authorizationCacheKey)) { + if (!authorization.authenticated() && authenticatedRateLimitFailure) { + break; + } + final HttpResponse response = transport.send(request(workflow, method, url, body, authorization)); + if (response.statusCode() / 100 == 2) { + if (authorization.authenticated()) { + successfulAuthorizations.put(authorizationCacheKey, authorization); + } + return response; + } + lastFailure = failure(operation, response); + if (authorization.authenticated() && rateLimitExceeded(response)) { + authenticatedRateLimitFailure = true; + } + if (!shouldTryNextAuthorization(response.statusCode())) { + throw lastFailure; + } + } + throw lastFailure == null + ? new IOException(operation + " failed: no authorization candidates were available.") + : lastFailure; + } + + private HttpResponse sendBytes( + final WorkflowRunRequest workflow, + final String method, + final String url, + final String body, + final String operation + ) throws IOException, InterruptedException { + WorkflowRunHttpException lastFailure = null; + boolean authenticatedRateLimitFailure = false; + final String authorizationCacheKey = authorizationCacheKey(workflow); + for (final GitHubRequestAuthorizations.Authorization authorization : authorizations(workflow, authorizationCacheKey)) { + if (!authorization.authenticated() && authenticatedRateLimitFailure) { + break; + } + final HttpResponse response = transport.sendBytes(request(workflow, method, url, body, authorization)); + if (response.statusCode() / 100 == 2) { + if (authorization.authenticated()) { + successfulAuthorizations.put(authorizationCacheKey, authorization); + } + return response; + } + lastFailure = failureBytes(operation, response); + if (authorization.authenticated() && rateLimitExceeded( + response.statusCode(), + response.headers(), + new String(Optional.ofNullable(response.body()).orElseGet(() -> new byte[0]), StandardCharsets.UTF_8) + )) { + authenticatedRateLimitFailure = true; + } + if (!shouldTryNextAuthorization(response.statusCode())) { + throw lastFailure; + } + } + throw lastFailure == null + ? new IOException(operation + " failed: no authorization candidates were available.") + : lastFailure; + } + + private List authorizations( + final WorkflowRunRequest workflow, + final String authorizationCacheKey + ) { + final List result = new ArrayList<>(); + Optional.ofNullable(successfulAuthorizations.get(authorizationCacheKey)).ifPresent(result::add); + final List authorizations = authorizationProvider.authorizations(workflow); + if (authorizations == null || authorizations.isEmpty()) { + result.add(GitHubRequestAuthorizations.Authorization.anonymous()); + } else { + result.addAll(authorizations); + } + return result.stream() + .filter(WorkflowRunClient::knownAuthorization) + .distinct() + .toList(); + } + + private static boolean knownAuthorization(final GitHubRequestAuthorizations.Authorization authorization) { + return authorization != null; + } + + private static HttpRequest request( + final WorkflowRunRequest workflow, + final String method, + final String url, + final String body, + final GitHubRequestAuthorizations.Authorization authorization + ) { + final HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(url)) + .timeout(TIMEOUT) + .header("Accept", "application/vnd.github+json") + .header("X-GitHub-Api-Version", API_VERSION) + .header("User-Agent", "GitHub-Workflow-Plugin"); + if (authorization.authenticated()) { + builder.header("Authorization", authorization.authorizationHeader()); + } + if ("POST".equals(method)) { + builder.header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8)); + } else if ("DELETE".equals(method)) { + builder.DELETE(); + } else { + builder.GET(); + } + return builder.build(); + } + + private static WorkflowRunHttpException failure(final String operation, final HttpResponse response) { + final boolean accountActionRecommended = needsAccountAction(response); + final String hint = accountActionRecommended + ? "\nAdd or refresh GitHub accounts in " + GitHubRequestAuthorizations.settingsHint() + "." + : ""; + final String summary = responseSummary(response); + return new WorkflowRunHttpException( + operation + " failed with HTTP " + response.statusCode() + (summary.isEmpty() ? "" : ": " + summary) + hint, + response.statusCode(), + response.body(), + accountActionRecommended + ); + } + + private static WorkflowRunHttpException failureBytes(final String operation, final HttpResponse response) { + final String body = new String(Optional.ofNullable(response.body()).orElseGet(() -> new byte[0]), StandardCharsets.UTF_8); + final boolean accountActionRecommended = needsAccountAction(response.statusCode(), response.headers(), body); + final String hint = accountActionRecommended + ? "\nAdd or refresh GitHub accounts in " + GitHubRequestAuthorizations.settingsHint() + "." + : ""; + final String summary = responseSummary(response.statusCode(), response.headers(), body); + return new WorkflowRunHttpException( + operation + " failed with HTTP " + response.statusCode() + (summary.isEmpty() ? "" : ": " + summary) + hint, + response.statusCode(), + body, + accountActionRecommended + ); + } + + private static String responseSummary(final HttpResponse response) { + return responseSummary(response.statusCode(), response.headers(), response.body()); + } + + private static String responseSummary(final int statusCode, final HttpHeaders headers, final String responseBody) { + final String body = Optional.ofNullable(responseBody).orElse("").strip(); + if (body.isEmpty()) { + return ""; + } + final String contentType = headers + .firstValue("Content-Type") + .orElse("") + .toLowerCase(); + if (contentType.contains("text/html") || body.startsWith(" response) { + return rateLimitExceeded(response.statusCode(), response.headers(), response.body()); + } + + private static boolean rateLimitExceeded(final int statusCode, final HttpHeaders headers, final String body) { + if (statusCode != 403 && statusCode != 429) { + return false; + } + if (headers + .firstValue("x-ratelimit-remaining") + .map(String::trim) + .filter("0"::equals) + .isPresent()) { + return true; + } + return Optional.ofNullable(body) + .map(value -> value.toLowerCase(Locale.ROOT)) + .filter(value -> value.contains("rate limit")) + .isPresent(); + } + + private static boolean needsAccountAction(final HttpResponse response) { + return needsAccountAction(response.statusCode(), response.headers(), response.body()); + } + + private static boolean needsAccountAction(final int statusCode, final HttpHeaders headers, final String body) { + if (statusCode == 401 || statusCode == 429) { + return true; + } + if (statusCode != 403) { + return false; + } + return !mustHaveAdminRights(body) || rateLimitExceeded(statusCode, headers, body); + } + + private static boolean mustHaveAdminRights(final HttpResponse response) { + return mustHaveAdminRights(response.body()); + } + + private static boolean mustHaveAdminRights(final String body) { + return Optional.ofNullable(body) + .map(value -> value.toLowerCase(Locale.ROOT)) + .filter(value -> value.contains("must have admin rights")) + .isPresent(); + } + + private static String workflowUrl(final WorkflowRunRequest request) { + return request.apiUrl() + "/repos/" + encode(request.owner()) + "/" + encode(request.repo()) + "/actions/workflows/" + encode(workflowId(request.workflowPath())); + } + + private static String authorizationCacheKey(final WorkflowRunRequest request) { + return Optional.ofNullable(request.apiUrl()).orElse("") + "|" + Optional.ofNullable(request.tokenEnvVar()).orElse(""); + } + + private static String runUrl(final WorkflowRunRequest request, final long runId) { + return request.apiUrl() + "/repos/" + encode(request.owner()) + "/" + encode(request.repo()) + "/actions/runs/" + runId; + } + + private static String workflowId(final String workflowPath) { + final String normalized = Optional.ofNullable(workflowPath).orElse("").replace('\\', '/'); + final int slash = normalized.lastIndexOf('/'); + return slash < 0 ? normalized : normalized.substring(slash + 1); + } + + private static String dispatchBody(final WorkflowRunRequest request) { + final StringJoiner inputs = new StringJoiner(","); + request.inputs().entrySet().stream() + .filter(entry -> hasText(entry.getKey())) + .limit(25) + .forEach(entry -> inputs.add(quote(entry.getKey()) + ":" + quote(entry.getValue()))); + final String inputsJson = inputs.length() == 0 ? "" : ",\"inputs\":{" + inputs + "}"; + return "{\"ref\":" + quote(request.ref()) + inputsJson + "}"; + } + + private static String resultSuffix(final String conclusion) { + return hasText(conclusion) ? "/" + conclusion : ""; + } + + private static JsonObject parseObject(final String body) { + if (!hasText(body)) { + return new JsonObject(); + } + final JsonElement element = JsonParser.parseString(body); + return element.isJsonObject() ? element.getAsJsonObject() : new JsonObject(); + } + + private static Optional stringValue(final JsonObject object, final String name) { + return Optional.ofNullable(object.get(name)) + .filter(JsonElement::isJsonPrimitive) + .map(JsonElement::getAsString) + .filter(WorkflowRunClient::hasText); + } + + private static Optional longValue(final JsonObject object, final String name) { + return Optional.ofNullable(object.get(name)) + .filter(JsonElement::isJsonPrimitive) + .map(value -> { + try { + return value.getAsLong(); + } catch (final NumberFormatException ignored) { + return -1L; + } + }) + .filter(value -> value >= 0); + } + + private static Optional booleanValue(final JsonObject object, final String name) { + return Optional.ofNullable(object.get(name)) + .filter(JsonElement::isJsonPrimitive) + .map(JsonElement::getAsBoolean); + } + + private static String encode(final String value) { + return URLEncoder.encode(Optional.ofNullable(value).orElse(""), StandardCharsets.UTF_8).replace("+", "%20"); + } + + private static String quote(final String value) { + return "\"" + Optional.ofNullable(value).orElse("") + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + "\""; + } + + private static boolean hasText(final String value) { + return value != null && !value.isBlank(); + } + + interface HttpTransport { + HttpResponse send(HttpRequest request) throws IOException, InterruptedException; + + default HttpResponse sendBytes(final HttpRequest request) throws IOException, InterruptedException { + throw new IOException("Binary transport is not available."); + } + } + + interface AuthorizationProvider { + List authorizations(WorkflowRunRequest request); + } + + private record JdkHttpTransport(HttpClient client) implements HttpTransport { + @Override + public HttpResponse send(final HttpRequest request) throws IOException, InterruptedException { + return client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + } + + @Override + public HttpResponse sendBytes(final HttpRequest request) throws IOException, InterruptedException { + return client.send(request, HttpResponse.BodyHandlers.ofByteArray()); + } + } + + public record DispatchResult(long runId, String runUrl, String htmlUrl) { + } + + public record RunStatus(long runId, String status, String conclusion, String htmlUrl) { + public boolean completed() { + return "completed".equals(status); + } + } + + public record CancelResult(int statusCode, boolean accepted) { + } + + public record RerunResult(int statusCode, boolean accepted) { + } + + public record DeleteResult(int statusCode, boolean accepted) { + } + + public record JobStatus(long id, String name, String status, String conclusion, String htmlUrl) { + } + + public record ArtifactStatus(long id, String name, long sizeInBytes, boolean expired, String archiveDownloadUrl) { + } + + public static final class WorkflowRunHttpException extends IOException { + + private final int statusCode; + private final String body; + private final boolean accountActionRecommended; + + public WorkflowRunHttpException( + final String message, + final int statusCode, + final String body, + final boolean accountActionRecommended + ) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.accountActionRecommended = accountActionRecommended; + } + + public int statusCode() { + return statusCode; + } + + public String body() { + return body; + } + + public boolean accountActionRecommended() { + return accountActionRecommended; + } + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfiguration.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfiguration.java new file mode 100644 index 0000000..a853563 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfiguration.java @@ -0,0 +1,178 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.CommandLineState; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.RunConfigurationBase; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.RuntimeConfigurationError; +import com.intellij.execution.configurations.RuntimeConfigurationException; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.InvalidDataException; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * Run configuration that dispatches a workflow_dispatch event and follows the resulting run. + */ +public final class WorkflowRunConfiguration extends RunConfigurationBase { + + private String apiUrl = "https://api.github.com"; + private String owner = ""; + private String repo = ""; + private String workflowPath = ""; + private String ref = "main"; + private String tokenEnvVar = ""; + private String inputsText = ""; + + WorkflowRunConfiguration(final Project project, final ConfigurationFactory factory, final String name) { + super(project, factory, name); + } + + @Override + public @NotNull SettingsEditor getConfigurationEditor() { + return new WorkflowRunSettingsEditor(); + } + + @Override + public @Nullable RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment environment) { + return new CommandLineState(environment) { + @Override + protected @NotNull ProcessHandler startProcess() throws ExecutionException { + return new WorkflowRunProcessHandler(getProject(), toRequest(), new WorkflowRunClient(getProject()), environment.getExecutor()); + } + }; + } + + @Override + public void checkConfiguration() throws RuntimeConfigurationException { + if (isBlank(apiUrl)) { + throw new RuntimeConfigurationError(GitHubWorkflowBundle.message("workflow.run.error.apiUrl")); + } + if (isBlank(owner) || isBlank(repo)) { + throw new RuntimeConfigurationError(GitHubWorkflowBundle.message("workflow.run.error.repository")); + } + if (isBlank(workflowPath)) { + throw new RuntimeConfigurationError(GitHubWorkflowBundle.message("workflow.run.error.workflow")); + } + if (isBlank(ref)) { + throw new RuntimeConfigurationError(GitHubWorkflowBundle.message("workflow.run.error.ref")); + } + if (WorkflowDispatchInputs.parseKeyValueText(inputsText).size() > 25) { + throw new RuntimeConfigurationError(GitHubWorkflowBundle.message("workflow.run.error.inputs")); + } + } + + @Override + public void readExternal(@NotNull final Element element) throws InvalidDataException { + super.readExternal(element); + apiUrl = value(element, "apiUrl", apiUrl); + owner = value(element, "owner", owner); + repo = value(element, "repo", repo); + workflowPath = value(element, "workflowPath", workflowPath); + ref = value(element, "ref", ref); + tokenEnvVar = value(element, "tokenEnvVar", tokenEnvVar); + inputsText = value(element, "inputsText", inputsText); + } + + @Override + public void writeExternal(@NotNull final Element element) { + super.writeExternal(element); + element.setAttribute("apiUrl", apiUrl); + element.setAttribute("owner", owner); + element.setAttribute("repo", repo); + element.setAttribute("workflowPath", workflowPath); + element.setAttribute("ref", ref); + element.setAttribute("tokenEnvVar", tokenEnvVar); + element.setAttribute("inputsText", inputsText); + } + + WorkflowRunRequest toRequest() { + final Map inputs = WorkflowDispatchInputs.parseKeyValueText(inputsText); + return new WorkflowRunRequest(apiUrl, owner, repo, workflowPath, ref, inputs, tokenEnvVar); + } + + public String apiUrl() { + return apiUrl; + } + + public WorkflowRunConfiguration apiUrl(final String apiUrl) { + this.apiUrl = clean(apiUrl); + return this; + } + + public String owner() { + return owner; + } + + public WorkflowRunConfiguration owner(final String owner) { + this.owner = clean(owner); + return this; + } + + public String repo() { + return repo; + } + + public WorkflowRunConfiguration repo(final String repo) { + this.repo = clean(repo); + return this; + } + + public String workflowPath() { + return workflowPath; + } + + public WorkflowRunConfiguration workflowPath(final String workflowPath) { + this.workflowPath = clean(workflowPath); + return this; + } + + public String ref() { + return ref; + } + + public WorkflowRunConfiguration ref(final String ref) { + this.ref = clean(ref); + return this; + } + + public String tokenEnvVar() { + return tokenEnvVar; + } + + public WorkflowRunConfiguration tokenEnvVar(final String tokenEnvVar) { + this.tokenEnvVar = clean(tokenEnvVar); + return this; + } + + public String inputsText() { + return inputsText; + } + + public WorkflowRunConfiguration inputsText(final String inputsText) { + this.inputsText = inputsText == null ? "" : inputsText; + return this; + } + + private static String value(final Element element, final String name, final String fallback) { + final String value = element.getAttributeValue(name); + return value == null ? fallback : value; + } + + private static String clean(final String value) { + return value == null ? "" : value.trim(); + } + + private static boolean isBlank(final String value) { + return value == null || value.isBlank(); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationProducer.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationProducer.java new file mode 100644 index 0000000..da107b1 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationProducer.java @@ -0,0 +1,93 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper; +import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; +import com.intellij.execution.actions.ConfigurationContext; +import com.intellij.execution.actions.LazyRunConfigurationProducer; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; +import java.util.Optional; + +/** + * Creates GitHub Workflow run configurations from workflow YAML files. + */ +public final class WorkflowRunConfigurationProducer extends LazyRunConfigurationProducer { + + private static final WorkflowDispatchInputs DISPATCH_INPUTS = new WorkflowDispatchInputs(); + + @Override + public @NotNull ConfigurationFactory getConfigurationFactory() { + return WorkflowRunConfigurationType.getInstance().factory(); + } + + @Override + protected boolean setupConfigurationFromContext( + @NotNull final WorkflowRunConfiguration configuration, + @NotNull final ConfigurationContext context, + @NotNull final Ref sourceElement + ) { + final PsiFile file = workflowFile(context.getPsiLocation()).orElse(null); + if (file == null) { + return false; + } + final Project project = context.getProject(); + final WorkflowRepository repository = new WorkflowRepositoryResolver().resolve(project, file.getVirtualFile()).orElse(null); + if (repository == null) { + return false; + } + final String path = workflowPath(project, file.getVirtualFile()).orElse(file.getName()); + configuration.setName(GitHubWorkflowBundle.message("workflow.run.configuration.name", file.getName())); + configuration.apiUrl(repository.apiUrl()) + .owner(repository.owner()) + .repo(repository.repo()) + .workflowPath(path) + .ref(new WorkflowCurrentBranchResolver().resolve(project, file.getVirtualFile()).orElse("main")) + .tokenEnvVar("") + .inputsText(DISPATCH_INPUTS.defaultsText(file.getText())); + sourceElement.set(file); + return true; + } + + @Override + public boolean isConfigurationFromContext( + @NotNull final WorkflowRunConfiguration configuration, + @NotNull final ConfigurationContext context + ) { + return workflowFile(context.getPsiLocation()) + .flatMap(file -> workflowPath(context.getProject(), file.getVirtualFile())) + .filter(path -> path.equals(configuration.workflowPath())) + .filter(path -> new WorkflowCurrentBranchResolver().resolve(context.getProject()) + .map(branch -> branch.equals(configuration.ref())) + .orElse(true)) + .isPresent(); + } + + private static Optional workflowFile(final PsiElement element) { + return Optional.ofNullable(element) + .map(PsiElement::getContainingFile) + .filter(file -> Optional.ofNullable(file.getVirtualFile()) + .flatMap(PsiElementHelper::toPath) + .filter(GitHubWorkflowHelper::isWorkflowPath) + .isPresent()); + } + + static Optional workflowPath(final Project project, final VirtualFile file) { + return Optional.ofNullable(project) + .flatMap(p -> Optional.ofNullable(com.intellij.openapi.project.ProjectUtil.guessProjectDir(p))) + .map(VirtualFile::getPath) + .map(Path::of) + .flatMap(root -> Optional.ofNullable(file) + .map(VirtualFile::getPath) + .map(Path::of) + .map(root::relativize) + .map(Path::toString) + .map(path -> path.replace('\\', '/'))); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationType.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationType.java new file mode 100644 index 0000000..d67e3cf --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationType.java @@ -0,0 +1,70 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.ConfigurationType; +import com.intellij.execution.configurations.ConfigurationTypeUtil; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +import javax.swing.Icon; + +/** + * Run configuration type used to dispatch GitHub Actions workflows from the IDE. + */ +public final class WorkflowRunConfigurationType implements ConfigurationType { + + public static final String ID = "GitHubWorkflow.RunConfiguration"; + + private final ConfigurationFactory factory = new WorkflowRunConfigurationFactory(this); + + public static WorkflowRunConfigurationType getInstance() { + return ConfigurationTypeUtil.findConfigurationType(WorkflowRunConfigurationType.class); + } + + @Override + public String getDisplayName() { + return GitHubWorkflowBundle.message("workflow.run.configuration.display"); + } + + @Override + public String getConfigurationTypeDescription() { + return GitHubWorkflowBundle.message("workflow.run.configuration.description"); + } + + @Override + public Icon getIcon() { + return AllIcons.Actions.Execute; + } + + @Override + public @NotNull String getId() { + return ID; + } + + @Override + public ConfigurationFactory[] getConfigurationFactories() { + return new ConfigurationFactory[]{factory}; + } + + public ConfigurationFactory factory() { + return factory; + } + + private static final class WorkflowRunConfigurationFactory extends ConfigurationFactory { + private WorkflowRunConfigurationFactory(final ConfigurationType type) { + super(type); + } + + @Override + public @NotNull String getId() { + return ID + ".Factory"; + } + + @Override + public RunConfiguration createTemplateConfiguration(@NotNull final Project project) { + return new WorkflowRunConfiguration(project, this, GitHubWorkflowBundle.message("workflow.run.configuration.display")); + } + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java new file mode 100644 index 0000000..39494eb --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java @@ -0,0 +1,1021 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.execution.Executor; +import com.intellij.execution.filters.TextConsoleBuilderFactory; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessListener; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.execution.ui.ConsoleViewContentType; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.execution.ui.RunContentManager; +import com.intellij.execution.ui.RunnerLayoutUi; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.ui.AnimatedIcon; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.OnePixelSplitter; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.ui.content.Content; +import com.intellij.util.concurrency.AppExecutorUtil; +import com.intellij.util.ui.JBUI; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JProgressBar; +import javax.swing.JTree; +import javax.swing.Timer; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BooleanSupplier; +import javax.swing.UIManager; + +/** + * Adds a JUnit-style workflow tree to the Run tool window and routes selected-node output to one detail console. + */ +final class WorkflowRunConsoleTabs implements WorkflowRunJobConsole { + + private static final int MAX_ATTACH_ATTEMPTS = 20; + private static final String CONTENT_ID = "github.workflow.jobs"; + private static final String DEFAULT_CONSOLE_TITLE = "Console"; + + private final Project project; + private final @Nullable Executor executor; + private final WorkflowRunProcessHandler processHandler; + private final WorkflowNode workflow = new WorkflowNode(); + private final ConcurrentMap jobs = new ConcurrentHashMap<>(); + private final ConcurrentMap groups = new ConcurrentHashMap<>(); + private final Object attachLock = new Object(); + private final AtomicBoolean attaching = new AtomicBoolean(false); + private final AtomicInteger attachAttempts = new AtomicInteger(0); + private final AtomicBoolean closed = new AtomicBoolean(false); + private final ProcessListener processListener = new ProcessListener() { + @Override + public void onTextAvailable(final @NotNull ProcessEvent event, final @NotNull Key outputType) { + workflow.print(event.getText(), processContentType(outputType)); + scheduleAttach(); + } + }; + private volatile TreeEntry selectedEntry = workflow; + private @Nullable ConsoleView detailConsole; + private @Nullable Content content; + private @Nullable DefaultTreeModel treeModel; + private @Nullable DefaultMutableTreeNode rootNode; + private @Nullable Tree tree; + private @Nullable JProgressBar progressBar; + private @Nullable RunnerLayoutUi runnerLayoutUi; + private @Nullable Timer animationTimer; + private final AtomicBoolean deleteInProgress = new AtomicBoolean(false); + private volatile long terminalRunId = -1; + private volatile String terminalConclusion = ""; + + WorkflowRunConsoleTabs(final Project project, final @Nullable Executor executor, final WorkflowRunProcessHandler processHandler) { + this.project = project; + this.executor = executor; + this.processHandler = processHandler; + this.processHandler.addProcessListener(processListener); + } + + @Override + public boolean jobStatus(final WorkflowRunClient.JobStatus job, final String text) { + return print(job, text, ConsoleViewContentType.SYSTEM_OUTPUT); + } + + @Override + public boolean jobStdout(final WorkflowRunClient.JobStatus job, final String text) { + return print(job, text, ConsoleViewContentType.NORMAL_OUTPUT); + } + + @Override + public boolean jobLog(final WorkflowRunClient.JobStatus job, final String text) { + if (executor == null || job.id() < 0) { + return false; + } + final JobNode node = jobNode(job); + node.update(job); + node.printLog(text); + scheduleAttach(); + return true; + } + + @Override + public boolean jobStderr(final WorkflowRunClient.JobStatus job, final String text) { + return print(job, text, ConsoleViewContentType.ERROR_OUTPUT); + } + + @Override + public void workflowStatus(final String text, final boolean error) { + workflow.print(text, error ? ConsoleViewContentType.ERROR_OUTPUT : ConsoleViewContentType.SYSTEM_OUTPUT); + scheduleAttach(); + } + + @Override + public void runFinished(final long runId, final String conclusion) { + terminalRunId = runId; + terminalConclusion = normalizeConclusion(conclusion); + jobs.values().forEach(job -> job.finish(terminalConclusion)); + processHandler.refreshArtifactAvailability(ignored -> + ApplicationManager.getApplication().invokeLater(this::updateContent)); + refreshTree(); + ApplicationManager.getApplication().invokeLater(this::stopAnimationTimer); + } + + @Override + public void runDeleted(final long runId) { + ApplicationManager.getApplication().invokeLater(this::closeRunContent); + } + + @Override + public void runDeleteFailed(final long runId) { + if (terminalRunId == runId) { + deleteInProgress.set(false); + ApplicationManager.getApplication().invokeLater(this::updateContent); + } + } + + @Override + public void close() { + if (closed.compareAndSet(false, true)) { + processHandler.removeProcessListener(processListener); + ApplicationManager.getApplication().invokeLater(this::stopAnimationTimer); + } + } + + private boolean print(final WorkflowRunClient.JobStatus job, final String text, final ConsoleViewContentType contentType) { + if (executor == null || job.id() < 0) { + return false; + } + final JobNode node = jobNode(job); + node.print(job, text, contentType); + scheduleAttach(); + return true; + } + + private Optional descriptor() { + if (project.isDisposed() || executor == null) { + return Optional.empty(); + } + return Optional.ofNullable(RunContentManager.getInstance(project).findContentDescriptor(executor, processHandler)); + } + + private JobNode jobNode(final WorkflowRunClient.JobStatus job) { + return jobs.computeIfAbsent(job.id(), ignored -> { + final JobNode node = new JobNode(job); + ApplicationManager.getApplication().invokeLater(() -> addJobNode(node)); + return node; + }); + } + + private void scheduleAttach() { + if (!attaching.compareAndSet(false, true)) { + return; + } + ApplicationManager.getApplication().invokeLater(this::attach); + } + + private void attach() { + final Optional descriptor = descriptor(); + if (descriptor.isEmpty() || descriptor.get().getRunnerLayoutUi() == null) { + retry(); + return; + } + + final RunnerLayoutUi layout = descriptor.get().getRunnerLayoutUi(); + final Content existing = layout.findContent(CONTENT_ID); + if (existing != null) { + synchronized (attachLock) { + content = existing; + } + keepOnlyWorkflowContent(layout, existing); + refreshTree(); + return; + } + + final ConsoleView createdConsole = TextConsoleBuilderFactory.getInstance() + .createBuilder(project) + .getConsole(); + final DefaultMutableTreeNode createdRoot = new DefaultMutableTreeNode(workflow); + final DefaultTreeModel createdModel = new DefaultTreeModel(createdRoot); + final Tree createdTree = new Tree(createdModel); + createdTree.setRootVisible(true); + createdTree.setShowsRootHandles(true); + createdTree.setCellRenderer(new JobTreeCellRenderer()); + createdTree.addTreeSelectionListener(event -> selectEntry(event.getPath())); + + final JProgressBar createdProgress = new JProgressBar(); + createdProgress.setBorder(JBUI.Borders.empty()); + createdProgress.setStringPainted(false); + createdProgress.setPreferredSize(new Dimension(0, JBUI.scale(3))); + + layout.getOptions().setTopRightToolbar(runToolbarActions(), "GitHubWorkflowRunToolbar"); + + final JPanel progressPanel = new JPanel(new BorderLayout()); + progressPanel.add(createdProgress, BorderLayout.CENTER); + + final JPanel detailPanel = new JPanel(new BorderLayout()); + detailPanel.add(progressPanel, BorderLayout.NORTH); + detailPanel.add(createdConsole.getComponent(), BorderLayout.CENTER); + + final OnePixelSplitter splitter = new OnePixelSplitter(false, 0.32f); + splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(createdTree)); + splitter.setSecondComponent(detailPanel); + + final Content createdContent = layout.createContent( + CONTENT_ID, + splitter, + GitHubWorkflowBundle.message("workflow.run.jobs.root"), + workflow.icon(), + focusTarget(createdConsole) + ); + createdContent.setDescription(GitHubWorkflowBundle.message("workflow.run.jobs.description")); + createdContent.setCloseable(false); + if (createdConsole instanceof Disposable disposable) { + createdContent.setDisposer(disposable); + } + layout.addContent(createdContent); + keepOnlyWorkflowContent(layout, createdContent); + synchronized (attachLock) { + content = createdContent; + detailConsole = createdConsole; + rootNode = createdRoot; + treeModel = createdModel; + tree = createdTree; + progressBar = createdProgress; + runnerLayoutUi = layout; + } + refreshTree(); + createdTree.setSelectionPath(new TreePath(createdRoot.getPath())); + replay(workflow); + updateAnimationTimer(); + } + + private static JComponent focusTarget(final ConsoleView console) { + return console.getPreferredFocusableComponent() instanceof JComponent component ? component : console.getComponent(); + } + + private void keepOnlyWorkflowContent(final RunnerLayoutUi layout, final Content workflowContent) { + for (final Content candidate : layout.getContents()) { + if (candidate != workflowContent && DEFAULT_CONSOLE_TITLE.equals(candidate.getDisplayName())) { + layout.removeContent(candidate, false); + } + } + layout.selectAndFocus(workflowContent, false, true); + } + + private void closeRunContent() { + if (project.isDisposed() || executor == null) { + return; + } + descriptor().ifPresent(descriptor -> RunContentManager.getInstance(project).removeRunContent(executor, descriptor)); + } + + private void addJobNode(final JobNode node) { + final DefaultMutableTreeNode root = rootNode; + final DefaultTreeModel model = treeModel; + if (root == null || model == null || node.treeNode != null || project.isDisposed()) { + return; + } + final DefaultMutableTreeNode parent = node.groupName().isBlank() + ? root + : groupTreeNode(node.groupName(), root, model); + node.treeNode = new DefaultMutableTreeNode(node); + model.insertNodeInto(node.treeNode, parent, parent.getChildCount()); + final Tree currentTree = tree; + if (currentTree != null) { + currentTree.expandPath(new TreePath(root.getPath())); + currentTree.expandPath(new TreePath(parent.getPath())); + } + refreshTree(); + } + + private DefaultMutableTreeNode groupTreeNode( + final String groupName, + final DefaultMutableTreeNode root, + final DefaultTreeModel model + ) { + final GroupNode group = groups.computeIfAbsent(groupName, GroupNode::new); + if (group.treeNode == null) { + group.treeNode = new DefaultMutableTreeNode(group); + model.insertNodeInto(group.treeNode, root, root.getChildCount()); + } + return group.treeNode; + } + + private void refreshTree() { + ApplicationManager.getApplication().invokeLater(() -> { + if (project.isDisposed()) { + return; + } + jobs.values().forEach(this::addJobNode); + final DefaultTreeModel model = treeModel; + final DefaultMutableTreeNode root = rootNode; + if (model != null && root != null) { + model.nodeChanged(root); + groups.values().stream() + .map(group -> group.treeNode) + .filter(node -> node != null) + .forEach(model::nodeChanged); + jobs.values().stream() + .map(job -> job.treeNode) + .filter(node -> node != null) + .forEach(model::nodeChanged); + } + updateContent(); + }); + } + + private void selectEntry(final TreePath path) { + final Object last = path.getLastPathComponent(); + if (!(last instanceof DefaultMutableTreeNode treeNode) || !(treeNode.getUserObject() instanceof TreeEntry entry)) { + selectedEntry = workflow; + replay(workflow); + updateContent(); + return; + } + selectedEntry = entry; + replay(entry); + updateContent(); + } + + private void replay(final TreeEntry entry) { + final ConsoleView console = detailConsole; + if (console == null || project.isDisposed()) { + return; + } + console.clear(); + entry.snapshot().forEach(text -> console.print(text.text(), text.contentType())); + } + + private void retry() { + if (project.isDisposed() || attachAttempts.incrementAndGet() >= MAX_ATTACH_ATTEMPTS) { + jobs.values().forEach(JobNode::clear); + return; + } + attaching.set(false); + AppExecutorUtil.getAppScheduledExecutorService().schedule(this::scheduleAttach, 250, TimeUnit.MILLISECONDS); + } + + private void updateContent() { + final Content current = content; + final JProgressBar progress = progressBar; + if (current == null) { + return; + } + final Icon nextIcon = workflow.icon(); + ApplicationManager.getApplication().invokeLater(() -> { + final Content nextContent = content; + if (nextContent != null && !project.isDisposed()) { + nextContent.setIcon(nextIcon); + } + if (progress != null && !project.isDisposed()) { + final int total = jobs.size(); + final int completed = (int) jobs.values().stream().filter(JobNode::completed).count(); + progress.setIndeterminate(total == 0 && !terminal()); + progress.setMaximum(Math.max(1, total)); + progress.setValue(terminal() ? Math.max(1, total) : Math.min(completed, Math.max(1, total))); + progress.setVisible(total > 0 || !terminal()); + progress.setForeground(progressColor()); + } + final RunnerLayoutUi layout = runnerLayoutUi; + if (layout != null && !project.isDisposed()) { + layout.updateActionsNow(); + } + updateAnimationTimer(); + }); + } + + private DefaultActionGroup runToolbarActions() { + final DefaultActionGroup group = new DefaultActionGroup(); + group.add(new ToolbarAction( + GitHubWorkflowBundle.message("workflow.run.rerun.all.tooltip"), + AllIcons.Actions.Rerun, + this::canRerunAll, + () -> processHandler.rerunRemoteRun(false) + )); + group.add(new ToolbarAction( + GitHubWorkflowBundle.message("workflow.run.rerun.failed.tooltip"), + AllIcons.Actions.Restart, + this::canRerunFailed, + () -> processHandler.rerunRemoteRun(true) + )); + group.addSeparator(); + group.add(new ToolbarAction( + GitHubWorkflowBundle.message("workflow.run.download.log.tooltip"), + AllIcons.Nodes.Console, + this::canDownloadSelectedLog, + () -> { + final TreeEntry entry = selectedEntry; + if (entry instanceof JobNode job) { + processHandler.downloadJobLog(job.jobId(), job.title()); + } + } + )); + group.add(new ToolbarAction( + GitHubWorkflowBundle.message("workflow.run.download.artifacts.tooltip"), + AllIcons.Actions.Download, + this::canDownloadArtifacts, + processHandler::downloadArtifacts + )); + group.addSeparator(); + group.add(new ToolbarAction( + GitHubWorkflowBundle.message("workflow.run.delete.tooltip"), + AllIcons.General.Delete, + this::canDeleteRun, + () -> { + if (deleteInProgress.compareAndSet(false, true)) { + processHandler.deleteRemoteRun(); + } + } + )); + return group; + } + + private boolean canRerunAll() { + return terminalRunId > 0 && terminal(); + } + + private boolean canRerunFailed() { + return terminalRunId > 0 && terminal() && jobs.values().stream().anyMatch(JobNode::failed); + } + + private boolean canDownloadSelectedLog() { + return terminalRunId > 0 + && selectedEntry instanceof JobNode job + && job.downloadableLog(); + } + + private boolean canDownloadArtifacts() { + return terminalRunId > 0 && processHandler.artifactAvailability() == 1; + } + + private boolean canDeleteRun() { + return terminalRunId > 0 && terminal() && !deleteInProgress.get(); + } + + private Color progressColor() { + if (!terminal()) { + return uiColor("ProgressBar.foreground", new Color(0x4A88C7)); + } + return successful(terminalConclusion) + ? uiColor("Actions.Green", new Color(0x59A869)) + : uiColor("Actions.Red", new Color(0xDB5860)); + } + + private static Color uiColor(final String key, final Color fallback) { + final Color color = UIManager.getColor(key); + return color == null ? fallback : color; + } + + private void updateAnimationTimer() { + if (shouldAnimate()) { + startAnimationTimer(); + } else { + stopAnimationTimer(); + } + } + + private void startAnimationTimer() { + if (animationTimer != null) { + return; + } + final Timer timer = new Timer(125, ignored -> { + if (!shouldAnimate()) { + stopAnimationTimer(); + return; + } + final Content currentContent = content; + if (currentContent != null) { + currentContent.setIcon(workflow.icon()); + } + final Tree currentTree = tree; + if (currentTree != null) { + currentTree.repaint(); + } + }); + timer.setRepeats(true); + timer.start(); + animationTimer = timer; + } + + private void stopAnimationTimer() { + final Timer timer = animationTimer; + if (timer != null) { + timer.stop(); + animationTimer = null; + } + } + + private boolean shouldAnimate() { + return !closed.get() + && !project.isDisposed() + && !processHandler.isProcessTerminated() + && !terminal() + && (jobs.isEmpty() || jobs.values().stream().anyMatch(JobNode::running)); + } + + private static ConsoleViewContentType processContentType(final Key outputType) { + return ProcessOutputTypes.STDERR.equals(outputType) + ? ConsoleViewContentType.ERROR_OUTPUT + : ConsoleViewContentType.SYSTEM_OUTPUT; + } + + private static ConsoleViewContentType contentType(final WorkflowRunLogRenderer.Kind kind) { + return switch (kind) { + case SYSTEM -> ConsoleViewContentType.SYSTEM_OUTPUT; + case WARNING -> ConsoleViewContentType.LOG_WARNING_OUTPUT; + case ERROR -> ConsoleViewContentType.LOG_ERROR_OUTPUT; + case NORMAL -> ConsoleViewContentType.NORMAL_OUTPUT; + }; + } + + private static boolean successful(final String conclusion) { + return "success".equals(conclusion) || "skipped".equals(conclusion) || "neutral".equals(conclusion); + } + + private static boolean cancelled(final String conclusion) { + return "cancelled".equals(conclusion) || "canceled".equals(conclusion); + } + + private static String normalizeConclusion(final String conclusion) { + return conclusion == null || conclusion.isBlank() + ? "failure" + : conclusion.toLowerCase(Locale.ROOT); + } + + private boolean terminal() { + return !terminalConclusion.isBlank(); + } + + static JobDisplayName splitJobName(final String name) { + final String normalized = name == null || name.isBlank() + ? GitHubWorkflowBundle.message("workflow.run.job.fallbackName", -1) + : name; + final int separator = normalized.indexOf(" / "); + if (separator <= 0 || separator + 3 >= normalized.length()) { + return new JobDisplayName("", normalized); + } + return new JobDisplayName(normalized.substring(0, separator), normalized.substring(separator + 3)); + } + + private static final class ToolbarAction extends DumbAwareAction { + private final String text; + private final BooleanSupplier visible; + private final Runnable command; + + private ToolbarAction( + final String tooltip, + final Icon icon, + final BooleanSupplier visible, + final Runnable command + ) { + super(tooltip, tooltip, icon); + this.text = tooltip; + this.visible = visible; + this.command = command; + } + + @Override + public void actionPerformed(@NotNull final AnActionEvent event) { + command.run(); + } + + @Override + public void update(@NotNull final AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + final boolean available = visible.getAsBoolean(); + presentation.setText(text, false); + presentation.setDescription(text); + presentation.setEnabledAndVisible(available); + } + } + + private interface TreeEntry { + String title(); + + String suffix(); + + Icon icon(); + + List snapshot(); + } + + private final class WorkflowNode implements TreeEntry { + private final Object lock = new Object(); + private final List output = new ArrayList<>(); + private final long startedMillis = System.currentTimeMillis(); + + private void print(final String text, final ConsoleViewContentType contentType) { + final PrintedText printedText = new PrintedText(text, contentType); + synchronized (lock) { + output.add(printedText); + } + printIfSelected(this, printedText); + refreshTree(); + } + + @Override + public String title() { + return GitHubWorkflowBundle.message("workflow.run.jobs.root"); + } + + @Override + public String suffix() { + final int total = jobs.size(); + if (total == 0) { + return ""; + } + final long completed = jobs.values().stream().filter(JobNode::completed).count(); + final long failed = jobs.values().stream().filter(JobNode::failed).count(); + final long skipped = jobs.values().stream().filter(JobNode::skipped).count(); + final StringBuilder result = new StringBuilder() + .append(completed).append("/").append(total) + .append(" ").append(GitHubWorkflowBundle.message("workflow.run.tree.done")) + .append(" ").append(formatDuration(System.currentTimeMillis() - startedMillis)); + if (failed > 0) { + result.append(" ").append(GitHubWorkflowBundle.message("workflow.run.tree.failed")).append(" ").append(failed); + } + if (skipped > 0) { + result.append(" ").append(GitHubWorkflowBundle.message("workflow.run.tree.skipped")).append(" ").append(skipped); + } + return result.toString(); + } + + @Override + public Icon icon() { + if (shouldAnimate()) { + return AnimatedIcon.Default.INSTANCE; + } + if (cancelled(terminalConclusion) || jobs.values().stream().anyMatch(JobNode::cancelled)) { + return AllIcons.RunConfigurations.TestTerminated; + } + if (jobs.values().stream().anyMatch(JobNode::failed)) { + return AllIcons.General.Error; + } + if (jobs.values().stream().anyMatch(JobNode::skipped)) { + return AllIcons.RunConfigurations.TestState.Yellow2; + } + if (jobs.isEmpty() && terminal()) { + return successful(terminalConclusion) + ? AllIcons.General.GreenCheckmark + : AllIcons.General.Error; + } + return AllIcons.General.GreenCheckmark; + } + + @Override + public List snapshot() { + synchronized (lock) { + return List.copyOf(output); + } + } + + private boolean completed() { + return terminal() || (!jobs.isEmpty() && jobs.values().stream().allMatch(JobNode::completed)); + } + } + + private final class GroupNode implements TreeEntry { + private final String name; + private @Nullable DefaultMutableTreeNode treeNode; + + private GroupNode(final String name) { + this.name = name; + } + + @Override + public String title() { + return name; + } + + @Override + public String suffix() { + final List children = children(); + if (children.isEmpty()) { + return ""; + } + final long completed = children.stream().filter(JobNode::completed).count(); + final long warnings = children.stream().mapToLong(JobNode::warnings).sum(); + final long errors = children.stream().mapToLong(JobNode::errors).sum(); + final StringBuilder result = new StringBuilder() + .append(completed).append("/").append(children.size()) + .append(" ").append(GitHubWorkflowBundle.message("workflow.run.tree.done")); + if (warnings > 0) { + result.append(" ").append(GitHubWorkflowBundle.message("workflow.run.tree.warn")).append(" ").append(warnings); + } + if (errors > 0) { + result.append(" ").append(GitHubWorkflowBundle.message("workflow.run.tree.err")).append(" ").append(errors); + } + return result.toString(); + } + + @Override + public Icon icon() { + final List children = children(); + if (children.stream().anyMatch(JobNode::running)) { + return AnimatedIcon.Default.INSTANCE; + } + if (children.stream().anyMatch(JobNode::cancelled)) { + return AllIcons.RunConfigurations.TestTerminated; + } + if (children.stream().anyMatch(JobNode::failed)) { + return AllIcons.General.Error; + } + if (children.stream().anyMatch(JobNode::skipped)) { + return AllIcons.RunConfigurations.TestState.Yellow2; + } + return children.isEmpty() ? AllIcons.RunConfigurations.TestNotRan : AllIcons.General.GreenCheckmark; + } + + @Override + public List snapshot() { + final List result = new ArrayList<>(); + children().forEach(job -> { + result.add(new PrintedText("\n== " + job.title() + " ==\n", ConsoleViewContentType.SYSTEM_OUTPUT)); + result.addAll(job.snapshot()); + }); + return result; + } + + private List children() { + return jobs.values().stream() + .filter(job -> name.equals(job.groupName())) + .sorted(Comparator.comparingLong(JobNode::jobId)) + .toList(); + } + } + + private final class JobNode implements TreeEntry { + private final long jobId; + private final Object lock = new Object(); + private final List output = new ArrayList<>(); + private final WorkflowRunLogRenderer logRenderer = new WorkflowRunLogRenderer(); + private volatile String groupName; + private volatile String displayName; + private volatile String status; + private volatile String conclusion; + private volatile long firstSeenMillis; + private volatile long startedMillis; + private volatile long completedMillis; + private volatile int warnings; + private volatile int errors; + private @Nullable DefaultMutableTreeNode treeNode; + + private JobNode(final WorkflowRunClient.JobStatus job) { + this.jobId = job.id(); + updateDisplayName(job); + this.status = job.status(); + this.conclusion = normalizeConclusion(job.conclusion()); + final long now = System.currentTimeMillis(); + this.firstSeenMillis = now; + updateTiming(job, now); + } + + private long jobId() { + return jobId; + } + + private String groupName() { + return groupName == null ? "" : groupName; + } + + private void print(final WorkflowRunClient.JobStatus job, final String text, final ConsoleViewContentType contentType) { + update(job); + append(new PrintedText(text, contentType)); + } + + private void printLog(final String text) { + logRenderer.render(text).forEach(segment -> append(new PrintedText(segment.text(), contentType(segment.kind())))); + } + + private void append(final PrintedText text) { + synchronized (lock) { + output.add(text); + if (text.contentType() == ConsoleViewContentType.LOG_WARNING_OUTPUT) { + warnings++; + } + if (text.contentType() == ConsoleViewContentType.LOG_ERROR_OUTPUT || text.contentType() == ConsoleViewContentType.ERROR_OUTPUT) { + errors++; + } + } + printIfSelected(this, text); + refreshTree(); + } + + private void update(final WorkflowRunClient.JobStatus job) { + updateDisplayName(job); + status = job.status(); + conclusion = normalizeConclusion(job.conclusion()); + updateTiming(job, System.currentTimeMillis()); + } + + private void updateDisplayName(final WorkflowRunClient.JobStatus job) { + final JobDisplayName parts = splitJobName(displayBaseName(job)); + groupName = parts.group(); + displayName = parts.name(); + } + + private void updateTiming(final WorkflowRunClient.JobStatus job, final long now) { + if (firstSeenMillis == 0) { + firstSeenMillis = now; + } + if ("in_progress".equals(job.status()) && startedMillis == 0) { + startedMillis = now; + } + if ("completed".equals(job.status()) && completedMillis == 0) { + completedMillis = now; + } + } + + @Override + public String title() { + return displayName == null ? "" : displayName; + } + + @Override + public String suffix() { + final StringBuilder result = new StringBuilder(); + final String duration = duration(); + if (!duration.isBlank()) { + result.append(duration); + } + if (warnings > 0) { + appendSuffix(result, GitHubWorkflowBundle.message("workflow.run.tree.warn") + " " + warnings); + } + if (errors > 0) { + appendSuffix(result, GitHubWorkflowBundle.message("workflow.run.tree.err") + " " + errors); + } + return result.toString(); + } + + @Override + public Icon icon() { + if (completed()) { + if (skipped()) { + return AllIcons.RunConfigurations.TestState.Yellow2; + } + if (cancelled()) { + return AllIcons.RunConfigurations.TestTerminated; + } + return successful(conclusion) ? AllIcons.General.GreenCheckmark : AllIcons.General.Error; + } + return running() ? AnimatedIcon.Default.INSTANCE : AllIcons.RunConfigurations.TestNotRan; + } + + @Override + public List snapshot() { + synchronized (lock) { + return List.copyOf(output); + } + } + + private int warnings() { + return warnings; + } + + private int errors() { + return errors; + } + + private boolean completed() { + return "completed".equals(status); + } + + private boolean running() { + return "in_progress".equals(status); + } + + private boolean failed() { + return completed() && !successful(conclusion) && !cancelled(); + } + + private boolean skipped() { + return completed() && ("skipped".equals(conclusion) || "neutral".equals(conclusion)); + } + + private boolean downloadableLog() { + synchronized (lock) { + return completed() || !output.isEmpty(); + } + } + + private boolean cancelled() { + return completed() && WorkflowRunConsoleTabs.cancelled(conclusion); + } + + private void finish(final String runConclusion) { + if (completed()) { + return; + } + final long now = System.currentTimeMillis(); + if (firstSeenMillis == 0) { + firstSeenMillis = now; + } + if (startedMillis == 0) { + startedMillis = firstSeenMillis; + } + status = "completed"; + conclusion = runConclusion; + completedMillis = now; + } + + private String duration() { + final long start = startedMillis > 0 ? startedMillis : firstSeenMillis; + final long end = completedMillis > 0 ? completedMillis : System.currentTimeMillis(); + if (start <= 0 || end < start) { + return ""; + } + return formatDuration(end - start); + } + + private void clear() { + synchronized (lock) { + output.clear(); + } + } + } + + private void printIfSelected(final TreeEntry entry, final PrintedText text) { + if (selectedEntry != entry || project.isDisposed()) { + return; + } + ApplicationManager.getApplication().invokeLater(() -> { + final ConsoleView console = detailConsole; + if (console != null && selectedEntry == entry) { + console.print(text.text(), text.contentType()); + } + }); + } + + private static String displayBaseName(final WorkflowRunClient.JobStatus job) { + return job.name() == null || job.name().isBlank() + ? GitHubWorkflowBundle.message("workflow.run.job.fallbackName", job.id()) + : job.name(); + } + + private static void appendSuffix(final StringBuilder builder, final String value) { + if (!builder.isEmpty()) { + builder.append(" "); + } + builder.append(value); + } + + private static String formatDuration(final long millis) { + final long seconds = Math.max(0, TimeUnit.MILLISECONDS.toSeconds(millis)); + return String.format(Locale.ROOT, "%02d:%02d", seconds / 60, seconds % 60); + } + + private static final class JobTreeCellRenderer extends ColoredTreeCellRenderer { + @Override + public void customizeCellRenderer( + final JTree tree, + final Object value, + final boolean selected, + final boolean expanded, + final boolean leaf, + final int row, + final boolean hasFocus + ) { + if (value instanceof DefaultMutableTreeNode node && node.getUserObject() instanceof TreeEntry entry) { + setIcon(entry.icon()); + append(entry.title(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + final String suffix = entry.suffix(); + if (!suffix.isBlank()) { + append(" " + suffix, SimpleTextAttributes.GRAYED_ATTRIBUTES); + } + } + } + } + + static record JobDisplayName(String group, String name) { + } + + private record PrintedText(String text, ConsoleViewContentType contentType) { + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunDownloads.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunDownloads.java new file mode 100644 index 0000000..72e12c5 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunDownloads.java @@ -0,0 +1,73 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.ide.actions.RevealFileAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Optional; + +/** + * Stores workflow run downloads under the IDE system directory and reveals them to the user. + */ +final class WorkflowRunDownloads { + + private WorkflowRunDownloads() { + } + + static Path writeJobLog( + final WorkflowRunRequest request, + final long runId, + final long jobId, + final String jobName, + final String log + ) throws IOException { + final Path file = runDirectory(request, runId).resolve(safeName(jobName) + "-" + jobId + ".log"); + Files.writeString(file, Optional.ofNullable(log).orElse(""), StandardCharsets.UTF_8); + return file; + } + + static Path writeArtifact( + final WorkflowRunRequest request, + final long runId, + final WorkflowRunClient.ArtifactStatus artifact, + final byte[] zip + ) throws IOException { + final Path file = runDirectory(request, runId).resolve(safeName(artifact.name()) + "-" + artifact.id() + ".zip"); + Files.write(file, Optional.ofNullable(zip).orElseGet(() -> new byte[0])); + return file; + } + + static void reveal(final Path path) { + if (path == null) { + return; + } + ApplicationManager.getApplication().invokeLater(() -> RevealFileAction.openFile(path.toFile())); + } + + private static Path runDirectory(final WorkflowRunRequest request, final long runId) throws IOException { + final Path directory = Path.of( + PathManager.getSystemPath(), + "github-workflow-plugin", + "downloads", + safeName(request.repositorySlug()), + "run-" + runId + ); + Files.createDirectories(directory); + return directory; + } + + private static String safeName(final String value) { + final String normalized = Optional.ofNullable(value) + .filter(text -> !text.isBlank()) + .orElse("download") + .toLowerCase(Locale.ROOT) + .replaceAll("[^a-z0-9._-]+", "-") + .replaceAll("^-+|-+$", ""); + return normalized.isBlank() ? "download" : normalized; + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java new file mode 100644 index 0000000..bf0d200 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java @@ -0,0 +1,51 @@ +package com.github.yunabraska.githubworkflow.services; + +/** + * Receives workflow job status and logs for a Run tool-window workflow view. + */ +interface WorkflowRunJobConsole { + + boolean jobStatus(WorkflowRunClient.JobStatus job, String text); + + boolean jobStdout(WorkflowRunClient.JobStatus job, String text); + + boolean jobStderr(WorkflowRunClient.JobStatus job, String text); + + default boolean jobLog(final WorkflowRunClient.JobStatus job, final String text) { + return jobStdout(job, text); + } + + default void workflowStatus(final String text, final boolean error) { + } + + default void runFinished(final long runId, final String conclusion) { + } + + default void runDeleted(final long runId) { + } + + default void runDeleteFailed(final long runId) { + } + + default void close() { + } + + static WorkflowRunJobConsole none() { + return new WorkflowRunJobConsole() { + @Override + public boolean jobStatus(final WorkflowRunClient.JobStatus job, final String text) { + return false; + } + + @Override + public boolean jobStdout(final WorkflowRunClient.JobStatus job, final String text) { + return false; + } + + @Override + public boolean jobStderr(final WorkflowRunClient.JobStatus job, final String text) { + return false; + } + }; + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjector.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjector.java new file mode 100644 index 0000000..1086766 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjector.java @@ -0,0 +1,221 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.lang.Language; +import com.intellij.lang.injection.MultiHostInjector; +import com.intellij.lang.injection.MultiHostRegistrar; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.yaml.psi.YAMLKeyValue; +import org.jetbrains.yaml.psi.YAMLScalar; +import org.jetbrains.yaml.psi.impl.YAMLScalarImpl; + +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_RUN; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getChild; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentJob; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getParentStep; +import static com.github.yunabraska.githubworkflow.helper.PsiElementHelper.getText; + +public final class WorkflowRunLanguageInjector implements MultiHostInjector { + + @Override + public void getLanguagesToInject(@NotNull final MultiHostRegistrar registrar, @NotNull final PsiElement context) { + if (!(context instanceof YAMLScalar scalar) || !isRunScalar(scalar)) { + return; + } + languageForShell(scalar) + .ifPresent(language -> inject(registrar, scalar, language)); + } + + @Override + public @NotNull List> elementsToInjectIn() { + return List.of(YAMLScalar.class); + } + + private static boolean isRunScalar(final YAMLScalar scalar) { + return scalar.getParent() instanceof YAMLKeyValue keyValue && FIELD_RUN.equals(keyValue.getKeyText()); + } + + private static Optional languageForShell(final YAMLScalar scalar) { + return shellFor(scalar) + .map(WorkflowRunLanguageInjector::languageId) + .flatMap(id -> Optional.ofNullable(Language.findLanguageByID(id))); + } + + private static Optional shellFor(final YAMLScalar scalar) { + return getParentStep(scalar) + .flatMap(step -> getText(step, "shell")) + .or(() -> getParentJob(scalar) + .flatMap(job -> getChild(job, "defaults")) + .flatMap(defaults -> getChild(defaults, FIELD_RUN)) + .flatMap(run -> getText(run, "shell"))) + .or(() -> getChild(scalar.getContainingFile(), "defaults") + .flatMap(defaults -> getChild(defaults, FIELD_RUN)) + .flatMap(run -> getText(run, "shell"))) + .or(() -> Optional.of("bash")); + } + + private static String languageId(final String shell) { + final String normalized = shell.toLowerCase(Locale.ROOT).trim(); + if (normalized.contains("pwsh") || normalized.contains("powershell")) { + return "PowerShell"; + } + if (normalized.contains("python")) { + return "Python"; + } + if (normalized.contains("node") || normalized.contains("javascript") || normalized.equals("js")) { + return "JavaScript"; + } + if (normalized.contains("ruby")) { + return "Ruby"; + } + if (normalized.contains("perl")) { + return "Perl"; + } + return "Shell Script"; + } + + private static void inject(final MultiHostRegistrar registrar, final YAMLScalar scalar, final Language language) { + final List ranges = contentRanges(scalar); + if (ranges.isEmpty()) { + return; + } + registrar.startInjecting(language); + ranges.forEach(range -> registrar.addPlace(null, null, scalar, range)); + registrar.doneInjecting(); + } + + private static List contentRanges(final YAMLScalar scalar) { + final List ranges = scalar instanceof YAMLScalarImpl scalarImpl + ? scalarImpl.getContentRanges() + : fallbackContentRanges(scalar); + final List withoutExpressions = ranges.stream() + .flatMap(range -> excludeWorkflowExpressions(scalar.getText(), range).stream()) + .toList(); + return subtractRanges(withoutExpressions, hereDocBodyRanges(scalar.getText(), new TextRange(0, scalar.getTextLength()))).stream() + .filter(range -> range.getStartOffset() < range.getEndOffset()) + .toList(); + } + + private static List fallbackContentRanges(final YAMLScalar scalar) { + final int length = scalar.getTextLength(); + return length == 0 ? List.of() : List.of(new TextRange(0, length)); + } + + private static List excludeWorkflowExpressions(final String text, final TextRange range) { + final java.util.ArrayList result = new java.util.ArrayList<>(); + int start = range.getStartOffset(); + while (start < range.getEndOffset()) { + final int expressionStart = text.indexOf("${{", start); + if (expressionStart < 0 || expressionStart >= range.getEndOffset()) { + result.add(new TextRange(start, range.getEndOffset())); + break; + } + if (start < expressionStart) { + result.add(new TextRange(start, expressionStart)); + } + final int expressionEnd = text.indexOf("}}", expressionStart + 3); + start = expressionEnd < 0 ? range.getEndOffset() : Math.min(expressionEnd + 2, range.getEndOffset()); + } + return result; + } + + private static List hereDocBodyRanges(final String text, final TextRange range) { + final java.util.ArrayList result = new java.util.ArrayList<>(); + String delimiter = ""; + int bodyStart = -1; + int lineStart = range.getStartOffset(); + while (lineStart < range.getEndOffset()) { + final int newline = text.indexOf('\n', lineStart); + final int lineEnd = newline < 0 ? range.getEndOffset() : Math.min(newline, range.getEndOffset()); + final String line = text.substring(lineStart, lineEnd); + if (delimiter.isBlank()) { + final Optional nextDelimiter = hereDocDelimiter(line); + if (nextDelimiter.isPresent()) { + delimiter = nextDelimiter.get(); + bodyStart = Math.min(lineEnd + 1, range.getEndOffset()); + } + } else if (line.trim().equals(delimiter)) { + if (bodyStart >= 0 && bodyStart < lineStart) { + result.add(new TextRange(bodyStart, lineStart)); + } + delimiter = ""; + bodyStart = -1; + } + if (newline < 0 || lineEnd >= range.getEndOffset()) { + break; + } + lineStart = lineEnd + 1; + } + if (!delimiter.isBlank() && bodyStart >= 0 && bodyStart < range.getEndOffset()) { + result.add(new TextRange(bodyStart, range.getEndOffset())); + } + return result; + } + + private static Optional hereDocDelimiter(final String line) { + char quote = 0; + for (int index = 0; index + 1 < line.length(); index++) { + final char current = line.charAt(index); + if (quote != 0) { + if (current == quote) { + quote = 0; + } + continue; + } + if (current == '\'' || current == '"') { + quote = current; + continue; + } + if (current == '<' && line.charAt(index + 1) == '<') { + int delimiterStart = index + 2; + if (delimiterStart < line.length() && line.charAt(delimiterStart) == '-') { + delimiterStart++; + } + while (delimiterStart < line.length() && Character.isWhitespace(line.charAt(delimiterStart))) { + delimiterStart++; + } + int delimiterEnd = delimiterStart; + while (delimiterEnd < line.length() && isDelimiterChar(line.charAt(delimiterEnd))) { + delimiterEnd++; + } + if (delimiterStart < delimiterEnd) { + return Optional.of(line.substring(delimiterStart, delimiterEnd)); + } + } + } + return Optional.empty(); + } + + private static boolean isDelimiterChar(final char character) { + return Character.isLetterOrDigit(character) || character == '_'; + } + + private static List subtractRanges(final List ranges, final List excludedRanges) { + List result = ranges; + for (final TextRange excludedRange : excludedRanges) { + result = result.stream() + .flatMap(range -> subtractRange(range, excludedRange).stream()) + .toList(); + } + return result; + } + + private static List subtractRange(final TextRange range, final TextRange excludedRange) { + if (!range.intersectsStrict(excludedRange)) { + return List.of(range); + } + final java.util.ArrayList result = new java.util.ArrayList<>(); + if (range.getStartOffset() < excludedRange.getStartOffset()) { + result.add(new TextRange(range.getStartOffset(), excludedRange.getStartOffset())); + } + if (excludedRange.getEndOffset() < range.getEndOffset()) { + result.add(new TextRange(excludedRange.getEndOffset(), range.getEndOffset())); + } + return result; + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLineMarkerContributor.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLineMarkerContributor.java new file mode 100644 index 0000000..863fc00 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLineMarkerContributor.java @@ -0,0 +1,87 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.helper.GitHubWorkflowHelper; +import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.execution.lineMarker.RunLineMarkerContributor; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.psi.YAMLKeyValue; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Adds the standard Run gutter action to workflow_dispatch entries. + */ +public final class WorkflowRunLineMarkerContributor extends RunLineMarkerContributor { + + private static final RepositoryAvailability DEFAULT_REPOSITORY_AVAILABILITY = + (project, file) -> new WorkflowRepositoryResolver().resolve(project, file).isPresent(); + private static final AtomicReference repositoryAvailability = + new AtomicReference<>(DEFAULT_REPOSITORY_AVAILABILITY); + + @Override + public @Nullable Info getInfo(final PsiElement element) { + if (!(element instanceof LeafPsiElement) || !"workflow_dispatch".equals(element.getText())) { + return null; + } + if (!(element.getParent() instanceof YAMLKeyValue keyValue) || !"workflow_dispatch".equals(keyValue.getKeyText())) { + return null; + } + final Optional workflowPath = Optional.ofNullable(element.getContainingFile()) + .map(file -> file.getVirtualFile()) + .flatMap(file -> WorkflowRunConfigurationProducer.workflowPath(element.getProject(), file) + .or(() -> PsiElementHelper.toPath(file).map(path -> path.getFileName().toString()))); + final boolean workflowFile = Optional.ofNullable(element.getContainingFile()) + .map(file -> file.getVirtualFile()) + .flatMap(PsiElementHelper::toPath) + .filter(GitHubWorkflowHelper::isWorkflowPath) + .isPresent(); + if (!workflowFile || workflowPath.isEmpty()) { + return null; + } + if (WorkflowRunTracker.getInstance(element.getProject()).isRunning(workflowPath.get())) { + return new Info(AllIcons.Actions.Suspend, new AnAction[]{new StopWorkflowRunAction(workflowPath.get())}, item -> GitHubWorkflowBundle.message("workflow.run.gutter.stop")); + } + final boolean repositoryAvailable = Optional.ofNullable(element.getContainingFile()) + .map(file -> file.getVirtualFile()) + .map(file -> repositoryAvailability.get().available(element.getProject(), file)) + .orElse(false); + if (!repositoryAvailable) { + return null; + } + return withExecutorActions(AllIcons.Actions.Execute); + } + + static RepositoryAvailability useRepositoryAvailabilityForTests(final RepositoryAvailability availability) { + return repositoryAvailability.getAndSet(availability == null ? DEFAULT_REPOSITORY_AVAILABILITY : availability); + } + + interface RepositoryAvailability { + boolean available(Project project, VirtualFile file); + } + + private static final class StopWorkflowRunAction extends AnAction { + + private final String workflowPath; + + private StopWorkflowRunAction(final String workflowPath) { + super(GitHubWorkflowBundle.message("workflow.run.gutter.stop.text"), GitHubWorkflowBundle.message("workflow.run.gutter.stop.description"), AllIcons.Actions.Suspend); + this.workflowPath = workflowPath; + } + + @Override + public void actionPerformed(@NotNull final AnActionEvent event) { + Optional.ofNullable(event.getProject()) + .map(WorkflowRunTracker::getInstance) + .ifPresent(tracker -> tracker.stop(workflowPath)); + } + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRenderer.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRenderer.java new file mode 100644 index 0000000..4ab0e77 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRenderer.java @@ -0,0 +1,209 @@ +package com.github.yunabraska.githubworkflow.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Converts raw GitHub Actions log lines into compact IDE console segments. + */ +final class WorkflowRunLogRenderer { + + private static final Pattern TIMESTAMP = Pattern.compile("^\\x{FEFF}?\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z\\s+"); + private static final Pattern GITHUB_COMMAND = Pattern.compile("^##\\[([^]]+)](.*)$"); + private static final Pattern WORKFLOW_COMMAND = Pattern.compile("^::([^: ]+)(?: [^:]*)?::(.*)$"); + private static final Pattern ANSI_SGR = Pattern.compile("\\x1B\\[([0-9;]*)m"); + private static final Pattern ANSI_CONTROL = Pattern.compile("\\x1B\\[[0-?]*[ -/]*[@-~]"); + + private int lineNumber = 0; + private boolean printedAny = false; + + static List renderOnce(final String text) { + return new WorkflowRunLogRenderer().render(text); + } + + static String renderPlainOnce(final String text) { + return new WorkflowRunLogRenderer().renderPlain(text); + } + + List render(final String text) { + if (text == null || text.isEmpty()) { + return List.of(); + } + final List result = new ArrayList<>(); + int start = 0; + while (start < text.length()) { + final int next = nextLineEnd(text, start); + appendLine(result, text.substring(start, next)); + start = next; + } + return List.copyOf(result); + } + + String renderPlain(final String text) { + final StringBuilder result = new StringBuilder(); + for (final Segment segment : render(text)) { + result.append(segment.text()); + } + return result.toString(); + } + + private void appendLine(final List result, final String rawLine) { + final LineParts parts = splitLine(rawLine); + final AnsiLine ansiLine = stripAnsi(TIMESTAMP.matcher(parts.text()).replaceFirst("")); + final String line = ansiLine.text(); + final Matcher githubCommand = GITHUB_COMMAND.matcher(line); + if (githubCommand.matches()) { + appendGitHubCommand(result, githubCommand.group(1), githubCommand.group(2), parts.separator()); + return; + } + final Matcher workflowCommand = WORKFLOW_COMMAND.matcher(line); + if (workflowCommand.matches()) { + appendWorkflowCommand(result, workflowCommand.group(1), workflowCommand.group(2), parts.separator()); + return; + } + appendNumbered(result, line, ansiLine.kind() == Kind.NORMAL ? inferredKind(line) : ansiLine.kind(), parts.separator()); + } + + private void appendGitHubCommand(final List result, final String command, final String value, final String separator) { + final String name = commandName(command); + switch (name) { + case "group" -> appendBlockHeader(result, value); + case "endgroup", "/group" -> appendBlockEnd(); + case "command" -> appendNumbered(result, label("workflow.log.command") + " " + value, Kind.SYSTEM, separator); + case "warning" -> appendNumbered(result, label("workflow.log.warning") + " " + value, Kind.WARNING, separator); + case "error" -> appendNumbered(result, label("workflow.log.error") + " " + value, Kind.ERROR, separator); + default -> appendNumbered(result, value.isBlank() ? "[" + name + "]" : value, Kind.SYSTEM, separator); + } + } + + private void appendWorkflowCommand(final List result, final String command, final String value, final String separator) { + final String name = commandName(command); + switch (name) { + case "warning" -> appendNumbered(result, label("workflow.log.warning") + " " + value, Kind.WARNING, separator); + case "error" -> appendNumbered(result, label("workflow.log.error") + " " + value, Kind.ERROR, separator); + case "group" -> appendBlockHeader(result, value); + case "endgroup", "/group" -> appendBlockEnd(); + default -> appendNumbered(result, value, Kind.SYSTEM, separator); + } + } + + private void appendBlockHeader(final List result, final String title) { + final String prefix = printedAny ? "\n" : ""; + result.add(new Segment(prefix + "== " + title.strip() + " ==\n", Kind.SYSTEM)); + lineNumber = 0; + printedAny = true; + } + + private void appendBlockEnd() { + lineNumber = 0; + } + + private void appendNumbered(final List result, final String line, final Kind kind, final String separator) { + printedAny = true; + if (line.isBlank()) { + result.add(new Segment(separator, kind)); + return; + } + lineNumber++; + result.add(new Segment(String.format(Locale.ROOT, "%04d | %s%s", lineNumber, line, separator), kind)); + } + + private static AnsiLine stripAnsi(final String line) { + Kind kind = Kind.NORMAL; + final Matcher matcher = ANSI_SGR.matcher(line); + while (matcher.find()) { + kind = strongest(kind, kindForAnsi(matcher.group(1))); + } + return new AnsiLine(ANSI_CONTROL.matcher(line).replaceAll(""), kind); + } + + private static Kind kindForAnsi(final String value) { + final String[] codes = value.isBlank() ? new String[]{"0"} : value.split(";"); + Kind result = Kind.NORMAL; + for (final String code : codes) { + result = strongest(result, switch (code) { + case "31", "91" -> Kind.ERROR; + case "33", "93" -> Kind.WARNING; + case "34", "35", "36", "90", "94", "95", "96" -> Kind.SYSTEM; + default -> Kind.NORMAL; + }); + } + return result; + } + + private static Kind strongest(final Kind left, final Kind right) { + return weight(right) > weight(left) ? right : left; + } + + private static int weight(final Kind kind) { + return switch (kind) { + case ERROR -> 3; + case WARNING -> 2; + case SYSTEM -> 1; + case NORMAL -> 0; + }; + } + + private static Kind inferredKind(final String line) { + final String normalized = line.stripLeading().toLowerCase(Locale.ROOT); + if (normalized.startsWith("error:") || normalized.startsWith("fatal:")) { + return Kind.ERROR; + } + if (normalized.startsWith("warning:") || normalized.startsWith("npm warn ")) { + return Kind.WARNING; + } + return Kind.NORMAL; + } + + private static String commandName(final String command) { + final int space = command.indexOf(' '); + return (space >= 0 ? command.substring(0, space) : command).toLowerCase(Locale.ROOT); + } + + private static String label(final String key) { + return GitHubWorkflowBundle.message(key); + } + + private static LineParts splitLine(final String line) { + if (line.endsWith("\r\n")) { + return new LineParts(line.substring(0, line.length() - 2), "\r\n"); + } + if (line.endsWith("\n") || line.endsWith("\r")) { + return new LineParts(line.substring(0, line.length() - 1), line.substring(line.length() - 1)); + } + return new LineParts(line, ""); + } + + private static int nextLineEnd(final String text, final int start) { + int index = start; + while (index < text.length() && text.charAt(index) != '\n' && text.charAt(index) != '\r') { + index++; + } + if (index >= text.length()) { + return index; + } + if (text.charAt(index) == '\r' && index + 1 < text.length() && text.charAt(index + 1) == '\n') { + return index + 2; + } + return index + 1; + } + + enum Kind { + NORMAL, + SYSTEM, + WARNING, + ERROR + } + + record Segment(String text, Kind kind) { + } + + private record AnsiLine(String text, Kind kind) { + } + + private record LineParts(String text, String separator) { + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java new file mode 100644 index 0000000..1c8fef3 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java @@ -0,0 +1,677 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.notification.NotificationAction; +import com.intellij.notification.NotificationGroupManager; +import com.intellij.notification.NotificationType; +import com.intellij.execution.Executor; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.options.ShowSettingsUtil; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.concurrent.ThreadLocalRandom; + +/** + * IDE process facade that dispatches, polls, logs, and cancels a remote GitHub workflow run. + */ +public final class WorkflowRunProcessHandler extends ProcessHandler { + + private final WorkflowRunRequest request; + private final WorkflowRunClient client; + private final Project project; + private final PollSettings pollSettings; + private WorkflowRunJobConsole jobConsole = WorkflowRunJobConsole.none(); + private final AtomicBoolean stopping = new AtomicBoolean(false); + private final AtomicBoolean terminated = new AtomicBoolean(false); + private final AtomicBoolean deleteRequested = new AtomicBoolean(false); + private final AtomicBoolean rerunAllRequested = new AtomicBoolean(false); + private final AtomicBoolean rerunFailedRequested = new AtomicBoolean(false); + private final AtomicInteger artifactAvailability = new AtomicInteger(-1); + private final AtomicLong runId = new AtomicLong(-1); + private final AtomicReference> task = new AtomicReference<>(); + + WorkflowRunProcessHandler(final Project project, final WorkflowRunRequest request, final WorkflowRunClient client) { + this(project, request, client, PollSettings.defaults()); + } + + WorkflowRunProcessHandler( + final Project project, + final WorkflowRunRequest request, + final WorkflowRunClient client, + final Executor executor + ) { + this(project, request, client, PollSettings.defaults()); + this.jobConsole = new WorkflowRunConsoleTabs(project, executor, this); + } + + WorkflowRunProcessHandler( + final Project project, + final WorkflowRunRequest request, + final WorkflowRunClient client, + final PollSettings pollSettings + ) { + this.project = project; + this.request = request; + this.client = client; + this.pollSettings = pollSettings; + } + + WorkflowRunProcessHandler( + final Project project, + final WorkflowRunRequest request, + final WorkflowRunClient client, + final PollSettings pollSettings, + final WorkflowRunJobConsole jobConsole + ) { + this(project, request, client, pollSettings); + this.jobConsole = jobConsole == null ? WorkflowRunJobConsole.none() : jobConsole; + } + + @Override + public void startNotify() { + super.startNotify(); + WorkflowRunTracker.getInstance(project).register(request.workflowPath(), this); + task.set(ApplicationManager.getApplication().executeOnPooledThread(this::runWorkflow)); + } + + @Override + protected void destroyProcessImpl() { + if (!stopping.compareAndSet(false, true)) { + return; + } + final long id = runId.get(); + stdout(id > 0 + ? GitHubWorkflowBundle.message("workflow.run.cancel.requested", id) + "\n" + : GitHubWorkflowBundle.message("workflow.run.stop.before.id") + "\n"); + final Future runningTask = task.get(); + if (runningTask != null) { + runningTask.cancel(true); + } + ApplicationManager.getApplication().executeOnPooledThread(() -> cancelRemoteRun(id)); + } + + private void cancelRemoteRun(final long id) { + if (id > 0) { + try { + final WorkflowRunClient.CancelResult result = client.cancel(request, id); + stderr(GitHubWorkflowBundle.message("workflow.run.cancel.http", result.statusCode()) + "\n"); + } catch (final IOException | InterruptedException exception) { + if (exception instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + stderr(GitHubWorkflowBundle.message("workflow.run.cancel.failed", exception.getMessage()) + "\n"); + } + } + terminate(1, "cancelled"); + } + + @Override + protected void detachProcessImpl() { + stopping.set(true); + if (terminated.compareAndSet(false, true)) { + WorkflowRunTracker.getInstance(project).unregister(request.workflowPath(), this); + jobConsole.close(); + notifyProcessDetached(); + } + } + + @Override + public boolean detachIsDefault() { + return false; + } + + @Override + public @Nullable OutputStream getProcessInput() { + return null; + } + + private void runWorkflow() { + try { + stdout(dispatchMessage() + "\n"); + final WorkflowRunClient.DispatchResult dispatch = client.dispatch(request); + final long id = resolveRunId(dispatch); + if (id > 0) { + runId.set(id); + } + final String conclusion = poll(id); + final String terminalConclusion = stopping.get() + ? "cancelled" + : hasText(conclusion) ? conclusion : "success"; + terminate(successful(terminalConclusion) ? 0 : 1, terminalConclusion); + } catch (final IOException | RuntimeException exception) { + if (exception instanceof WorkflowRunClient.WorkflowRunHttpException httpException && httpException.accountActionRecommended()) { + notifyAuthenticationHelp(); + } + stderr(exception.getMessage() + "\n"); + terminate(1, "failure"); + } catch (final InterruptedException exception) { + Thread.currentThread().interrupt(); + if (!stopping.get()) { + stderr(GitHubWorkflowBundle.message("workflow.run.interrupted") + "\n"); + } + terminate(1, stopping.get() ? "cancelled" : "failure"); + } + } + + private long resolveRunId(final WorkflowRunClient.DispatchResult dispatch) throws IOException, InterruptedException { + if (hasText(dispatch.htmlUrl())) { + stdout(GitHubWorkflowBundle.message("workflow.run.link", dispatch.htmlUrl()) + "\n"); + } + if (dispatch.runId() > 0) { + return dispatch.runId(); + } + stdout(GitHubWorkflowBundle.message("workflow.run.discovery") + "\n"); + for (int attempt = 0; attempt < 12 && !stopping.get(); attempt++) { + final var latest = client.latestRun(request); + if (latest.isPresent()) { + final WorkflowRunClient.RunStatus run = latest.get(); + if (hasText(run.htmlUrl())) { + stdout(GitHubWorkflowBundle.message("workflow.run.link", run.htmlUrl()) + "\n"); + } + return run.runId(); + } + TimeUnit.MILLISECONDS.sleep(pollSettings.runDiscoveryMillis()); + } + stdout(GitHubWorkflowBundle.message("workflow.run.discovery.none") + "\n"); + return -1; + } + + private String poll(final long id) throws IOException, InterruptedException { + if (id <= 0) { + return ""; + } + WorkflowRunClient.RunStatus previous = new WorkflowRunClient.RunStatus(id, "", "", ""); + final Map jobLogs = new LinkedHashMap<>(); + while (!stopping.get()) { + final WorkflowRunClient.RunStatus status = client.status(request, id); + if (!status.status().equals(previous.status()) || !status.conclusion().equals(previous.conclusion())) { + stdout(GitHubWorkflowBundle.message("workflow.run.status", status.status(), suffix(status.conclusion())) + "\n"); + previous = status; + } + if (status.completed()) { + streamJobLogs(id, jobLogs, true); + return hasText(status.conclusion()) ? status.conclusion() : "success"; + } + streamJobLogs(id, jobLogs, false); + TimeUnit.MILLISECONDS.sleep(pollSettings.statusPollMillis()); + } + return "cancelled"; + } + + private void streamJobLogs(final long id, final Map jobLogs, final boolean finalPass) throws IOException, InterruptedException { + final long now = System.currentTimeMillis(); + boolean changed = false; + for (final WorkflowRunClient.JobStatus job : client.jobs(request, id)) { + final JobLogState state = jobLogs.computeIfAbsent(job.id(), ignored -> new JobLogState()); + if (!job.status().equals(state.status) || !job.conclusion().equals(state.conclusion)) { + printJobHeader(job, state); + updateTiming(state, job, now); + final String status = GitHubWorkflowBundle.message( + "workflow.run.job.main", + statePrefix(job), + job.name(), + job.status(), + suffix(job.conclusion()), + durationSuffix(state, now) + ) + "\n"; + stdout(status); + jobConsole.jobStatus(job, GitHubWorkflowBundle.message( + "workflow.run.job.status", + statePrefix(job), + job.status(), + suffix(job.conclusion()), + durationSuffix(state, now) + ) + "\n"); + state.status = job.status(); + state.conclusion = job.conclusion(); + state.name = job.name(); + changed = true; + } + if (shouldFetchLog(job, state, now, finalPass)) { + fetchJobLog(job, state, now, finalPass); + } + } + if (changed) { + stdout(overview(jobLogs, now)); + } + } + + private boolean shouldFetchLog( + final WorkflowRunClient.JobStatus job, + final JobLogState state, + final long now, + final boolean finalPass + ) { + if (!"in_progress".equals(job.status()) && !"completed".equals(job.status())) { + return false; + } + if (finalPass || "completed".equals(job.status())) { + return !state.finalLogFetched; + } + if (now < state.nextLiveLogFetchMillis) { + return false; + } + return now - state.lastLogFetchMillis >= pollSettings.logPollMillis(); + } + + private void fetchJobLog( + final WorkflowRunClient.JobStatus job, + final JobLogState state, + final long now, + final boolean finalPass + ) throws InterruptedException { + state.lastLogFetchMillis = now; + try { + final String logs = client.jobLogs(request, job.id()); + if (hasText(logs)) { + printLogDelta(job, state, logs); + } + if (finalPass || "completed".equals(job.status())) { + state.finalLogFetched = true; + } + } catch (final IOException exception) { + if (shouldDeferLiveLogFailure(exception, finalPass)) { + if (!state.liveLogNoticeShown) { + final String notice = GitHubWorkflowBundle.message("workflow.run.logs.later") + "\n"; + if (!jobConsole.jobStatus(job, notice)) { + stdout(GitHubWorkflowBundle.message("workflow.run.job.logs.later", job.name(), notice)); + } + state.liveLogNoticeShown = true; + } + state.nextLiveLogFetchMillis = now + pollSettings.liveLogFailureRetryMillis(); + return; + } + if (finalPass || !state.logErrorShown) { + final String message = GitHubWorkflowBundle.message("workflow.run.log.failed", exception.getMessage()) + "\n"; + if (!jobConsole.jobStderr(job, message)) { + stderr(GitHubWorkflowBundle.message("workflow.run.log.failed.job", job.name(), exception.getMessage()) + "\n"); + } + state.logErrorShown = true; + } + } + } + + private void printLogDelta(final WorkflowRunClient.JobStatus job, final JobLogState state, final String logs) { + final String text = logs.stripTrailing(); + if (text.length() <= state.printedLength) { + return; + } + final String delta = text.substring(state.printedLength).stripLeading(); + if (hasText(delta)) { + if (!jobConsole.jobLog(job, delta + "\n")) { + final String rendered = state.fallbackRenderer.renderPlain(delta + "\n"); + final String fallbackText = "\n== " + job.name() + " ==\n" + rendered; + stdout(fallbackText); + } + } + state.printedLength = text.length(); + } + + private void printJobHeader(final WorkflowRunClient.JobStatus job, final JobLogState state) { + if (state.headerPrinted) { + return; + } + final String url = hasText(job.htmlUrl()) ? GitHubWorkflowBundle.message("workflow.run.job.url", job.htmlUrl()) + "\n" : ""; + final String header = GitHubWorkflowBundle.message("workflow.run.job.header", job.name()) + "\n" + url; + stdout(header); + jobConsole.jobStatus(job, header); + state.headerPrinted = true; + } + + private static void updateTiming(final JobLogState state, final WorkflowRunClient.JobStatus job, final long now) { + if (state.firstSeenMillis == 0) { + state.firstSeenMillis = now; + } + if ("in_progress".equals(job.status()) && state.startedMillis == 0) { + state.startedMillis = now; + } + if ("completed".equals(job.status()) && state.completedMillis == 0) { + state.completedMillis = now; + } + } + + private static String overview(final Map states, final long now) { + final long total = states.size(); + final long done = states.values().stream().filter(state -> "completed".equals(state.status)).count(); + final long running = states.values().stream().filter(state -> "in_progress".equals(state.status)).count(); + final StringBuilder result = new StringBuilder() + .append(GitHubWorkflowBundle.message("workflow.run.overview", progressBar(done, total), done, total, running)) + .append("\n"); + int index = 0; + for (final JobLogState state : states.values()) { + final boolean last = ++index == states.size(); + result.append(last ? "`-- " : "|-- ") + .append(statePrefix(state)) + .append(" ") + .append(state.name) + .append(durationSuffix(state, now)) + .append("\n"); + } + return result.toString(); + } + + private static String progressBar(final long done, final long total) { + if (total <= 0) { + return "[----------]"; + } + final int width = 10; + final int filled = (int) Math.min(width, Math.max(0, done * width / total)); + return "[" + "#".repeat(filled) + "-".repeat(width - filled) + "]"; + } + + private static String durationSuffix(final JobLogState state, final long now) { + final long start = state.startedMillis > 0 ? state.startedMillis : state.firstSeenMillis; + final long end = state.completedMillis > 0 ? state.completedMillis : now; + if (start <= 0 || end < start) { + return ""; + } + return " " + formatDuration(end - start); + } + + private static String formatDuration(final long millis) { + final long seconds = Math.max(0, TimeUnit.MILLISECONDS.toSeconds(millis)); + final long minutes = seconds / 60; + return String.format(Locale.ROOT, "%02d:%02d", minutes, seconds % 60); + } + + private static String statePrefix(final WorkflowRunClient.JobStatus job) { + if ("completed".equals(job.status())) { + return successful(job.conclusion()) + ? GitHubWorkflowBundle.message("workflow.run.state.ok") + : GitHubWorkflowBundle.message("workflow.run.state.fail"); + } + if ("in_progress".equals(job.status())) { + return GitHubWorkflowBundle.message("workflow.run.state.running"); + } + return GitHubWorkflowBundle.message("workflow.run.state.waiting"); + } + + private static String statePrefix(final JobLogState state) { + if ("completed".equals(state.status)) { + return successful(state.conclusion) + ? GitHubWorkflowBundle.message("workflow.run.state.ok") + : GitHubWorkflowBundle.message("workflow.run.state.fail"); + } + if ("in_progress".equals(state.status)) { + return GitHubWorkflowBundle.message("workflow.run.state.running"); + } + return GitHubWorkflowBundle.message("workflow.run.state.waiting"); + } + + private static boolean successful(final String conclusion) { + return "success".equals(conclusion) || "skipped".equals(conclusion) || "neutral".equals(conclusion); + } + + private String dispatchMessage() { + final String[] verbs = GitHubWorkflowBundle.message("workflow.run.dispatch.verbs").split("\\|"); + final String[] objects = GitHubWorkflowBundle.message("workflow.run.dispatch.objects").split("\\|"); + final String verb = verbs[ThreadLocalRandom.current().nextInt(verbs.length)]; + final String object = objects[ThreadLocalRandom.current().nextInt(objects.length)]; + return GitHubWorkflowBundle.message("workflow.run.dispatch", verb, object, request.workflowPath(), workflowUrl(), request.repositorySlug(), request.ref()); + } + + private String workflowUrl() { + final String webUrl = request.apiUrl().equals("https://api.github.com") + ? "https://github.com" + : request.apiUrl().replaceFirst("/api/v3/?$", ""); + return " (" + webUrl + "/" + request.owner() + "/" + request.repo() + "/blob/" + request.ref() + "/" + request.workflowPath() + ")"; + } + + private static boolean shouldDeferLiveLogFailure(final IOException exception, final boolean finalPass) { + return !finalPass && exception instanceof WorkflowRunClient.WorkflowRunHttpException; + } + + private static String suffix(final String conclusion) { + return conclusion == null || conclusion.isBlank() ? "" : "/" + conclusion; + } + + private static boolean hasText(final String value) { + return value != null && !value.isBlank(); + } + + void deleteRemoteRun() { + final long id = runId.get(); + if (id <= 0) { + workflowStatus(GitHubWorkflowBundle.message("workflow.run.delete.noRun") + "\n", true); + return; + } + if (!deleteRequested.compareAndSet(false, true)) { + return; + } + workflowStatus(GitHubWorkflowBundle.message("workflow.run.delete.requested", id) + "\n", false); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + final WorkflowRunClient.DeleteResult result = client.delete(request, id); + final String message = result.accepted() + ? GitHubWorkflowBundle.message("workflow.run.delete.done", id) + : GitHubWorkflowBundle.message("workflow.run.delete.http", result.statusCode()); + workflowStatus(message + "\n", !result.accepted()); + if (result.accepted()) { + jobConsole.runDeleted(id); + } else { + jobConsole.runDeleteFailed(id); + deleteRequested.set(false); + } + } catch (final IOException | InterruptedException exception) { + if (exception instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + workflowStatus(GitHubWorkflowBundle.message("workflow.run.delete.failed", exception.getMessage()) + "\n", true); + jobConsole.runDeleteFailed(id); + deleteRequested.set(false); + } + }); + } + + void rerunRemoteRun(final boolean failedOnly) { + final long id = runId.get(); + if (id <= 0) { + workflowStatus(GitHubWorkflowBundle.message("workflow.run.rerun.noRun") + "\n", true); + return; + } + final AtomicBoolean gate = failedOnly ? rerunFailedRequested : rerunAllRequested; + if (!gate.compareAndSet(false, true)) { + return; + } + workflowStatus(GitHubWorkflowBundle.message(failedOnly + ? "workflow.run.rerun.failed.requested" + : "workflow.run.rerun.all.requested", id) + "\n", false); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + final WorkflowRunClient.RerunResult result = client.rerun(request, id, failedOnly); + final String message = result.accepted() + ? GitHubWorkflowBundle.message(failedOnly + ? "workflow.run.rerun.failed.done" + : "workflow.run.rerun.all.done", id) + : GitHubWorkflowBundle.message("workflow.run.rerun.http", result.statusCode()); + workflowStatus(message + "\n", !result.accepted()); + } catch (final IOException | InterruptedException exception) { + if (exception instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + workflowStatus(GitHubWorkflowBundle.message("workflow.run.rerun.failed", exception.getMessage()) + "\n", true); + } finally { + gate.set(false); + } + }); + } + + int artifactAvailability() { + return artifactAvailability.get(); + } + + void refreshArtifactAvailability(final Consumer callback) { + final long id = runId.get(); + if (id <= 0) { + artifactAvailability.set(0); + callback.accept(false); + return; + } + final int known = artifactAvailability.get(); + if (known >= 0) { + callback.accept(known == 1); + return; + } + ApplicationManager.getApplication().executeOnPooledThread(() -> { + boolean available = false; + try { + available = client.artifacts(request, id).stream().anyMatch(artifact -> !artifact.expired()); + } catch (final IOException | InterruptedException exception) { + if (exception instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + } + artifactAvailability.set(available ? 1 : 0); + callback.accept(available); + }); + } + + void downloadJobLog(final long jobId, final String jobName) { + final long id = runId.get(); + if (id <= 0 || jobId <= 0) { + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.noRun") + "\n", true); + return; + } + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.log.requested", jobName) + "\n", false); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + final String log = client.jobLogs(request, jobId); + final Path file = WorkflowRunDownloads.writeJobLog(request, id, jobId, jobName, log); + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.log.done", file) + "\n", false); + WorkflowRunDownloads.reveal(file); + } catch (final IOException | InterruptedException exception) { + if (exception instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.failed", exception.getMessage()) + "\n", true); + } + }); + } + + void downloadArtifacts() { + final long id = runId.get(); + if (id <= 0) { + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.noRun") + "\n", true); + return; + } + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.artifacts.requested") + "\n", false); + ApplicationManager.getApplication().executeOnPooledThread(() -> { + try { + final List artifacts = client.artifacts(request, id); + if (artifacts.isEmpty()) { + artifactAvailability.set(0); + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.artifacts.empty") + "\n", false); + return; + } + Path lastFile = null; + int downloaded = 0; + for (final WorkflowRunClient.ArtifactStatus artifact : artifacts) { + if (artifact.expired()) { + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.artifact.expired", artifact.name()) + "\n", false); + continue; + } + final byte[] zip = client.artifactZip(request, artifact.id()); + lastFile = WorkflowRunDownloads.writeArtifact(request, id, artifact, zip); + downloaded++; + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.artifact.done", artifact.name(), lastFile) + "\n", false); + } + if (downloaded == 0) { + artifactAvailability.set(0); + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.artifacts.empty") + "\n", false); + return; + } + artifactAvailability.set(1); + if (lastFile != null) { + WorkflowRunDownloads.reveal(lastFile.getParent()); + } + } catch (final IOException | InterruptedException exception) { + if (exception instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + workflowStatus(GitHubWorkflowBundle.message("workflow.run.download.failed", exception.getMessage()) + "\n", true); + } + }); + } + + private void workflowStatus(final String text, final boolean error) { + jobConsole.workflowStatus(text, error); + if (!isProcessTerminated()) { + notifyTextAvailable(text, error ? ProcessOutputTypes.STDERR : ProcessOutputTypes.STDOUT); + } + } + + private void terminate(final int exitCode, final String conclusion) { + if (terminated.compareAndSet(false, true)) { + WorkflowRunTracker.getInstance(project).unregister(request.workflowPath(), this); + jobConsole.runFinished(runId.get(), conclusion); + jobConsole.close(); + notifyProcessTerminated(exitCode); + } + } + + private void notifyAuthenticationHelp() { + final var notification = NotificationGroupManager.getInstance() + .getNotificationGroup("GitHub Workflow") + .createNotification( + GitHubWorkflowBundle.message("workflow.run.notification.auth", GitHubRequestAuthorizations.settingsHint()), + NotificationType.WARNING + ); + notification.addAction(NotificationAction.createSimple(GitHubWorkflowBundle.message("workflow.run.notification.openSettings"), () -> + ApplicationManager.getApplication().invokeLater(() -> + ShowSettingsUtil.getInstance().showSettingsDialog(project, "GitHub")))); + notification.notify(project); + } + + private void stdout(final String text) { + notifyTextAvailable(text, ProcessOutputTypes.STDOUT); + } + + private void stderr(final String text) { + notifyTextAvailable(text, ProcessOutputTypes.STDERR); + } + + record PollSettings(long statusPollMillis, long logPollMillis, long runDiscoveryMillis, long liveLogFailureRetryMillis) { + + PollSettings(final long statusPollMillis, final long logPollMillis, final long runDiscoveryMillis) { + this(statusPollMillis, logPollMillis, runDiscoveryMillis, Math.max(logPollMillis, 60_000)); + } + + private static PollSettings defaults() { + return new PollSettings(10_000, 30_000, 2_000, 60_000); + } + } + + private static final class JobLogState { + private String name = "job"; + private String status = ""; + private String conclusion = ""; + private long firstSeenMillis = 0; + private long startedMillis = 0; + private long completedMillis = 0; + private int printedLength = 0; + private long lastLogFetchMillis = 0; + private long nextLiveLogFetchMillis = 0; + private final WorkflowRunLogRenderer fallbackRenderer = new WorkflowRunLogRenderer(); + private boolean finalLogFetched = false; + private boolean logErrorShown = false; + private boolean headerPrinted = false; + private boolean liveLogNoticeShown = false; + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunRequest.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunRequest.java new file mode 100644 index 0000000..651f63c --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunRequest.java @@ -0,0 +1,34 @@ +package com.github.yunabraska.githubworkflow.services; + +import java.util.Map; + +/** + * Request data needed to dispatch and observe one GitHub Actions workflow run. + * + * @param apiUrl GitHub REST API base URL + * @param owner repository owner + * @param repo repository name + * @param workflowPath workflow file path or file name + * @param ref branch or tag used for workflow dispatch + * @param inputs workflow_dispatch input values + * @param tokenEnvVar optional environment variable used only after IDE GitHub accounts fail or are unavailable + */ +public record WorkflowRunRequest( + String apiUrl, + String owner, + String repo, + String workflowPath, + String ref, + Map inputs, + String tokenEnvVar +) { + + public WorkflowRunRequest { + inputs = Map.copyOf(inputs == null ? Map.of() : inputs); + } + + public String repositorySlug() { + return owner + "/" + repo; + } + +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java new file mode 100644 index 0000000..adff3ff --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java @@ -0,0 +1,143 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.table.JBTable; +import org.jetbrains.annotations.NotNull; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.table.DefaultTableModel; +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.Map; +import java.util.Objects; + +/** + * Plain Swing settings editor for GitHub Workflow run configurations. + */ +public final class WorkflowRunSettingsEditor extends SettingsEditor { + + private final JPanel panel = new JPanel(new BorderLayout(8, 8)); + private final JTextField apiUrl = new JTextField(); + private final JTextField owner = new JTextField(); + private final JTextField repo = new JTextField(); + private final JTextField workflowPath = new JTextField(); + private final JTextField ref = new JTextField(); + private final JTextField tokenEnvVar = new JTextField(); + private final JPanel inputPanel = new JPanel(new BorderLayout(4, 4)); + private final DefaultTableModel inputsModel = new DefaultTableModel(new Object[][]{}, new Object[]{ + GitHubWorkflowBundle.message("documentation.name.label"), + GitHubWorkflowBundle.message("documentation.value.label") + }) { + @Override + public boolean isCellEditable(final int row, final int column) { + return true; + } + }; + private final JBTable inputsTable = new JBTable(inputsModel); + + public WorkflowRunSettingsEditor() { + final JPanel fields = new JPanel(new GridBagLayout()); + fields.setBorder(BorderFactory.createEmptyBorder(8, 8, 0, 8)); + addRow(fields, 0, GitHubWorkflowBundle.message("workflow.run.field.apiUrl"), apiUrl); + addRow(fields, 1, GitHubWorkflowBundle.message("workflow.run.field.owner"), owner); + addRow(fields, 2, GitHubWorkflowBundle.message("workflow.run.field.repo"), repo); + addRow(fields, 3, GitHubWorkflowBundle.message("workflow.run.field.workflow"), workflowPath); + addRow(fields, 4, GitHubWorkflowBundle.message("workflow.run.field.ref"), ref); + addRow(fields, 5, GitHubWorkflowBundle.message("workflow.run.field.tokenEnv"), tokenEnvVar); + panel.add(fields, BorderLayout.NORTH); + + inputsTable.setFillsViewportHeight(true); + inputPanel.setBorder(BorderFactory.createTitledBorder(GitHubWorkflowBundle.message("workflow.run.inputs.title"))); + inputPanel.add(ToolbarDecorator.createDecorator(inputsTable) + .setAddAction(button -> addInputRow("", "")) + .setRemoveAction(button -> removeSelectedInputRows()) + .disableUpDownActions() + .createPanel(), BorderLayout.CENTER); + panel.add(inputPanel, BorderLayout.CENTER); + } + + @Override + protected void resetEditorFrom(@NotNull final WorkflowRunConfiguration configuration) { + apiUrl.setText(configuration.apiUrl()); + owner.setText(configuration.owner()); + repo.setText(configuration.repo()); + workflowPath.setText(configuration.workflowPath()); + ref.setText(configuration.ref()); + tokenEnvVar.setText(configuration.tokenEnvVar()); + resetInputs(configuration); + } + + @Override + protected void applyEditorTo(@NotNull final WorkflowRunConfiguration configuration) throws ConfigurationException { + configuration.apiUrl(apiUrl.getText()) + .owner(owner.getText()) + .repo(repo.getText()) + .workflowPath(workflowPath.getText()) + .ref(ref.getText()) + .tokenEnvVar(tokenEnvVar.getText()) + .inputsText(inputsText()); + } + + @Override + protected @NotNull JComponent createEditor() { + return panel; + } + + private static void addRow(final JPanel panel, final int row, final String label, final JTextField field) { + final GridBagConstraints labelConstraints = new GridBagConstraints(); + labelConstraints.gridx = 0; + labelConstraints.gridy = row; + labelConstraints.anchor = GridBagConstraints.WEST; + labelConstraints.insets = new Insets(2, 0, 2, 8); + panel.add(new JLabel(label), labelConstraints); + + final GridBagConstraints fieldConstraints = new GridBagConstraints(); + fieldConstraints.gridx = 1; + fieldConstraints.gridy = row; + fieldConstraints.weightx = 1; + fieldConstraints.fill = GridBagConstraints.HORIZONTAL; + fieldConstraints.insets = new Insets(2, 0, 2, 0); + panel.add(field, fieldConstraints); + } + + private void resetInputs(final WorkflowRunConfiguration configuration) { + inputsModel.setRowCount(0); + for (final Map.Entry entry : WorkflowDispatchInputs.parseKeyValueText(configuration.inputsText()).entrySet()) { + addInputRow(entry.getKey(), entry.getValue()); + } + } + + private void addInputRow(final String key, final String value) { + inputsModel.addRow(new Object[]{key, value}); + } + + private void removeSelectedInputRows() { + final int[] selectedRows = inputsTable.getSelectedRows(); + for (int index = selectedRows.length - 1; index >= 0; index--) { + inputsModel.removeRow(inputsTable.convertRowIndexToModel(selectedRows[index])); + } + } + + private String inputsText() { + if (inputsTable.isEditing() && inputsTable.getCellEditor() != null) { + inputsTable.getCellEditor().stopCellEditing(); + } + final StringBuilder result = new StringBuilder(); + for (int row = 0; row < inputsModel.getRowCount(); row++) { + final String key = Objects.toString(inputsModel.getValueAt(row, 0), "").trim(); + if (!key.isBlank()) { + final String value = Objects.toString(inputsModel.getValueAt(row, 1), ""); + result.append(key).append("=").append(value).append("\n"); + } + } + return result.toString(); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunTracker.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunTracker.java new file mode 100644 index 0000000..63086b9 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunTracker.java @@ -0,0 +1,65 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.Service; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Tracks workflow runs started from this project so editor gutter actions can switch between run and stop. + */ +@Service(Service.Level.PROJECT) +public final class WorkflowRunTracker { + + private final Project project; + private final ConcurrentMap runs = new ConcurrentHashMap<>(); + + public WorkflowRunTracker(@NotNull final Project project) { + this.project = project; + } + + public static WorkflowRunTracker getInstance(final Project project) { + return project.getService(WorkflowRunTracker.class); + } + + public static String key(final String workflowPath) { + return Optional.ofNullable(workflowPath).orElse("").replace('\\', '/'); + } + + public boolean isRunning(final String workflowPath) { + return runs.containsKey(key(workflowPath)); + } + + public void register(final String workflowPath, final ProcessHandler processHandler) { + runs.put(key(workflowPath), processHandler); + refreshGutters(); + } + + public void unregister(final String workflowPath, final ProcessHandler processHandler) { + runs.remove(key(workflowPath), processHandler); + refreshGutters(); + } + + public boolean stop(final String workflowPath) { + return Optional.ofNullable(runs.get(key(workflowPath))) + .map(processHandler -> { + processHandler.destroyProcess(); + return true; + }) + .orElse(false); + } + + private void refreshGutters() { + ApplicationManager.getApplication().invokeLater(() -> { + if (!project.isDisposed()) { + DaemonCodeAnalyzer.getInstance(project).settingsChanged(); + } + }); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowSyntaxSchema.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowSyntaxSchema.java new file mode 100644 index 0000000..08057f5 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowSyntaxSchema.java @@ -0,0 +1,328 @@ +package com.github.yunabraska.githubworkflow.services; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * GitHub Actions workflow syntax completion tables from the public workflow syntax reference. + */ +final class WorkflowSyntaxSchema { + + private WorkflowSyntaxSchema() { + } + + static Map topLevelKeys() { + return mapWithBundle( + "completion.workflow.top.", + "name", + "run-name", + "on", + "permissions", + "env", + "defaults", + "concurrency", + "jobs" + ); + } + + static Map eventKeys() { + return mapWithBundle( + "completion.workflow.event.", + "branch_protection_rule", + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "discussion", + "discussion_comment", + "fork", + "gollum", + "image_version", + "issue_comment", + "issues", + "label", + "merge_group", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "pull_request_target", + "push", + "registry_package", + "release", + "repository_dispatch", + "schedule", + "status", + "watch", + "workflow_call", + "workflow_dispatch", + "workflow_run" + ); + } + + static Map eventFilterKeys() { + return mapWithBundle( + "completion.workflow.eventFilter.", + "types", + "branches", + "branches-ignore", + "tags", + "tags-ignore", + "paths", + "paths-ignore", + "workflows", + "cron" + ); + } + + static Map eventFilterKeysFor(final String event) { + return switch (event) { + case "schedule" -> mapWithBundle("completion.workflow.eventFilter.", "cron"); + case "workflow_run" -> mapWithBundle("completion.workflow.eventFilter.", "workflows", "types", "branches", "branches-ignore"); + case "push" -> mapWithBundle("completion.workflow.eventFilter.", "branches", "branches-ignore", "tags", "tags-ignore", "paths", "paths-ignore"); + case "pull_request", "pull_request_target" -> mapWithBundle("completion.workflow.eventFilter.", "types", "branches", "branches-ignore", "paths", "paths-ignore"); + default -> eventFilterKeys(); + }; + } + + static Map eventActivityTypesFor(final String event) { + return switch (event) { + case "branch_protection_rule" -> activityTypes("created", "deleted"); + case "check_run" -> activityTypes("created", "rerequested", "completed", "requested_action"); + case "check_suite" -> activityTypes("completed"); + case "discussion" -> activityTypes( + "created", "edited", "deleted", "transferred", "pinned", "unpinned", "labeled", "unlabeled", + "locked", "unlocked", "category_changed", "answered", "unanswered" + ); + case "discussion_comment", "issue_comment", "pull_request_review_comment" -> activityTypes("created", "edited", "deleted"); + case "issues" -> activityTypes( + "opened", "edited", "deleted", "transferred", "pinned", "unpinned", "closed", "reopened", + "assigned", "unassigned", "labeled", "unlabeled", "locked", "unlocked", "milestoned", "demilestoned" + ); + case "label" -> activityTypes("created", "edited", "deleted"); + case "merge_group" -> activityTypes("checks_requested"); + case "milestone" -> activityTypes("created", "closed", "opened", "edited", "deleted"); + case "pull_request", "pull_request_target" -> activityTypes( + "assigned", "unassigned", "labeled", "unlabeled", "opened", "edited", "closed", "reopened", + "synchronize", "converted_to_draft", "locked", "unlocked", "enqueued", "dequeued", + "milestoned", "demilestoned", "ready_for_review", "review_requested", "review_request_removed", + "auto_merge_enabled", "auto_merge_disabled" + ); + case "pull_request_review" -> activityTypes("submitted", "edited", "dismissed"); + case "registry_package" -> activityTypes("published", "updated"); + case "release" -> activityTypes("published", "unpublished", "created", "edited", "deleted", "prereleased", "released"); + case "watch" -> activityTypes("started"); + case "workflow_run" -> activityTypes("completed", "requested", "in_progress"); + default -> java.util.Collections.emptyMap(); + }; + } + + static Map permissionScopes() { + return mapWithBundle( + "completion.workflow.permission.", + "actions", + "artifact-metadata", + "attestations", + "checks", + "code-quality", + "contents", + "deployments", + "discussions", + "id-token", + "issues", + "models", + "packages", + "pages", + "pull-requests", + "security-events", + "statuses", + "vulnerability-alerts" + ); + } + + static Map permissionValues() { + return mapWithBundle("completion.workflow.permission.value.", "read", "write", "none"); + } + + static Map permissionValuesFor(final String permission) { + if ("id-token".equals(permission)) { + return mapWithBundle("completion.workflow.permission.value.", "write", "none"); + } + if ("models".equals(permission) || "vulnerability-alerts".equals(permission)) { + return mapWithBundle("completion.workflow.permission.value.", "read", "none"); + } + return permissionValues(); + } + + static Map permissionShorthandValues() { + final Map values = new LinkedHashMap<>(); + values.put("read-all", "read-all"); + values.put("write-all", "write-all"); + values.put("{}", "empty"); + return mapWithBundleKeys("completion.workflow.permission.shorthand.", values); + } + + static Map jobKeys() { + return mapWithBundle( + "completion.workflow.job.", + "name", + "permissions", + "needs", + "if", + "runs-on", + "snapshot", + "environment", + "concurrency", + "outputs", + "env", + "defaults", + "steps", + "timeout-minutes", + "strategy", + "continue-on-error", + "container", + "services", + "uses", + "with", + "secrets" + ); + } + + static Map defaultsRunKeys() { + return mapWithBundle("completion.workflow.defaultsRun.", "shell", "working-directory"); + } + + static Map concurrencyKeys() { + return mapWithBundle("completion.workflow.concurrency.", "group", "cancel-in-progress"); + } + + static Map environmentKeys() { + return mapWithBundle("completion.workflow.environment.", "name", "url"); + } + + static Map strategyKeys() { + return mapWithBundle("completion.workflow.strategy.", "matrix", "fail-fast", "max-parallel"); + } + + static Map matrixKeys() { + return mapWithBundle("completion.workflow.matrix.", "include", "exclude"); + } + + static Map stepKeys() { + return mapWithBundle( + "completion.workflow.step.", + "id", + "if", + "name", + "uses", + "run", + "shell", + "with", + "env", + "continue-on-error", + "timeout-minutes", + "working-directory" + ); + } + + static Map containerKeys() { + return mapWithBundle("completion.workflow.container.", "image", "credentials", "env", "ports", "volumes", "options"); + } + + static Map serviceKeys() { + return mapWithBundle("completion.workflow.service.", "image", "credentials", "env", "ports", "volumes", "options"); + } + + static Map credentialsKeys() { + return mapWithBundle("completion.workflow.credentials.", "username", "password"); + } + + static Map workflowInputTypes() { + return mapWithBundle("completion.workflow.inputType.", "string", "boolean", "choice", "number", "environment"); + } + + static Map reusableWorkflowInputTypes() { + return mapWithBundle("completion.workflow.inputType.", "string", "boolean", "number"); + } + + static Map workflowInputPropertyKeys() { + final Map result = new LinkedHashMap<>(); + result.put("description", GitHubWorkflowBundle.message("documentation.description.label")); + result.put("type", GitHubWorkflowBundle.message("documentation.type", "string | boolean | choice | number | environment")); + result.put("required", GitHubWorkflowBundle.message("documentation.required", true)); + result.put("default", GitHubWorkflowBundle.message("documentation.default", "")); + result.put("options", GitHubWorkflowBundle.message("documentation.value.label")); + return java.util.Collections.unmodifiableMap(result); + } + + static Map workflowOutputPropertyKeys() { + final Map result = new LinkedHashMap<>(); + result.put("description", GitHubWorkflowBundle.message("documentation.description.label")); + result.put("value", GitHubWorkflowBundle.message("documentation.value.label")); + return java.util.Collections.unmodifiableMap(result); + } + + static Map workflowSecretPropertyKeys() { + final Map result = new LinkedHashMap<>(); + result.put("description", GitHubWorkflowBundle.message("documentation.description.label")); + result.put("required", GitHubWorkflowBundle.message("documentation.required", true)); + return java.util.Collections.unmodifiableMap(result); + } + + static Map booleanValues() { + return mapWithBundle("completion.workflow.boolean.", "true", "false"); + } + + static Map runnerLabels() { + return mapWithBundle( + "completion.workflow.runner.", + "ubuntu-latest", + "ubuntu-24.04", + "ubuntu-22.04", + "windows-latest", + "windows-2025", + "windows-2022", + "macos-latest", + "macos-15", + "macos-14", + "self-hosted" + ); + } + + private static Map map(final String... keys) { + final Map result = new LinkedHashMap<>(); + for (final String key : keys) { + result.put(key, GitHubWorkflowBundle.message("completion.workflow.syntax")); + } + return java.util.Collections.unmodifiableMap(result); + } + + private static Map mapWithBundle(final String prefix, final String... keys) { + final Map result = new LinkedHashMap<>(); + for (final String key : keys) { + result.put(key, GitHubWorkflowBundle.message(prefix + key)); + } + return java.util.Collections.unmodifiableMap(result); + } + + private static Map mapWithBundleKeys(final String prefix, final Map keysToBundleSuffix) { + final Map result = new LinkedHashMap<>(); + keysToBundleSuffix.forEach((key, bundleSuffix) -> result.put(key, GitHubWorkflowBundle.message(prefix + bundleSuffix))); + return java.util.Collections.unmodifiableMap(result); + } + + private static Map activityTypes(final String... keys) { + final Map result = new LinkedHashMap<>(); + for (final String key : keys) { + result.put(key, GitHubWorkflowBundle.message("completion.workflow.eventFilter.types")); + } + return java.util.Collections.unmodifiableMap(result); + } +} diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowTextAttributes.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowTextAttributes.java new file mode 100644 index 0000000..8a79383 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowTextAttributes.java @@ -0,0 +1,31 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; + +public final class WorkflowTextAttributes { + + public static final TextAttributesKey VARIABLE_REFERENCE = TextAttributesKey.createTextAttributesKey( + "GITHUB_WORKFLOW_VARIABLE_REFERENCE", + DefaultLanguageHighlighterColors.CONSTANT + ); + + public static final TextAttributesKey DECLARATION = TextAttributesKey.createTextAttributesKey( + "GITHUB_WORKFLOW_DECLARATION", + DefaultLanguageHighlighterColors.STATIC_FIELD + ); + + public static final TextAttributesKey RUNNER_VARIABLE = TextAttributesKey.createTextAttributesKey( + "GITHUB_WORKFLOW_RUNNER_VARIABLE", + DefaultLanguageHighlighterColors.GLOBAL_VARIABLE + ); + + public static final TextAttributesKey SCALAR_LITERAL = TextAttributesKey.createTextAttributesKey( + "GITHUB_WORKFLOW_SCALAR_LITERAL", + DefaultLanguageHighlighterColors.NUMBER + ); + + private WorkflowTextAttributes() { + // constants + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 895c300..df1483b 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -3,6 +3,7 @@ com.github.yunabraska.githubworkflowplugin Github Workflow Yuna Morgenstern + messages.GitHubWorkflowBundle com.intellij.modules.platform @@ -17,12 +18,29 @@ + + + + + + + + + @@ -30,16 +48,39 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/github-docs/default-env.tsv b/src/main/resources/github-docs/default-env.tsv new file mode 100644 index 0000000..d7ceb55 --- /dev/null +++ b/src/main/resources/github-docs/default-env.tsv @@ -0,0 +1,45 @@ +# Generated by ./gradlew generateGitHubDocsData from GitHub Docs. +CI Always set to true. +GITHUB_ACTION The name of the action currently running, or the id of a step. For example, for an action, __repo-owner_name-of-action-repo. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same script or action more than once in the same job, the name will include a suffix that consists of the sequence number preceded by an underscore. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2. +GITHUB_ACTION_PATH The path where an action is located. This property is only supported in composite actions. You can use this path to change directories to where the action is located and access other files in that same repository. For example, /home/runner/work/_actions/repo-owner/name-of-action-repo/v1. +GITHUB_ACTION_REPOSITORY For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout. +GITHUB_ACTIONS Always set to true when GitHub Actions is running the workflow. You can use this variable to differentiate when tests are being run locally or by GitHub Actions. +GITHUB_ACTOR The name of the person or app that initiated the workflow. For example, octocat. +GITHUB_ACTOR_ID The account ID of the person or app that triggered the initial workflow run. For example, 1234567. Note that this is different from the actor username. +GITHUB_API_URL Returns the API URL. For example: https://api.github.com. +GITHUB_BASE_REF The name of the base ref or target branch of the pull request in a workflow run. This is only set when the event that triggers a workflow run is either pull_request or pull_request_target. For example, main. +GITHUB_ENV The path on the runner to the file that sets variables from workflow commands. The path to this file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/set_env_87406d6e-4979-4d42-98e1-3dab1f48b13a. For more information, see Workflow commands for GitHub Actions. +GITHUB_EVENT_NAME The name of the event that triggered the workflow. For example, workflow_dispatch. +GITHUB_EVENT_PATH The path to the file on the runner that contains the full event webhook payload. For example, /github/workflow/event.json. +GITHUB_GRAPHQL_URL Returns the GraphQL API URL. For example: https://api.github.com/graphql. +GITHUB_HEAD_REF The head ref or source branch of the pull request in a workflow run. This property is only set when the event that triggers a workflow run is either pull_request or pull_request_target. For example, feature-branch-1. +GITHUB_JOB The job_id of the current job. For example, greeting_job. +GITHUB_OUTPUT The path on the runner to the file that sets the current step's outputs from workflow commands. The path to this file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/set_output_a50ef383-b063-46d9-9157-57953fc9f3f0. For more information, see Workflow commands for GitHub Actions. +GITHUB_PATH The path on the runner to the file that sets system PATH variables from workflow commands. The path to this file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/add_path_899b9445-ad4a-400c-aa89-249f18632cf5. For more information, see Workflow commands for GitHub Actions. +GITHUB_REF The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request that were not merged, this is the pull request merge branch. If the pull request was merged, this is the branch it was merged into. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/. For pull request events except pull_request_target that were not merged, it is refs/pull//merge. pull_request_target events have the ref from the base branch. For tags it is refs/tags/. For example, refs/heads/feature-branch-1. For more information about pull request merge branches, see About pull requests. +GITHUB_REF_NAME The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. For pull requests that were not merged, the format is /merge. +GITHUB_REF_PROTECTED true if branch protections or rulesets are configured for the ref that triggered the workflow run. +GITHUB_REF_TYPE The type of ref that triggered the workflow run. Valid values are branch or tag. +GITHUB_REPOSITORY The owner and repository name. For example, octocat/Hello-World. +GITHUB_REPOSITORY_ID The ID of the repository. For example, 123456789. Note that this is different from the repository name. +GITHUB_REPOSITORY_OWNER The repository owner's name. For example, octocat. +GITHUB_REPOSITORY_OWNER_ID The repository owner's account ID. For example, 1234567. Note that this is different from the owner's name. +GITHUB_RETENTION_DAYS The number of days that workflow run logs and artifacts are kept. For example, 90. +GITHUB_RUN_ATTEMPT A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. For example, 3. +GITHUB_RUN_ID A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. For example, 1658821493. +GITHUB_RUN_NUMBER A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. For example, 3. +GITHUB_SERVER_URL The URL of the GitHub server. For example: https://github.com. +GITHUB_SHA The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see Events that trigger workflows. For example, ffac537e6cbbf934b08745a378932722df287a53. +GITHUB_STEP_SUMMARY The path on the runner to the file that contains job summaries from workflow commands. The path to this file is unique to the current step and changes for each step in a job. For example, /home/runner/_layout/_work/_temp/_runner_file_commands/step_summary_1cb22d7f-5663-41a8-9ffc-13472605c76c. For more information, see Workflow commands for GitHub Actions. +GITHUB_TRIGGERING_ACTOR The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. +GITHUB_WORKFLOW The name of the workflow. For example, My test workflow. If the workflow file doesn't specify a name, the value of this variable is the full path of the workflow file in the repository. +GITHUB_WORKFLOW_REF The ref path to the workflow. For example, octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch. +GITHUB_WORKFLOW_SHA The commit SHA for the workflow file. +GITHUB_WORKSPACE The default working directory on the runner for steps, and the default location of your repository when using the checkout action. For example, /home/runner/work/my-repo-name/my-repo-name. +RUNNER_ARCH The architecture of the runner executing the job. Possible values are X86, X64, ARM, or ARM64. +RUNNER_DEBUG This is set only if debug logging is enabled, and always has the value of 1. It can be useful as an indicator to enable additional debugging or verbose logging in your own job steps. +RUNNER_ENVIRONMENT The environment of the runner executing the job. Possible values are: github-hosted for GitHub-hosted runners provided by GitHub, and self-hosted for self-hosted runners configured by the repository owner. +RUNNER_NAME The name of the runner executing the job. This name may not be unique in a workflow run as runners at the repository and organization levels could use the same name. For example, Hosted Agent +RUNNER_OS The operating system of the runner executing the job. Possible values are Linux, Windows, or macOS. For example, Windows +RUNNER_TEMP The path to a temporary directory on the runner. This directory is emptied at the beginning and end of each job. Note that files will not be removed if the runner's user account does not have permission to delete them. For example, D:\a\_temp +RUNNER_TOOL_CACHE The path to the directory containing preinstalled tools for GitHub-hosted runners. For more information, see GitHub-hosted runners. For example, C:\hostedtoolcache\windows diff --git a/src/main/resources/github-docs/github-context.tsv b/src/main/resources/github-docs/github-context.tsv new file mode 100644 index 0000000..8fb0063 --- /dev/null +++ b/src/main/resources/github-docs/github-context.tsv @@ -0,0 +1,40 @@ +# Generated by ./gradlew generateGitHubDocsData from GitHub Docs. +action The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2. +action_path The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action, for example by changing directories to the path (using the corresponding environment variable): cd "$GITHUB_ACTION_PATH" . For more information on environment variables, see Secure use reference. +action_ref For a step executing an action, this is the ref of the action being executed. For example, v2. Do not use in the run keyword. To make this context work with composite actions, reference it within the env context of the composite action. +action_repository For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout. Do not use in the run keyword. To make this context work with composite actions, reference it within the env context of the composite action. +action_status For a composite action, the current result of the composite action. +actor The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from github.triggering_actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. +actor_id The account ID of the person or app that triggered the initial workflow run. For example, 1234567. Note that this is different from the actor username. +api_url The URL of the GitHub REST API. +base_ref The base_ref or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. +env Path on the runner to the file that sets environment variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see Workflow commands for GitHub Actions. +event The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in Events that trigger workflows. For example, for a workflow run triggered by the push event, this object contains the contents of the push webhook payload. +event_name The name of the event that triggered the workflow run. +event_path The path to the file on the runner that contains the full event webhook payload. +graphql_url The URL of the GitHub GraphQL API. +head_ref The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. +job The job_id of the current job. Note: This context property is set by the Actions runner, and is only available within the execution steps of a job. Otherwise, the value of this property will be null. +path Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see Workflow commands for GitHub Actions. +ref The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request that were not merged, this is the pull request merge branch. If the pull request was merged, this is the branch it was merged into. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/. For pull request events except pull_request_target that were not merged, it is refs/pull//merge. pull_request_target events have the ref from the base branch. For tags it is refs/tags/. For example, refs/heads/feature-branch-1. For more information about pull request merge branches, see About pull requests. +ref_name The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. For pull requests that were not merged, the format is /merge. +ref_protected true if branch protections or rulesets are configured for the ref that triggered the workflow run. +ref_type The type of ref that triggered the workflow run. Valid values are branch or tag. +repository The owner and repository name. For example, octocat/Hello-World. +repository_id The ID of the repository. For example, 123456789. Note that this is different from the repository name. +repository_owner The repository owner's username. For example, octocat. +repository_owner_id The repository owner's account ID. For example, 1234567. Note that this is different from the owner's name. +repositoryUrl The Git URL to the repository. For example, git://github.com/octocat/hello-world.git. +retention_days The number of days that workflow run logs and artifacts are kept. +run_id A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. +run_number A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. +run_attempt A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. +secret_source The source of a secret used in a workflow. Possible values are None, Actions, Codespaces, or Dependabot. +server_url The URL of the GitHub server. For example: https://github.com. +sha The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see Events that trigger workflows. For example, ffac537e6cbbf934b08745a378932722df287a53. +token A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the GITHUB_TOKEN secret. For more information, see Use GITHUB_TOKEN for authentication in workflows. Note: This context property is set by the Actions runner, and is only available within the execution steps of a job. Otherwise, the value of this property will be null. +triggering_actor The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. +workflow The name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository. +workflow_ref The ref path to the workflow. For example, octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch. +workflow_sha The commit SHA for the workflow file. +workspace The default working directory on the runner for steps, and the default location of your repository when using the checkout action. diff --git a/src/main/resources/messages/GitHubWorkflowBundle.properties b/src/main/resources/messages/GitHubWorkflowBundle.properties new file mode 100644 index 0000000..2771e50 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub Workflow +plugin.description=Support for GitHub Actions workflow files +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=GitHub Workflow plugin tools +action.GitHubWorkflow.RefreshActionCache.text=Refresh Action Cache +action.GitHubWorkflow.RefreshActionCache.description=Refresh resolved remote GitHub Actions and reusable workflow metadata +action.GitHubWorkflow.RestoreActionWarnings.text=Restore Action Warnings +action.GitHubWorkflow.RestoreActionWarnings.description=Restore suppressed action, input, and output validation warnings +action.GitHubWorkflow.ClearActionCache.text=Clear Action Cache +action.GitHubWorkflow.ClearActionCache.description=Clear cached GitHub Actions and reusable workflow metadata +notification.cache.cleared=Cleared {0} cached GitHub Workflow entries. +notification.cache.refresh.started=Refreshing {0} cached remote GitHub Workflow entries. +notification.warnings.restored=Restored warnings for {0} GitHub Workflow entries. +workflow.run.configuration.display=GitHub Workflow +workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs +workflow.run.configuration.name=GitHub Workflow: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=Owner +workflow.run.field.repo=Repository +workflow.run.field.workflow=Workflow file +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Token env var fallback +workflow.run.inputs.title=workflow_dispatch inputs (key=value) +workflow.run.error.apiUrl=GitHub API URL is required. +workflow.run.error.repository=GitHub repository owner and name are required. +workflow.run.error.workflow=Workflow file is required. +workflow.run.error.ref=Branch or tag ref is required. +workflow.run.error.inputs=GitHub workflow_dispatch supports at most 25 inputs. +workflow.run.gutter.stop=Stop workflow run +workflow.run.gutter.stop.text=Stop workflow run +workflow.run.gutter.stop.description=Cancel this run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.run.cancel.requested=Cancel requested: {0}. +workflow.run.stop.before.id=Stop requested. No run id yet. +workflow.run.cancel.http=Cancel HTTP {0}. Oof. +workflow.run.cancel.failed=Cancel fizzled: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=Run accepted. Hunting run id. +workflow.run.discovery.none=No run id yet. Actions tab knows more. +workflow.run.status=Status: {0}{1} +workflow.run.job.main=Job: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Status: {0} {1}{2}{3} +workflow.run.logs.later=Logs will appear when GitHub publishes them. +workflow.run.job.logs.later=Job {0}: {1} +workflow.run.log.failed=Log download failed: {0} +workflow.run.log.failed.job=Log download failed for {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Job: {0} +workflow.run.job.fallbackName=Job {0} +workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running +workflow.run.state.ok=[OK] +workflow.run.state.fail=[FAIL] +workflow.run.state.running=[RUN] +workflow.run.state.waiting=[WAIT] +workflow.run.dispatch.verbs=Priming|Queuing|Summoning|Launching|Booting +workflow.run.dispatch.objects=workflow|automation|pipeline|run +workflow.run.dispatch={0} {1} {2}{3} for {4} on {5}. +workflow.run.notification.auth=GitHub workflow dispatch needs an authenticated GitHub account. Add or refresh accounts in {0}. +workflow.run.notification.openSettings=Open GitHub Settings +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +inspection.action.delete.invalid=Delete invalid {0} [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Toggle warnings [{0}] for [{1}] +inspection.warning.on=on +inspection.warning.off=off +inspection.statement.incomplete=Incomplete statement [{0}] +inspection.invalid.suffix.remove=Remove invalid suffix [{0}] +inspection.replace.with=Replace with [{0}] +inspection.invalid.remove=Remove invalid [{0}] +inspection.workflow.syntax.unknownTopLevelKey=Unknown workflow key [{0}] +inspection.workflow.syntax.unknownEventKey=Unknown workflow event [{0}] +inspection.workflow.syntax.unknownTriggerKey=Unknown trigger key [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Unknown trigger filter [{0}] +inspection.workflow.syntax.unknownTriggerValue=Unknown trigger value [{0}] +inspection.workflow.syntax.unknownPermission=Unknown permission [{0}] +inspection.workflow.syntax.unknownPermissionValue=Unknown permission value [{0}] +inspection.workflow.syntax.unknownJobKey=Unknown job key [{0}] +inspection.workflow.syntax.unknownStepKey=Unknown step key [{0}] +inspection.action.reload=Reload [{0}] +inspection.action.unresolved=Unresolved [{0}] - check GitHub account access, private repository permissions, rate limits, missing refs, or missing action/workflow metadata +inspection.action.jump=Jump to file [{0}] +inspection.output.unused=Unused [{0}] +inspection.secret.invalid.if=Remove [{0}] - Secrets are not valid in `if` statements +inspection.secret.replace.runtime=Replace [{0}] with [{1}] - if it is not provided at runtime +inspection.needs.invalid.job=Remove invalid jobId [{0}] - this jobId doesn''t match any previous job +documentation.description=Description: {0} +documentation.type=Type: {0} +documentation.required=Required: {0} +documentation.default=Default: {0} +documentation.deprecated=Deprecated: {0} +documentation.open.declaration=Open declaration ({0}) +documentation.input.label=Input +documentation.secret.label=Secret +documentation.env.label=Environment variable +documentation.matrix.label=Matrix property +documentation.need.label=Needed job +documentation.need.description=Direct job dependency. +documentation.needOutput.label=Needed job output +documentation.reusableJob.label=Reusable workflow job +documentation.reusableJob.description=Job declared in this reusable workflow. +documentation.reusableJobOutput.label=Reusable workflow job output +documentation.service.label=Service container +documentation.servicePort.label=Service port +documentation.container.label=Job container +documentation.symbol.label=Workflow symbol +documentation.symbol.description=Resolved workflow expression. +documentation.workflowOutput.label=Workflow output +documentation.jobOutput.label=Job output +documentation.action.label=Action +documentation.externalAction.label=External action +documentation.reusableWorkflow.label=Reusable workflow +documentation.resolvedFrom=resolved from {0} +documentation.notResolved=not resolved yet +documentation.inputs.title=Inputs +documentation.outputs.title=Outputs +documentation.secrets.title=Secrets +documentation.value.label=Value +documentation.step.title=Step {0} +documentation.name.label=Name +documentation.uses.label=Uses +documentation.run.label=Run +documentation.description.label=Description +documentation.step.label=Step +documentation.source.label=Source +documentation.stepOutput.label=Step output +documentation.context.github=github context +documentation.context.github.description=Information about the current workflow run and event. +documentation.context.gitea=gitea context +documentation.context.gitea.description=Gitea-compatible alias for the GitHub Actions context. +documentation.context.inputs=inputs context +documentation.context.inputs.description=Workflow, dispatch, or action inputs available here. +documentation.context.secrets=secrets context +documentation.context.secrets.description=Secret values available to this workflow or reusable workflow call. +documentation.context.env=env context +documentation.context.env.description=Environment variables visible at this location. +documentation.context.matrix=matrix context +documentation.context.matrix.description=Matrix values for the current job. +documentation.context.steps=steps context +documentation.context.steps.description=Previous steps in the current job, including outputs and status. +documentation.context.needs=needs context +documentation.context.needs.description=Direct job dependencies and their outputs/results. +documentation.context.jobs=jobs context +documentation.context.jobs.description=Reusable workflow jobs and outputs. +documentation.context.outputs=outputs +documentation.context.outputs.description=Output values exposed by this step or job. +documentation.context.result=result +documentation.context.result.description=Job result: success, failure, cancelled, or skipped. +documentation.context.outcome=outcome +documentation.context.outcome.description=Step result before continue-on-error is applied. +documentation.context.conclusion=conclusion +documentation.context.conclusion.description=Step result after continue-on-error is applied. +error.report.action=Report Exception +error.report.description=Description +error.report.steps=Steps to Reproduce +error.report.sample=Please provide code sample if applicable +error.report.message=Message +error.report.runtime=Runtime Information +error.report.pluginVersion=Plugin version: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=Stacktrace +completion.shell.bash=Bash shell. Uses bash on Linux and macOS runners, and Git for Windows bash on Windows runners. +completion.shell.sh=POSIX shell fallback. +completion.shell.pwsh=PowerShell Core. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Windows command prompt. +completion.shell.python=Python command runner. +completion.runner.name=The name of the runner executing the job. +completion.runner.os=The operating system of the runner executing the job. Possible values are Linux, Windows, or macOS. +completion.runner.arch=The architecture of the runner executing the job. Possible values are X86, X64, ARM, or ARM64. +completion.runner.temp=The path to a temporary directory on the runner. This directory is emptied at the beginning and end of each job. Note that files will not be removed if the runner''s user account does not have permission to delete them. +completion.runner.toolCache=The path to the directory containing preinstalled tools for GitHub-hosted runners. +completion.runner.debug=This is set only if debug logging is enabled, and always has the value of 1. +completion.runner.environment=The environment of the runner executing the job. Possible values are github-hosted or self-hosted. +completion.job.status=The current status of the job. +completion.job.checkRunId=The check run ID of the current job. +completion.job.container=Information about the job''s container. +completion.job.services=The service containers created for a job. +completion.job.workflowRef=The full ref of the workflow file that defines the current job. +completion.job.workflowSha=The commit SHA of the workflow file that defines the current job. +completion.job.workflowRepository=The owner/repo of the repository containing the workflow file that defines the current job. +completion.job.workflowFilePath=The workflow file path, relative to the repository root. +completion.job.containerField=Job container field +completion.job.service=Job service +completion.job.serviceField=Job service field +completion.job.mappedServicePort=Mapped service port +completion.strategy.failFast=Whether all in-progress jobs are canceled if any matrix job fails. +completion.strategy.jobIndex=The zero-based index of the current job in the matrix. +completion.strategy.jobTotal=The total number of jobs in the matrix. +completion.strategy.maxParallel=The maximum number of matrix jobs that can run simultaneously. +completion.context.inputs=Workflow inputs, such as workflow_dispatch or workflow_call. +completion.context.secrets=Workflow secrets. +completion.context.job=Information about the currently running job. +completion.context.jobs=Workflow jobs. +completion.context.matrix=Matrix properties defined for the current matrix job. +completion.context.strategy=Matrix execution strategy information for the current job. +completion.context.steps=Steps with an id in the current job. +completion.context.env=Environment variables from jobs and steps. +completion.context.vars=Custom configuration variables from organization, repository, and environment scopes. +completion.context.needs=Jobs that must complete before this job can run, plus their outputs and results. +completion.context.github=Workflow run and event information from the GitHub context. +completion.context.gitea=Gitea-compatible alias for the GitHub Actions context. +completion.context.runner=Information about the runner executing the current job. +completion.secret.githubToken=Automatically created token for each workflow run. +completion.remote.repository=Remote repository +completion.uses.local.workflow=Local reusable workflow +completion.uses.local.action=Local action +completion.uses.ref.known=Known workflow reference +completion.uses.ref.remote=Remote workflow reference +completion.uses.remote.known=Known remote action or reusable workflow +completion.workflow.syntax=GitHub Actions workflow syntax +completion.workflow.top.name=Workflow display name +completion.workflow.top.run-name=Dynamic run name +completion.workflow.top.on=Events that start the workflow +completion.workflow.top.permissions=Default GITHUB_TOKEN permissions +completion.workflow.top.env=Workflow-wide environment variables +completion.workflow.top.defaults=Default job and step settings +completion.workflow.top.concurrency=Concurrency group and cancellation +completion.workflow.top.jobs=Jobs that run in this workflow +completion.workflow.event.branch_protection_rule=Branch protection changed +completion.workflow.event.check_run=Single check run changed +completion.workflow.event.check_suite=Check suite changed +completion.workflow.event.create=Branch or tag created +completion.workflow.event.delete=Branch or tag deleted +completion.workflow.event.deployment=Deployment created +completion.workflow.event.deployment_status=Deployment status changed +completion.workflow.event.discussion=Discussion changed +completion.workflow.event.discussion_comment=Discussion comment changed +completion.workflow.event.fork=Repository forked +completion.workflow.event.gollum=Wiki page changed +completion.workflow.event.image_version=Package image version changed +completion.workflow.event.issue_comment=Issue or PR comment changed +completion.workflow.event.issues=Issue changed +completion.workflow.event.label=Label changed +completion.workflow.event.merge_group=Merge queue check requested +completion.workflow.event.milestone=Milestone changed +completion.workflow.event.page_build=Pages build ran +completion.workflow.event.project=Classic project changed +completion.workflow.event.project_card=Classic project card changed +completion.workflow.event.project_column=Classic project column changed +completion.workflow.event.public=Repository became public +completion.workflow.event.pull_request=Pull request changed +completion.workflow.event.pull_request_review=PR review changed +completion.workflow.event.pull_request_review_comment=PR review comment changed +completion.workflow.event.pull_request_target=PR target context. Sharp knives. +completion.workflow.event.push=Commit or tag pushed +completion.workflow.event.registry_package=Package published or updated +completion.workflow.event.release=Release changed +completion.workflow.event.repository_dispatch=Custom API event +completion.workflow.event.schedule=Cron tick. Clockwork. +completion.workflow.event.status=Commit status changed +completion.workflow.event.watch=Repository starred +completion.workflow.event.workflow_call=Reusable workflow call +completion.workflow.event.workflow_dispatch=Manual run button +completion.workflow.event.workflow_run=Workflow run changed +completion.workflow.eventFilter.types=Limit activity types +completion.workflow.eventFilter.branches=Only these branches +completion.workflow.eventFilter.branches-ignore=Skip these branches +completion.workflow.eventFilter.tags=Only these tags +completion.workflow.eventFilter.tags-ignore=Skip these tags +completion.workflow.eventFilter.paths=Only these paths +completion.workflow.eventFilter.paths-ignore=Skip these paths +completion.workflow.eventFilter.workflows=Workflow names to watch +completion.workflow.eventFilter.cron=Cron schedule. Tiny clockwork. +completion.workflow.permission.actions=Workflow runs and action artifacts +completion.workflow.permission.artifact-metadata=Artifact metadata records +completion.workflow.permission.attestations=Artifact attestations +completion.workflow.permission.checks=Check runs and suites +completion.workflow.permission.code-quality=Code quality reports +completion.workflow.permission.contents=Repository contents +completion.workflow.permission.deployments=Deployments +completion.workflow.permission.discussions=Discussions +completion.workflow.permission.id-token=OpenID Connect tokens +completion.workflow.permission.issues=Issues +completion.workflow.permission.models=GitHub Models +completion.workflow.permission.packages=GitHub Packages +completion.workflow.permission.pages=GitHub Pages +completion.workflow.permission.pull-requests=Pull requests +completion.workflow.permission.security-events=Code scanning and security events +completion.workflow.permission.statuses=Commit statuses +completion.workflow.permission.vulnerability-alerts=Dependabot alerts +completion.workflow.permission.value.read=Read access +completion.workflow.permission.value.write=Write access, read included +completion.workflow.permission.value.none=No access +completion.workflow.permission.shorthand.read-all=All permissions read. Big blanket. +completion.workflow.permission.shorthand.write-all=All permissions write. Big hammer. +completion.workflow.permission.shorthand.empty=Disable token permissions +completion.workflow.job.name=Job display name +completion.workflow.job.permissions=Job token permissions +completion.workflow.job.needs=Jobs to wait for +completion.workflow.job.if=Job condition +completion.workflow.job.runs-on=Runner label or group +completion.workflow.job.snapshot=Runner snapshot +completion.workflow.job.environment=Deployment environment +completion.workflow.job.concurrency=Job concurrency lock +completion.workflow.job.outputs=Outputs other jobs can read +completion.workflow.job.env=Job environment variables +completion.workflow.job.defaults=Job default settings +completion.workflow.job.steps=Step list. The actual work. +completion.workflow.job.timeout-minutes=Job timeout in minutes +completion.workflow.job.strategy=Matrix and scheduling strategy +completion.workflow.job.continue-on-error=Let this job fail softly +completion.workflow.job.container=Container for this job +completion.workflow.job.services=Sidecar service containers +completion.workflow.job.uses=Reusable workflow to call +completion.workflow.job.with=Inputs for called workflow +completion.workflow.job.secrets=Secrets for called workflow +completion.workflow.defaultsRun.shell=Default shell for run steps +completion.workflow.defaultsRun.working-directory=Default working directory +completion.workflow.concurrency.group=Lock name for queued runs +completion.workflow.concurrency.cancel-in-progress=Cancel older matching runs +completion.workflow.environment.name=Environment name +completion.workflow.environment.url=Environment URL +completion.workflow.strategy.matrix=Matrix axes and variants +completion.workflow.strategy.fail-fast=Cancel matrix siblings on failure +completion.workflow.strategy.max-parallel=Matrix parallelism cap +completion.workflow.matrix.include=Add matrix combinations +completion.workflow.matrix.exclude=Remove matrix combinations +completion.workflow.step.id=Step id for references +completion.workflow.step.if=Step condition +completion.workflow.step.name=Step display name +completion.workflow.step.uses=Action to run +completion.workflow.step.run=Shell script to run +completion.workflow.step.shell=Shell for this run step +completion.workflow.step.with=Action inputs +completion.workflow.step.env=Step environment variables +completion.workflow.step.continue-on-error=Let this step fail softly +completion.workflow.step.timeout-minutes=Step timeout in minutes +completion.workflow.step.working-directory=Step working directory +completion.workflow.container.image=Container image +completion.workflow.container.credentials=Registry credentials +completion.workflow.container.env=Container environment variables +completion.workflow.container.ports=Ports to expose +completion.workflow.container.volumes=Volumes to mount +completion.workflow.container.options=Docker create options +completion.workflow.service.image=Service container image +completion.workflow.service.credentials=Registry credentials +completion.workflow.service.env=Service environment variables +completion.workflow.service.ports=Service ports +completion.workflow.service.volumes=Service volumes +completion.workflow.service.options=Docker create options +completion.workflow.credentials.username=Registry username +completion.workflow.credentials.password=Registry password or token +completion.workflow.inputType.string=Text input +completion.workflow.inputType.boolean=True or false input +completion.workflow.inputType.choice=Dropdown choice input +completion.workflow.inputType.number=Number input +completion.workflow.inputType.environment=Environment picker input +completion.workflow.boolean.true=Yes. Flip it on. +completion.workflow.boolean.false=No. Keep it dark. +completion.workflow.runner.ubuntu-latest=Latest Ubuntu runner +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 runner +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 runner +completion.workflow.runner.windows-latest=Latest Windows runner +completion.workflow.runner.windows-2025=Windows Server 2025 runner +completion.workflow.runner.windows-2022=Windows Server 2022 runner +completion.workflow.runner.macos-latest=Latest macOS runner +completion.workflow.runner.macos-15=macOS 15 runner +completion.workflow.runner.macos-14=macOS 14 runner +completion.workflow.runner.self-hosted=Your own runner. Your circus. +completion.steps.outputs=The set of outputs defined for the step. +completion.steps.conclusion=The result of a completed step after continue-on-error is applied. +completion.steps.outcome=The result of a completed step before continue-on-error is applied. +completion.jobs.outputs=The set of outputs defined for the job. +completion.jobs.result=The result of the job. +settings.displayName=GitHub Workflow +settings.language.label=Language: +settings.language.system=IDE/system default +settings.cache.title=Action cache +settings.cache.column.key=Cache key +settings.cache.column.name=Name +settings.cache.column.kind=Kind +settings.cache.column.state=State +settings.cache.column.expires=Expires +settings.cache.kind.local=local +settings.cache.kind.remote=remote +settings.cache.state.resolved=resolved +settings.cache.state.pending=pending +settings.cache.state.expired=stale +settings.cache.state.suppressed=suppressed +settings.cache.refresh=Refresh table +settings.cache.deleteSelected=Delete selected +settings.cache.deleteAll=Delete all +settings.cache.export=Export +settings.cache.import=Import +settings.cache.summary=Cache: {0} entries, {1} resolved, {2} remote, {3} stale, {4} muted. Cache: {5} KB. +settings.cache.noneSelected=Select cache rows first. The broom refuses guesswork. +settings.cache.deleteSelected.done=Deleted {0} cache entries. Tiny dust cloud contained. +settings.cache.deleteAll.confirm=Delete all GitHub Workflow cache entries? +settings.cache.deleteAll.done=Deleted all cache entries. The cache is now suspiciously quiet. +settings.cache.export.done=Exported {0} cache entries. Small archive beast caged. +settings.cache.import.done=Imported cache entries. The archive beast behaved. +settings.cache.import.unsupported=Unsupported GitHub Workflow cache file. +settings.cache.import.brokenLine=Broken GitHub Workflow cache line. +settings.cache.import.brokenKey=Broken GitHub Workflow cache key. +settings.support.button=Support this plugin +settings.support.tooltip=Open the support page +settings.support.line.0=Feed the build furnace +settings.support.line.1=Buy the parser coffee +settings.support.line.2=Sponsor fewer haunted workflows +workflow.run.jobs.title=Workflow jobs +workflow.run.jobs.root=Workflow run +workflow.run.jobs.description=GitHub Actions job tree and selected job log +workflow.run.tree.done=done +workflow.run.tree.failed=failed +workflow.run.tree.skipped=skipped +workflow.run.tree.warn=warn +workflow.run.tree.err=err +workflow.run.delete.tooltip=Delete run +workflow.run.delete.noRun=No run id yet. +workflow.run.delete.requested=Deleting run {0}. +workflow.run.delete.done=Run {0} deleted. +workflow.run.delete.http=Delete HTTP {0}. Oof. +workflow.run.delete.failed=Delete fizzled: {0} +workflow.run.rerun.all.tooltip=Rerun workflow +workflow.run.rerun.failed.tooltip=Rerun failed jobs +workflow.run.rerun.noRun=No run id yet. +workflow.run.rerun.all.requested=Rerun requested: {0}. +workflow.run.rerun.failed.requested=Failed jobs requested: {0}. +workflow.run.rerun.all.done=Rerun queued: {0}. +workflow.run.rerun.failed.done=Failed jobs queued: {0}. +workflow.run.rerun.http=Rerun HTTP {0}. Oof. +workflow.run.rerun.failed=Rerun fizzled: {0} +workflow.run.download.log.tooltip=Save job log +workflow.run.download.artifacts.tooltip=Download artifacts +workflow.run.download.noRun=No run id yet. +workflow.run.download.log.requested=Fetching log for {0}. +workflow.run.download.log.done=Log saved: {0}. +workflow.run.download.artifacts.requested=Fetching artifacts. +workflow.run.download.artifacts.empty=No artifacts. Tiny void. +workflow.run.download.artifact.expired=Artifact expired: {0}. +workflow.run.download.artifact.done=Artifact saved: {0} -> {1}. +workflow.run.download.failed=Download fizzled: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ar.properties b/src/main/resources/messages/GitHubWorkflowBundle_ar.properties new file mode 100644 index 0000000..a371bad --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_ar.properties @@ -0,0 +1,442 @@ +plugin.name=ุณูŠุฑ ุงู„ุนู…ู„ GitHub +plugin.description=ุฏุนู… ู…ู„ูุงุช ุณูŠุฑ ุนู…ู„ ุฅุฌุฑุงุกุงุช GitHub +group.GitHubWorkflow.Tools.text=ุณูŠุฑ ุงู„ุนู…ู„ GitHub +group.GitHubWorkflow.Tools.description=GitHub ุฃุฏูˆุงุช ุงู„ุจุฑู†ุงู…ุฌ ุงู„ู…ุณุงุนุฏ ู„ุณูŠุฑ ุงู„ุนู…ู„ +action.GitHubWorkflow.RefreshActionCache.text=Refresh ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ู„ู„ุนู…ู„ +action.GitHubWorkflow.RefreshActionCache.description=ู‚ุงู… Refresh ุจุญู„ ุฅุฌุฑุงุกุงุช GitHub ุนู† ุจุนุฏ ูˆุจูŠุงู†ุงุช ุชุนุฑูŠู ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ุฉ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… +action.GitHubWorkflow.RestoreActionWarnings.text=ุงุณุชุนุงุฏุฉ ุชุญุฐูŠุฑุงุช ุงู„ุนู…ู„ +action.GitHubWorkflow.RestoreActionWarnings.description=ุงุณุชุนุงุฏุฉ ุชุญุฐูŠุฑุงุช ุงู„ุชุญู‚ู‚ ู…ู† ุตุญุฉ ุงู„ุฅุฌุฑุงุกุงุช ูˆุงู„ู…ุฏุฎู„ุงุช ูˆุงู„ู…ุฎุฑุฌุงุช +action.GitHubWorkflow.ClearActionCache.text=ู…ุณุญ ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ู„ู„ุนู…ู„ +action.GitHubWorkflow.ClearActionCache.description=ุงู…ุณุญ ุฅุฌุฑุงุกุงุช GitHub ุงู„ู…ุฎุฒู†ุฉ ู…ุคู‚ุชู‹ุง ูˆุจูŠุงู†ุงุช ุชุนุฑูŠู ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ุฉ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… +notification.cache.cleared=ุชู… ู…ุณุญ ุฅุฏุฎุงู„ุงุช ุณูŠุฑ ุนู…ู„ {0} ุงู„ู…ุฎุฒู†ุฉ ู…ุคู‚ุชู‹ุง. +notification.cache.refresh.started=Refreshing {0} ู‚ุงู… ุจุชุฎุฒูŠู† ุฅุฏุฎุงู„ุงุช ุณูŠุฑ ุงู„ุนู…ู„ GitHub ุนู† ุจุนุฏ ู…ุคู‚ุชู‹ุง. +notification.warnings.restored=ุชู…ุช ุงุณุชุนุงุฏุฉ ุงู„ุชุญุฐูŠุฑุงุช ู„ุฅุฏุฎุงู„ุงุช ุณูŠุฑ ุงู„ุนู…ู„ {0} GitHub. +workflow.run.configuration.display=ุณูŠุฑ ุงู„ุนู…ู„ GitHub +workflow.run.configuration.description=ุฅุฑุณุงู„ ูˆู…ุชุงุจุนุฉ ุณูŠุฑ ุนู…ู„ ุฅุฌุฑุงุกุงุช GitHub +workflow.run.configuration.name=ุณูŠุฑ ุงู„ุนู…ู„ GitHub: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=ู…ุงู„ูƒ +workflow.run.field.repo=ู…ุณุชูˆุฏุน +workflow.run.field.workflow=ู…ู„ู ุณูŠุฑ ุงู„ุนู…ู„ +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=ุงู„ุฑู…ุฒ ุงู„ู…ู…ูŠุฒ env ูุงุฑ ุงุญุชูŠุงุทูŠ +workflow.run.inputs.title=ู…ุฏุฎู„ุงุช workflow_dispatch (ุงู„ู…ูุชุงุญ=ุงู„ู‚ูŠู…ุฉ) +workflow.run.error.apiUrl=ู…ุทู„ูˆุจ GitHub API URL. +workflow.run.error.repository=ู…ุทู„ูˆุจ ุงุณู… ูˆู…ุงู„ูƒ ู…ุณุชูˆุฏุน GitHub. +workflow.run.error.workflow=ู…ุทู„ูˆุจ ู…ู„ู ุณูŠุฑ ุงู„ุนู…ู„. +workflow.run.error.ref=ู…ุทู„ูˆุจ ูุฑุน ุฃูˆ ุนู„ุงู…ุฉ ุงู„ู…ุฑุฌุน. +workflow.run.error.inputs=GitHub ูŠุฏุนู… workflow_dispatch 25 ู…ุฏุฎู„ุงู‹ ุนู„ู‰ ุงู„ุฃูƒุซุฑ. +workflow.run.gutter.stop=ุฅูŠู‚ุงู ุชุดุบูŠู„ ุณูŠุฑ ุงู„ุนู…ู„ +workflow.run.gutter.stop.text=ุฅูŠู‚ุงู ุชุดุบูŠู„ ุณูŠุฑ ุงู„ุนู…ู„ +workflow.run.gutter.stop.description=ุฅู„ุบุงุก ู‡ุฐุง ุงู„ุชุดุบูŠู„ +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=ูŠุฌุฑูŠ: +workflow.log.warning=ุชุญุฐูŠุฑ: +workflow.log.error=ุฎุทุฃ: +workflow.run.cancel.requested=ุงู„ุฅู„ุบุงุก ุงู„ู…ุทู„ูˆุจ: {0}. +workflow.run.stop.before.id=ุทู„ุจ ุงู„ุชูˆู‚ู. ู„ุง ูŠูˆุฌุฏ ู…ุนุฑู ุชุดุบูŠู„ ุญุชู‰ ุงู„ุขู†. +workflow.run.cancel.http=ุฅู„ุบุงุก HTTP {0}. ุนููˆุง. +workflow.run.cancel.failed=ูุดู„ ุงู„ุฅู„ุบุงุก: {0} +workflow.run.interrupted=ุชู…ุช ู…ู‚ุงุทุนุชู‡. +workflow.run.link=ุชุดุบูŠู„: {0} +workflow.run.discovery=ุชุดุบูŠู„ ู…ู‚ุจูˆู„. ู…ุนุฑู ุชุดุบูŠู„ ุงู„ุตูŠุฏ +workflow.run.discovery.none=ู„ุง ูŠูˆุฌุฏ ู…ุนุฑู ุชุดุบูŠู„ ุญุชู‰ ุงู„ุขู†. ุนู„ุงู…ุฉ ุงู„ุชุจูˆูŠุจ "ุงู„ุฅุฌุฑุงุกุงุช" ุชุนุฑู ุงู„ู…ุฒูŠุฏ. +workflow.run.status=ุงู„ุญุงู„ุฉ: {0}{1} +workflow.run.job.main=ุงู„ูˆุธูŠูุฉ: {0} {1} [{2}{3}{4}] +workflow.run.job.status=ุงู„ุญุงู„ุฉ: {0} {1}{2}{3} +workflow.run.logs.later=ุณุชุธู‡ุฑ ุงู„ุณุฌู„ุงุช ุนู†ุฏู…ุง ูŠู†ุดุฑู‡ุง GitHub. +workflow.run.job.logs.later=ุงู„ูˆุธูŠูุฉ {0}: {1} +workflow.run.log.failed=ูุดู„ ุชู†ุฒูŠู„ ุงู„ุณุฌู„: {0} +workflow.run.log.failed.job=ูุดู„ ุชู†ุฒูŠู„ ุงู„ุณุฌู„ ู„ู€ {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=ุงู„ูˆุธูŠูุฉ: {0} +workflow.run.job.fallbackName=ุงู„ูˆุธูŠูุฉ {0} +workflow.run.overview=ุชู… ุชู†ููŠุฐ ุณูŠุฑ ุงู„ุนู…ู„ {0} {1}/{2}ุŒ ูˆุชุดุบูŠู„ {3} +workflow.run.state.ok=[ู†ุนู…] +workflow.run.state.fail=[ูŠูุดู„] +workflow.run.state.running=[ูŠุฌุฑูŠ] +workflow.run.state.waiting=[ุงู†ุชุธุฑ] +workflow.run.dispatch.verbs=ุงู„ุชู…ู‡ูŠุฏ|ุงู„ุงู†ุชุธุงุฑ|ุงู„ุงุณุชุฏุนุงุก|ุงู„ุชุดุบูŠู„|ุงู„ุชู…ู‡ูŠุฏ +workflow.run.dispatch.objects=ุณูŠุฑ ุงู„ุนู…ู„|ุงู„ุฃุชู…ุชุฉ|ุฎุท ุงู„ุฃู†ุงุจูŠุจ|ุงู„ุชุดุบูŠู„ +workflow.run.dispatch={0} {1} {2}{3} ู„ู€ {4} ุนู„ู‰ {5}. +workflow.run.notification.auth=ูŠุญุชุงุฌ ุฅุฑุณุงู„ ุณูŠุฑ ุนู…ู„ GitHub ุฅู„ู‰ ุญุณุงุจ GitHub ุชู…ุช ู…ุตุงุฏู‚ุชู‡. ุฅุถุงูุฉ ุฃูˆ ุชุญุฏูŠุซ ุงู„ุญุณุงุจุงุช ููŠ {0}. +workflow.run.notification.openSettings=ุงูุชุญ ุฅุนุฏุงุฏุงุช GitHub +workflow.cache.progress.title=ุญู„ ุฅุฌุฑุงุกุงุช GitHub +workflow.cache.progress.text=ุญู„ {0} {1} +workflow.cache.kind.action=ูุนู„ +workflow.cache.kind.workflow=ุณูŠุฑ ุงู„ุนู…ู„ +inspection.parameter.input=ู…ุฏุฎู„ +inspection.parameter.secret=ุณุฑ +inspection.action.delete.invalid=ุญุฐู {0} ุบูŠุฑ ุตุงู„ุญ [{1}] +inspection.action.update.major=ุชุญุฏูŠุซ ุงู„ุฅุฌุฑุงุก [{0}] ุฅู„ู‰ [{1}] +inspection.warning.toggle=ุชุจุฏูŠู„ ุงู„ุชุญุฐูŠุฑุงุช [{0}] ู„ู€ [{1}] +inspection.warning.on=ุนู„ู‰ +inspection.warning.off=ุนู† +inspection.statement.incomplete=ุจูŠุงู† ุบูŠุฑ ู…ูƒุชู…ู„ [{0}] +inspection.invalid.suffix.remove=ุฅุฒุงู„ุฉ ุงู„ู„ุงุญู‚ุฉ ุบูŠุฑ ุงู„ุตุงู„ุญุฉ [{0}] +inspection.replace.with=ุงุณุชุจุฏู„ ุจู€ [{0}] +inspection.invalid.remove=ุฅุฒุงู„ุฉ [{0}] ุบูŠุฑ ุงู„ุตุงู„ุญ +inspection.workflow.syntax.unknownTopLevelKey=ู…ูุชุงุญ ุณูŠุฑ ุนู…ู„ ุบูŠุฑ ู…ุนุฑูˆู [{0}] +inspection.workflow.syntax.unknownEventKey=ุญุฏุซ ุณูŠุฑ ุนู…ู„ ุบูŠุฑ ู…ุนุฑูˆู [{0}] +inspection.workflow.syntax.unknownTriggerKey=ู…ูุชุงุญ ุชุดุบูŠู„ ุบูŠุฑ ู…ุนุฑูˆู [{0}] +inspection.workflow.syntax.unknownTriggerFilter=ุนุงู…ู„ ุชุตููŠุฉ ู…ุดุบู„ ุบูŠุฑ ู…ุนุฑูˆู [{0}] +inspection.workflow.syntax.unknownTriggerValue=ู‚ูŠู…ุฉ ุชุดุบูŠู„ ุบูŠุฑ ู…ุนุฑูˆูุฉ [{0}] +inspection.workflow.syntax.unknownPermission=ุฅุฐู† ุบูŠุฑ ู…ุนุฑูˆู [{0}] +inspection.workflow.syntax.unknownPermissionValue=ู‚ูŠู…ุฉ ุฅุฐู† ุบูŠุฑ ู…ุนุฑูˆูุฉ [{0}] +inspection.workflow.syntax.unknownJobKey=ู…ูุชุงุญ ู…ู‡ู…ุฉ ุบูŠุฑ ู…ุนุฑูˆู [{0}] +inspection.workflow.syntax.unknownStepKey=ู…ูุชุงุญ ุฎุทูˆุฉ ุบูŠุฑ ู…ุนุฑูˆู [{0}] +inspection.action.reload=ุฅุนุงุฏุฉ ุชุญู…ูŠู„ [{0}] +inspection.action.unresolved=ู„ู… ูŠุชู… ุญู„ [{0}] - ุชุญู‚ู‚ ู…ู† ุงู„ูˆุตูˆู„ ุฅู„ู‰ ุญุณุงุจ GitHubุŒ ุฃูˆ ุฃุฐูˆู†ุงุช ุงู„ู…ุณุชูˆุฏุน ุงู„ุฎุงุตุŒ ุฃูˆ ุญุฏูˆุฏ ุงู„ู…ุนุฏู„ุŒ ุฃูˆ ุงู„ู…ุฑุงุฌุน ุงู„ู…ูู‚ูˆุฏุฉุŒ ุฃูˆ ุจูŠุงู†ุงุช ุชุนุฑูŠู ุงู„ุฅุฌุฑุงุก/ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู…ูู‚ูˆุฏุฉ +inspection.action.jump=ุงู†ุชู‚ู„ ุฅู„ู‰ ุงู„ู…ู„ู [{0}] +inspection.output.unused=ุบูŠุฑ ู…ุณุชุฎุฏู… [{0}] +inspection.secret.invalid.if=ุฅุฒุงู„ุฉ [{0}] - ุงู„ุฃุณุฑุงุฑ ุบูŠุฑ ุตุงู„ุญุฉ ููŠ ุนุจุงุฑุงุช `if` +inspection.secret.replace.runtime=ุงุณุชุจุฏู„ [{0}] ุจู€ [{1}] - ุฅุฐุง ู„ู… ูŠุชู… ุชูˆููŠุฑู‡ ููŠ ูˆู‚ุช ุงู„ุชุดุบูŠู„ +inspection.needs.invalid.job=ู‚ู… ุจุฅุฒุงู„ุฉ ู…ุนุฑู ุงู„ูˆุธูŠูุฉ ุบูŠุฑ ุงู„ุตุงู„ุญ [{0}] - ู„ุง ูŠุชุทุงุจู‚ ู…ุนุฑู ุงู„ูˆุธูŠูุฉ ู‡ุฐุง ู…ุน ุฃูŠ ู…ู‡ู…ุฉ ุณุงุจู‚ุฉ +documentation.description=ุงู„ูˆุตู: {0} +documentation.type=ุงู„ู†ูˆุน: {0} +documentation.required=ู…ุทู„ูˆุจ: {0} +documentation.default=ุงู„ุงูุชุฑุงุถูŠ: {0} +documentation.deprecated=ู…ู‡ู…ู„: {0} +documentation.open.declaration=ุฅุนู„ุงู† ู…ูุชูˆุญ ({0}) +documentation.input.label=ุงู„ุฅุฏุฎุงู„ +documentation.secret.label=ุณุฑ +documentation.env.label=ู…ุชุบูŠุฑ ุงู„ุจูŠุฆุฉ +documentation.matrix.label=ุฎุงุตูŠุฉ ุงู„ู…ุตููˆูุฉ +documentation.need.label=ุงู„ูˆุธูŠูุฉ ุงู„ู…ุทู„ูˆุจุฉ +documentation.need.description=ุงู„ุงุนุชู…ุงุฏ ุงู„ู…ุจุงุดุฑ ุนู„ู‰ ุงู„ูˆุธูŠูุฉ. +documentation.needOutput.label=ู…ุทู„ูˆุจ ู…ุฎุฑุฌุงุช ุงู„ุนู…ู„ +documentation.reusableJob.label=ูˆุธูŠูุฉ ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ุฉ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… +documentation.reusableJob.description=ุชู… ุงู„ุฅุนู„ุงู† ุนู† ุงู„ูˆุธูŠูุฉ ููŠ ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… ู‡ุฐุง. +documentation.reusableJobOutput.label=ู…ุฎุฑุฌุงุช ู…ู‡ู…ุฉ ุณูŠุฑ ุงู„ุนู…ู„ ู‚ุงุจู„ุฉ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… +documentation.service.label=ุญุงูˆูŠุฉ ุงู„ุฎุฏู…ุฉ +documentation.servicePort.label=ู…ู†ูุฐ ุงู„ุฎุฏู…ุฉ +documentation.container.label=ุญุงูˆูŠุฉ ุงู„ูˆุธูŠูุฉ +documentation.symbol.label=ุฑู…ุฒ ุณูŠุฑ ุงู„ุนู…ู„ +documentation.symbol.description=ุชู… ุญู„ ุชุนุจูŠุฑ ุณูŠุฑ ุงู„ุนู…ู„. +documentation.workflowOutput.label=ุฅุฎุฑุงุฌ ุณูŠุฑ ุงู„ุนู…ู„ +documentation.jobOutput.label=ู…ุฎุฑุฌุงุช ุงู„ูˆุธูŠูุฉ +documentation.action.label=ุงู„ุนู…ู„ +documentation.externalAction.label=ุงู„ุนู…ู„ ุงู„ุฎุงุฑุฌูŠ +documentation.reusableWorkflow.label=ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… +documentation.resolvedFrom=ุชู… ุญู„ู‡ุง ู…ู† {0} +documentation.notResolved=ู„ู… ูŠุชู… ุญู„ู‡ุง ุจุนุฏ +documentation.inputs.title=ุงู„ู…ุฏุฎู„ุงุช +documentation.outputs.title=ุงู„ู†ูˆุงุชุฌ +documentation.secrets.title=ุฃุณุฑุงุฑ +documentation.value.label=ุงู„ู‚ูŠู…ุฉ +documentation.step.title=ุงู„ุฎุทูˆุฉ {0} +documentation.name.label=ุงู„ุงุณู… +documentation.uses.label=ุงู„ุงุณุชุฎุฏุงู…ุงุช +documentation.run.label=ุชุดุบูŠู„ +documentation.description.label=ุงู„ูˆุตู +documentation.step.label=ุฎุทูˆุฉ +documentation.source.label=ุงู„ู…ุตุฏุฑ +documentation.stepOutput.label=ุฅุฎุฑุงุฌ ุงู„ุฎุทูˆุฉ +documentation.context.github=ุณูŠุงู‚ ุฌูŠุซุจ +documentation.context.github.description=ู…ุนู„ูˆู…ุงุช ุญูˆู„ ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ุญุงู„ูŠ ูˆุงู„ุญุฏุซ. +documentation.context.gitea=ุณูŠุงู‚ ุฌูŠุชูŠุง +documentation.context.gitea.description=ุงู„ุงุณู… ุงู„ู…ุณุชุนุงุฑ ุงู„ู…ุชูˆุงูู‚ ู…ุน Gitea ู„ุณูŠุงู‚ ุฅุฌุฑุงุกุงุช GitHub. +documentation.context.inputs=ุณูŠุงู‚ ุงู„ู…ุฏุฎู„ุงุช +documentation.context.inputs.description=ุชุชูˆูุฑ ู‡ู†ุง ู…ุฏุฎู„ุงุช ุณูŠุฑ ุงู„ุนู…ู„ ุฃูˆ ุงู„ุฅุฑุณุงู„ ุฃูˆ ุงู„ุฅุฌุฑุงุก. +documentation.context.secrets=ุณูŠุงู‚ ุงู„ุฃุณุฑุงุฑ +documentation.context.secrets.description=ุงู„ู‚ูŠู… ุงู„ุณุฑูŠุฉ ุงู„ู…ุชูˆูุฑุฉ ู„ุณูŠุฑ ุงู„ุนู…ู„ ู‡ุฐุง ุฃูˆ ุงุณุชุฏุนุงุก ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู…. +documentation.context.env=ุณูŠุงู‚ ุงู„ุจูŠุฆุฉ +documentation.context.env.description=ู…ุชุบูŠุฑุงุช ุงู„ุจูŠุฆุฉ ู…ุฑุฆูŠุฉ ููŠ ู‡ุฐุง ุงู„ู…ูˆู‚ุน. +documentation.context.matrix=ุณูŠุงู‚ ุงู„ู…ุตููˆูุฉ +documentation.context.matrix.description=ู‚ูŠู… ุงู„ู…ุตููˆูุฉ ู„ู„ูˆุธูŠูุฉ ุงู„ุญุงู„ูŠุฉ. +documentation.context.steps=ุณูŠุงู‚ ุงู„ุฎุทูˆุงุช +documentation.context.steps.description=ุงู„ุฎุทูˆุงุช ุงู„ุณุงุจู‚ุฉ ููŠ ุงู„ูˆุธูŠูุฉ ุงู„ุญุงู„ูŠุฉ ุจู…ุง ููŠ ุฐู„ูƒ ุงู„ู…ุฎุฑุฌุงุช ูˆุงู„ุญุงู„ุฉ. +documentation.context.needs=ูŠุญุชุงุฌ ุงู„ุณูŠุงู‚ +documentation.context.needs.description=ุงู„ุชุจุนูŠุงุช ุงู„ูˆุธูŠููŠุฉ ุงู„ู…ุจุงุดุฑุฉ ูˆู…ุฎุฑุฌุงุชู‡ุง/ู†ุชุงุฆุฌู‡ุง. +documentation.context.jobs=ุณูŠุงู‚ ุงู„ูˆุธุงุฆู +documentation.context.jobs.description=ูˆุธุงุฆู ูˆู…ุฎุฑุฌุงุช ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ุฉ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู…. +documentation.context.outputs=ุงู„ู†ูˆุงุชุฌ +documentation.context.outputs.description=ู‚ูŠู… ุงู„ุฅุฎุฑุงุฌ ุงู„ุชูŠ ุชู… ุงู„ูƒุดู ุนู†ู‡ุง ุจูˆุงุณุทุฉ ู‡ุฐู‡ ุงู„ุฎุทูˆุฉ ุฃูˆ ุงู„ู…ู‡ู…ุฉ. +documentation.context.result=ู†ุชูŠุฌุฉ +documentation.context.result.description=ู†ุชูŠุฌุฉ ุงู„ูˆุธูŠูุฉ: ุงู„ู†ุฌุงุญ ุฃูˆ ุงู„ูุดู„ ุฃูˆ ุงู„ุฅู„ุบุงุก ุฃูˆ ุงู„ุชุฎุทูŠ. +documentation.context.outcome=ุงู„ู†ุชูŠุฌุฉ +documentation.context.outcome.description=ู†ุชูŠุฌุฉ ุงู„ุฎุทูˆุฉ ู‚ุจู„ ุชุทุจูŠู‚ ู…ุชุงุจุนุฉ ุงู„ุฎุทุฃ. +documentation.context.conclusion=ุงู„ุงุณุชู†ุชุงุฌ +documentation.context.conclusion.description=ู†ุชูŠุฌุฉ ุงู„ุฎุทูˆุฉ ุจุนุฏ ุชุทุจูŠู‚ ุงู„ู…ุชุงุจุนุฉ ุนู„ู‰ ุงู„ุฎุทุฃ. +error.report.action=ุงุณุชุซู†ุงุก ุงู„ุชู‚ุฑูŠุฑ +error.report.description=ุงู„ูˆุตู +error.report.steps=ุฎุทูˆุงุช ุงู„ุชูƒุงุซุฑ +error.report.sample=ูŠุฑุฌู‰ ุชู‚ุฏูŠู… ู†ู…ูˆุฐุฌ ุงู„ูƒูˆุฏ ุฅู† ุฃู…ูƒู† +error.report.message=ุฑุณุงู„ุฉ +error.report.runtime=ู…ุนู„ูˆู…ุงุช ูˆู‚ุช ุงู„ุชุดุบูŠู„ +error.report.pluginVersion=ุฅุตุฏุงุฑ ุงู„ุจุฑู†ุงู…ุฌ ุงู„ู…ุณุงุนุฏ: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=ุณุชุงูƒุชุฑูŠุณ +completion.shell.bash=ู‚ุฐูŠูุฉ Bash. ูŠุณุชุฎุฏู… bash ุนู„ู‰ ู…ุชุณุงุจู‚ูŠ Linux ูˆmacOSุŒ ูˆGit ู„ู€ Windows bash ุนู„ู‰ ู…ุชุณุงุจู‚ูŠ Windows. +completion.shell.sh=POSIX ู‚ุฐูŠูุฉ ุงุญุชูŠุงุทูŠุฉ. +completion.shell.pwsh=PowerShell ุงู„ุฃุณุงุณูŠุฉ. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=ู…ูˆุฌู‡ ุงู„ุฃูˆุงู…ุฑ Windows. +completion.shell.python=ู…ุดุบู„ ุงู„ุฃูˆุงู…ุฑ Python +completion.runner.name=ุงุณู… ุงู„ุนุฏุงุก ุงู„ุฐูŠ ูŠู†ูุฐ ุงู„ู…ู‡ู…ุฉ. +completion.runner.os=ู†ุธุงู… ุงู„ุชุดุบูŠู„ ุงู„ุฎุงุต ุจุงู„ุนุฏุงุก ุงู„ุฐูŠ ูŠู†ูุฐ ุงู„ู…ู‡ู…ุฉ. ุงู„ู‚ูŠู… ุงู„ู…ุญุชู…ู„ุฉ ู‡ูŠ LinuxุŒ ุฃูˆ WindowsุŒ ุฃูˆ macOS. +completion.runner.arch=ุจู†ูŠุฉ ุงู„ุนุฏุงุก ุงู„ุฐูŠ ูŠู†ูุฐ ุงู„ู…ู‡ู…ุฉ. ุงู„ู‚ูŠู… ุงู„ู…ุญุชู…ู„ุฉ ู‡ูŠ X86 ุฃูˆ X64 ุฃูˆ ARM ุฃูˆ ARM64. +completion.runner.temp=ุงู„ู…ุณุงุฑ ุฅู„ู‰ ุงู„ุฏู„ูŠู„ ุงู„ู…ุคู‚ุช ุนู„ู‰ ุงู„ุนุฏุงุก. ูŠุชู… ุฅูุฑุงุบ ู‡ุฐุง ุงู„ุฏู„ูŠู„ ููŠ ุจุฏุงูŠุฉ ูˆู†ู‡ุงูŠุฉ ูƒู„ ู…ู‡ู…ุฉ. ู„ุงุญุธ ุฃู†ู‡ ู„ู† ุชุชู… ุฅุฒุงู„ุฉ ุงู„ู…ู„ูุงุช ุฅุฐุง ู„ู… ูŠูƒู† ู„ุฏู‰ ุญุณุงุจ ุงู„ู…ุณุชุฎุฏู… ุงู„ุฎุงุต ุจุงู„ุนุฏุงุก ุฅุฐู† ุจุญุฐูู‡ุง. +completion.runner.toolCache=ุงู„ู…ุณุงุฑ ุฅู„ู‰ ุงู„ุฏู„ูŠู„ ุงู„ุฐูŠ ูŠุญุชูˆูŠ ุนู„ู‰ ุงู„ุฃุฏูˆุงุช ุงู„ู…ุซุจุชุฉ ู…ุณุจู‚ู‹ุง ู„ู„ุนุฏุงุฆูŠู† ุงู„ู…ุณุชุถุงููŠู† ุนู„ู‰ GitHub. +completion.runner.debug=ูŠุชู… ุชุนูŠูŠู† ู‡ุฐุง ูู‚ุท ููŠ ุญุงู„ุฉ ุชู…ูƒูŠู† ุชุณุฌูŠู„ ุงู„ุชุตุญูŠุญุŒ ูˆุชูƒูˆู† ู‚ูŠู…ุชู‡ ุฏุงุฆู…ู‹ุง 1. +completion.runner.environment=ุจูŠุฆุฉ ุงู„ุนุฏุงุก ุงู„ุฐูŠ ูŠู†ูุฐ ุงู„ู…ู‡ู…ุฉ. ุงู„ู‚ูŠู… ุงู„ู…ุญุชู…ู„ุฉ ู‡ูŠ ู…ุณุชุถุงูุฉ ุนู„ู‰ github ุฃูˆ ู…ุณุชุถุงูุฉ ุฐุงุชูŠู‹ุง. +completion.job.status=ุงู„ูˆุถุน ุงู„ุญุงู„ูŠ ู„ู„ูˆุธูŠูุฉ. +completion.job.checkRunId=ู…ุนุฑู ุชุดุบูŠู„ ุงู„ูุญุต ู„ู„ูˆุธูŠูุฉ ุงู„ุญุงู„ูŠุฉ. +completion.job.container=ู…ุนู„ูˆู…ุงุช ุญูˆู„ ุญุงูˆูŠุฉ ุงู„ูˆุธูŠูุฉ. +completion.job.services=ุญุงูˆูŠุงุช ุงู„ุฎุฏู…ุฉ ุงู„ุชูŠ ุชู… ุฅู†ุดุงุคู‡ุง ู„ู…ู‡ู…ุฉ ู…ุง. +completion.job.workflowRef=ุงู„ู…ุฑุฌุน ุงู„ูƒุงู…ู„ ู„ู…ู„ู ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ุฐูŠ ูŠุญุฏุฏ ุงู„ูˆุธูŠูุฉ ุงู„ุญุงู„ูŠุฉ. +completion.job.workflowSha=ุงู„ุชุฒุงู… SHA ู„ู…ู„ู ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ุฐูŠ ูŠุญุฏุฏ ุงู„ู…ู‡ู…ุฉ ุงู„ุญุงู„ูŠุฉ. +completion.job.workflowRepository=ู…ุงู„ูƒ/ู…ุณุชูˆุฏุน ุงู„ู…ุณุชูˆุฏุน ุงู„ุฐูŠ ูŠุญุชูˆูŠ ุนู„ู‰ ู…ู„ู ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ุฐูŠ ูŠุญุฏุฏ ุงู„ูˆุธูŠูุฉ ุงู„ุญุงู„ูŠุฉ. +completion.job.workflowFilePath=ู…ุณุงุฑ ู…ู„ู ุณูŠุฑ ุงู„ุนู…ู„ุŒ ุจุงู„ู†ุณุจุฉ ุฅู„ู‰ ุฌุฐุฑ ุงู„ู…ุณุชูˆุฏุน. +completion.job.containerField=ู…ุฌุงู„ ุญุงูˆูŠุฉ ุงู„ูˆุธูŠูุฉ +completion.job.service=ุฎุฏู…ุฉ ุงู„ูˆุธูŠูุฉ +completion.job.serviceField=ู…ุฌุงู„ ุงู„ุฎุฏู…ุฉ ุงู„ูˆุธูŠููŠุฉ +completion.job.mappedServicePort=ู…ู†ูุฐ ุงู„ุฎุฏู…ุฉ ุงู„ู…ุนูŠู†ุฉ +completion.strategy.failFast=ู…ุง ุฅุฐุง ูƒุงู† ุณูŠุชู… ุฅู„ุบุงุก ูƒุงูุฉ ุงู„ู…ู‡ุงู… ู‚ูŠุฏ ุงู„ุชู‚ุฏู… ููŠ ุญุงู„ุฉ ูุดู„ ุฃูŠ ู…ู‡ู…ุฉ ู…ุตููˆูุฉ. +completion.strategy.jobIndex=ุงู„ูู‡ุฑุณ ุงู„ุตูุฑูŠ ู„ู„ูˆุธูŠูุฉ ุงู„ุญุงู„ูŠุฉ ููŠ ุงู„ู…ุตููˆูุฉ. +completion.strategy.jobTotal=ุฅุฌู…ุงู„ูŠ ุนุฏุฏ ุงู„ูˆุธุงุฆู ููŠ ุงู„ู…ุตููˆูุฉ. +completion.strategy.maxParallel=ุงู„ุญุฏ ุงู„ุฃู‚ุตู‰ ู„ุนุฏุฏ ูˆุธุงุฆู ุงู„ู…ุตููˆูุฉ ุงู„ุชูŠ ูŠู…ูƒู† ุชุดุบูŠู„ู‡ุง ููŠ ูˆู‚ุช ูˆุงุญุฏ. +completion.context.inputs=ู…ุฏุฎู„ุงุช ุณูŠุฑ ุงู„ุนู…ู„ุŒ ู…ุซู„ workflow_dispatch ุฃูˆ workflow_call. +completion.context.secrets=ุฃุณุฑุงุฑ ุณูŠุฑ ุงู„ุนู…ู„. +completion.context.job=ู…ุนู„ูˆู…ุงุช ุญูˆู„ ุงู„ูˆุธูŠูุฉ ุงู„ุฌุงุฑูŠุฉ ุญุงู„ูŠุง. +completion.context.jobs=ูˆุธุงุฆู ุณูŠุฑ ุงู„ุนู…ู„. +completion.context.matrix=ุฎุตุงุฆุต ุงู„ู…ุตููˆูุฉ ุงู„ู…ุญุฏุฏุฉ ู„ูˆุธูŠูุฉ ุงู„ู…ุตููˆูุฉ ุงู„ุญุงู„ูŠุฉ. +completion.context.strategy=ู…ุนู„ูˆู…ุงุช ุงุณุชุฑุงุชูŠุฌูŠุฉ ุชู†ููŠุฐ ุงู„ู…ุตููˆูุฉ ู„ู„ูˆุธูŠูุฉ ุงู„ุญุงู„ูŠุฉ. +completion.context.steps=ุฎุทูˆุงุช ู…ุน ู…ุนุฑู ููŠ ุงู„ูˆุธูŠูุฉ ุงู„ุญุงู„ูŠุฉ. +completion.context.env=ู…ุชุบูŠุฑุงุช ุงู„ุจูŠุฆุฉ ู…ู† ูˆุธุงุฆู ูˆุฎุทูˆุงุช. +completion.context.vars=ู…ุชุบูŠุฑุงุช ุงู„ุชูƒูˆูŠู† ุงู„ู…ุฎุตุตุฉ ู…ู† ู†ุทุงู‚ุงุช ุงู„ู…ุคุณุณุฉ ูˆุงู„ู…ุณุชูˆุฏุน ูˆุงู„ุจูŠุฆุฉ. +completion.context.needs=ุงู„ู…ู‡ุงู… ุงู„ุชูŠ ูŠุฌุจ ุฅูƒู…ุงู„ู‡ุง ู‚ุจู„ ุฃู† ุชุชู…ูƒู† ู…ู† ุชุดุบูŠู„ ู‡ุฐู‡ ุงู„ูˆุธูŠูุฉุŒ ุจุงู„ุฅุถุงูุฉ ุฅู„ู‰ ู…ุฎุฑุฌุงุชู‡ุง ูˆู†ุชุงุฆุฌู‡ุง. +completion.context.github=ุชุดุบูŠู„ ุณูŠุฑ ุงู„ุนู…ู„ ูˆู…ุนู„ูˆู…ุงุช ุงู„ุญุฏุซ ู…ู† ุณูŠุงู‚ GitHub. +completion.context.gitea=ุงู„ุงุณู… ุงู„ู…ุณุชุนุงุฑ ุงู„ู…ุชูˆุงูู‚ ู…ุน Gitea ู„ุณูŠุงู‚ ุฅุฌุฑุงุกุงุช GitHub. +completion.context.runner=ู…ุนู„ูˆู…ุงุช ุญูˆู„ ุงู„ุนุฏุงุก ุงู„ุฐูŠ ูŠู†ูุฐ ุงู„ูˆุธูŠูุฉ ุงู„ุญุงู„ูŠุฉ. +completion.secret.githubToken=ุงู„ุฑู…ุฒ ุงู„ู…ู…ูŠุฒ ุงู„ุฐูŠ ุชู… ุฅู†ุดุงุคู‡ ุชู„ู‚ุงุฆูŠู‹ุง ู„ูƒู„ ุชุดุบูŠู„ ุณูŠุฑ ุนู…ู„. +completion.remote.repository=ุงู„ู…ุณุชูˆุฏุน ุงู„ุจุนูŠุฏ +completion.uses.local.workflow=ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู…ุญู„ูŠ ุงู„ู‚ุงุจู„ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… +completion.uses.local.action=ุงู„ุนู…ู„ ุงู„ู…ุญู„ูŠ +completion.uses.ref.known=ู…ุฑุฌุน ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู…ุนุฑูˆู +completion.uses.ref.remote=ู…ุฑุฌุน ุณูŠุฑ ุงู„ุนู…ู„ ุนู† ุจุนุฏ +completion.uses.remote.known=ุงู„ุฅุฌุฑุงุก ุนู† ุจุนุฏ ุงู„ู…ุนุฑูˆู ุฃูˆ ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… +completion.workflow.syntax=GitHub ุจู†ุงุก ุฌู…ู„ุฉ ุณูŠุฑ ุงู„ุนู…ู„ +completion.workflow.top.name=ุงุณู… ุนุฑุถ ุณูŠุฑ ุงู„ุนู…ู„ +completion.workflow.top.run-name=ุงุณู… ุงู„ุชุดุบูŠู„ ุงู„ุฏูŠู†ุงู…ูŠูƒูŠ +completion.workflow.top.on=ุงู„ุฃุญุฏุงุซ ุงู„ุชูŠ ุชุจุฏุฃ ุณูŠุฑ ุงู„ุนู…ู„ +completion.workflow.top.permissions=ุฃุฐูˆู†ุงุช GITHUB_TOKEN ุงู„ุงูุชุฑุงุถูŠุฉ +completion.workflow.top.env=ู…ุชุบูŠุฑุงุช ุงู„ุจูŠุฆุฉ ุนู„ู‰ ู…ุณุชูˆู‰ ุณูŠุฑ ุงู„ุนู…ู„ +completion.workflow.top.defaults=ุฅุนุฏุงุฏุงุช ุงู„ู…ู‡ู…ุฉ ูˆุงู„ุฎุทูˆุฉ ุงู„ุงูุชุฑุงุถูŠุฉ +completion.workflow.top.concurrency=ู…ุฌู…ูˆุนุฉ ุงู„ุชุฒุงู…ู† ูˆุงู„ุฅู„ุบุงุก +completion.workflow.top.jobs=ุงู„ูˆุธุงุฆู ุงู„ุชูŠ ูŠุชู… ุชุดุบูŠู„ู‡ุง ููŠ ุณูŠุฑ ุงู„ุนู…ู„ ู‡ุฐุง +completion.workflow.event.branch_protection_rule=ุชู… ุชุบูŠูŠุฑ ุญู…ุงูŠุฉ ุงู„ูุฑุน +completion.workflow.event.check_run=ุชู… ุชุบูŠูŠุฑ ุชุดุบูŠู„ ุงู„ูุญุต ุงู„ูุฑุฏูŠ +completion.workflow.event.check_suite=ุชู… ุชุบูŠูŠุฑ ุฌู†ุงุญ ุงู„ุชุญู‚ู‚ +completion.workflow.event.create=ุชู… ุฅู†ุดุงุก ุงู„ูุฑุน ุฃูˆ ุงู„ุนู„ุงู…ุฉ +completion.workflow.event.delete=ุชู… ุญุฐู ุงู„ูุฑุน ุฃูˆ ุงู„ุนู„ุงู…ุฉ +completion.workflow.event.deployment=ุชู… ุฅู†ุดุงุก ุงู„ู†ุดุฑ +completion.workflow.event.deployment_status=ุชุบูŠุฑุช ุญุงู„ุฉ ุงู„ู†ุดุฑ +completion.workflow.event.discussion=ุชุบูŠุฑุช ุงู„ู…ู†ุงู‚ุดุฉ +completion.workflow.event.discussion_comment=ุชู… ุชุบูŠูŠุฑ ุชุนู„ูŠู‚ ุงู„ู…ู†ุงู‚ุดุฉ +completion.workflow.event.fork=ู…ุณุชูˆุฏุน ู…ุชุดุนุจ +completion.workflow.event.gollum=ุชู… ุชุบูŠูŠุฑ ุตูุญุฉ ุงู„ูˆูŠูƒูŠ +completion.workflow.event.image_version=ุชู… ุชุบูŠูŠุฑ ุฅุตุฏุงุฑ ุตูˆุฑุฉ ุงู„ุญุฒู…ุฉ +completion.workflow.event.issue_comment=ุชู… ุชุบูŠูŠุฑ ุงู„ู…ุดูƒู„ุฉ ุฃูˆ ุชุนู„ูŠู‚ PR +completion.workflow.event.issues=ุชู… ุชุบูŠูŠุฑ ุงู„ู…ุดูƒู„ุฉ +completion.workflow.event.label=ุชู… ุชุบูŠูŠุฑ ุงู„ุชุณู…ูŠุฉ +completion.workflow.event.merge_group=ุชู… ุทู„ุจ ุงู„ุชุญู‚ู‚ ู…ู† ู‚ุงุฆู…ุฉ ุงู„ุงู†ุชุธุงุฑ +completion.workflow.event.milestone=ุชู… ุชุบูŠูŠุฑ ุงู„ู…ุนู„ู… +completion.workflow.event.page_build=ุชู… ุชุดุบูŠู„ ุฅู†ุดุงุก ุงู„ุตูุญุงุช +completion.workflow.event.project=ุชุบูŠุฑ ุงู„ู…ุดุฑูˆุน ุงู„ูƒู„ุงุณูŠูƒูŠ +completion.workflow.event.project_card=ุชู… ุชุบูŠูŠุฑ ุจุทุงู‚ุฉ ุงู„ู…ุดุฑูˆุน ุงู„ูƒู„ุงุณูŠูƒูŠุฉ +completion.workflow.event.project_column=ุชู… ุชุบูŠูŠุฑ ุนู…ูˆุฏ ุงู„ู…ุดุฑูˆุน ุงู„ูƒู„ุงุณูŠูƒูŠ +completion.workflow.event.public=ุฃุตุจุญ ุงู„ู…ุณุชูˆุฏุน ุนุงู…ู‹ุง +completion.workflow.event.pull_request=ุชู… ุชุบูŠูŠุฑ ุทู„ุจ ุงู„ุณุญุจ +completion.workflow.event.pull_request_review=ุชู… ุชุบูŠูŠุฑ ู…ุฑุงุฌุนุฉ PR +completion.workflow.event.pull_request_review_comment=ุชู… ุชุบูŠูŠุฑ ุชุนู„ูŠู‚ ู…ุฑุงุฌุนุฉ PR +completion.workflow.event.pull_request_target=ุณูŠุงู‚ ุงู„ู‡ุฏู PR. ุณูƒุงูƒูŠู† ุญุงุฏุฉ. +completion.workflow.event.push=ุงู„ุงู„ุชุฒุงู… ุฃูˆ ุฏูุน ุงู„ุนู„ุงู…ุฉ +completion.workflow.event.registry_package=ุชู… ู†ุดุฑ ุงู„ุญุฒู…ุฉ ุฃูˆ ุชุญุฏูŠุซู‡ุง +completion.workflow.event.release=ุชู… ุชุบูŠูŠุฑ ุงู„ุฅุตุฏุงุฑ +completion.workflow.event.repository_dispatch=ุญุฏุซ ูˆุงุฌู‡ุฉ ุจุฑู…ุฌุฉ ุงู„ุชุทุจูŠู‚ุงุช ุงู„ู…ุฎุตุตุฉ +completion.workflow.event.schedule=ุนู„ุงู…ุฉ ูƒุฑูˆู†. ุชุตูˆุฑู‡ุง. +completion.workflow.event.status=ุชู… ุชุบูŠูŠุฑ ุญุงู„ุฉ ุงู„ุงู„ุชุฒุงู… +completion.workflow.event.watch=ุชู… ุชู…ูŠูŠุฒ ุงู„ู…ุณุชูˆุฏุน ุจู†ุฌู…ุฉ +completion.workflow.event.workflow_call=ุงุณุชุฏุนุงุก ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… +completion.workflow.event.workflow_dispatch=ุฒุฑ ุงู„ุชุดุบูŠู„ ุงู„ูŠุฏูˆูŠ +completion.workflow.event.workflow_run=ุชู… ุชุบูŠูŠุฑ ุชุดุบูŠู„ ุณูŠุฑ ุงู„ุนู…ู„ +completion.workflow.eventFilter.types=ุงู„ุญุฏ ู…ู† ุฃู†ูˆุงุน ุงู„ู†ุดุงุท +completion.workflow.eventFilter.branches=ู‡ุฐู‡ ุงู„ูุฑูˆุน ูู‚ุท +completion.workflow.eventFilter.branches-ignore=ุชุฎุทูŠ ู‡ุฐู‡ ุงู„ูุฑูˆุน +completion.workflow.eventFilter.tags=ู‡ุฐู‡ ุงู„ุนู„ุงู…ุงุช ูู‚ุท +completion.workflow.eventFilter.tags-ignore=ุชุฎุทูŠ ู‡ุฐู‡ ุงู„ุนู„ุงู…ุงุช +completion.workflow.eventFilter.paths=ู‡ุฐู‡ ุงู„ู…ุณุงุฑุงุช ูู‚ุท +completion.workflow.eventFilter.paths-ignore=ุชุฎุทูŠ ู‡ุฐู‡ ุงู„ู…ุณุงุฑุงุช +completion.workflow.eventFilter.workflows=ุฃุณู…ุงุก ุณูŠุฑ ุงู„ุนู…ู„ ู„ู„ู…ุดุงู‡ุฏุฉ +completion.workflow.eventFilter.cron=ุฌุฏูˆู„ ูƒุฑูˆู†. ุงู„ุณุงุนุฉ ุงู„ุตุบูŠุฑุฉ. +completion.workflow.permission.actions=ูŠุนู…ู„ ุณูŠุฑ ุงู„ุนู…ู„ ูˆุงู„ุชุญู ุงู„ุนู…ู„ +completion.workflow.permission.artifact-metadata=ุณุฌู„ุงุช ุงู„ุจูŠุงู†ุงุช ุงู„ูˆุตููŠุฉ ู„ู„ู‚ุทุนุฉ ุงู„ุฃุซุฑูŠุฉ +completion.workflow.permission.attestations=ุดู‡ุงุฏุงุช ู‚ุทุนุฉ ุฃุซุฑูŠุฉ +completion.workflow.permission.checks=ุชุญู‚ู‚ ู…ู† ุงู„ุชุดุบูŠู„ ูˆุงู„ุฃุฌู†ุญุฉ +completion.workflow.permission.code-quality=ุชู‚ุงุฑูŠุฑ ุฌูˆุฏุฉ ุงู„ูƒูˆุฏ +completion.workflow.permission.contents=ู…ุญุชูˆูŠุงุช ุงู„ู…ุณุชูˆุฏุน +completion.workflow.permission.deployments=ุนู…ู„ูŠุงุช ุงู„ู†ุดุฑ +completion.workflow.permission.discussions=ุงู„ู…ู†ุงู‚ุดุงุช +completion.workflow.permission.id-token=ุงู„ุฑู…ูˆุฒ ุงู„ู…ู…ูŠุฒุฉ OpenID Connect +completion.workflow.permission.issues=ู…ุดุงูƒู„ +completion.workflow.permission.models=ู†ู…ุงุฐุฌ GitHub +completion.workflow.permission.packages=ุญุฒู… GitHub +completion.workflow.permission.pages=ุตูุญุงุช GitHub +completion.workflow.permission.pull-requests=ุณุญุจ ุงู„ุทู„ุจุงุช +completion.workflow.permission.security-events=ู…ุณุญ ุงู„ูƒูˆุฏ ูˆุงู„ุฃุญุฏุงุซ ุงู„ุฃู…ู†ูŠุฉ +completion.workflow.permission.statuses=ุญุงู„ุงุช ุงู„ุงู„ุชุฒุงู… +completion.workflow.permission.vulnerability-alerts=ุชู†ุจูŠู‡ุงุช Dependabot +completion.workflow.permission.value.read=ุงู„ูˆุตูˆู„ ู„ู„ู‚ุฑุงุกุฉ +completion.workflow.permission.value.write=ุฅู…ูƒุงู†ูŠุฉ ุงู„ูˆุตูˆู„ ู„ู„ูƒุชุงุจุฉุŒ ูˆุงู„ู‚ุฑุงุกุฉ ู…ุชุถู…ู†ุฉ +completion.workflow.permission.value.none=ู„ุง ูŠู…ูƒู† ุงู„ูˆุตูˆู„ +completion.workflow.permission.shorthand.read-all=ู‚ุฑุงุกุฉ ูƒุงูุฉ ุงู„ุฃุฐูˆู†ุงุช. ุจุทุงู†ูŠุฉ ูƒุจูŠุฑุฉ. +completion.workflow.permission.shorthand.write-all=ุฌู…ูŠุน ุงู„ุฃุฐูˆู†ุงุช ุงู„ูƒุชุงุจุฉ. ู…ุทุฑู‚ุฉ ูƒุจูŠุฑุฉ. +completion.workflow.permission.shorthand.empty=ุชุนุทูŠู„ ุฃุฐูˆู†ุงุช ุงู„ุฑู…ุฒ ุงู„ู…ู…ูŠุฒ +completion.workflow.job.name=ุงุณู… ุนุฑุถ ุงู„ูˆุธูŠูุฉ +completion.workflow.job.permissions=ุฃุฐูˆู†ุงุช ุงู„ุฑู…ุฒ ุงู„ู…ู…ูŠุฒ ู„ู„ูˆุธูŠูุฉ +completion.workflow.job.needs=ูˆุธุงุฆู ู„ู„ุงู†ุชุธุงุฑ +completion.workflow.job.if=ุญุงู„ุฉ ุงู„ูˆุธูŠูุฉ +completion.workflow.job.runs-on=ุชุณู…ูŠุฉ ุงู„ุนุฏุงุก ุฃูˆ ุงู„ู…ุฌู…ูˆุนุฉ +completion.workflow.job.snapshot=ู„ู‚ุทุฉ ุนุฏุงุก +completion.workflow.job.environment=ุจูŠุฆุฉ ุงู„ู†ุดุฑ +completion.workflow.job.concurrency=ู‚ูู„ ุงู„ุชุฒุงู…ู† ุงู„ูˆุธูŠููŠ +completion.workflow.job.outputs=ู…ุฎุฑุฌุงุช ูŠู…ูƒู† ู„ู„ูˆุธุงุฆู ุงู„ุฃุฎุฑู‰ ู‚ุฑุงุกุชู‡ุง +completion.workflow.job.env=ู…ุชุบูŠุฑุงุช ุจูŠุฆุฉ ุงู„ุนู…ู„ +completion.workflow.job.defaults=ุงู„ุฅุนุฏุงุฏุงุช ุงู„ุงูุชุฑุงุถูŠุฉ ู„ู„ู…ู‡ู…ุฉ +completion.workflow.job.steps=ู‚ุงุฆู…ุฉ ุงู„ุฎุทูˆุฉ. ุงู„ุนู…ู„ ุงู„ูุนู„ูŠ. +completion.workflow.job.timeout-minutes=ู…ู‡ู„ุฉ ุงู„ู…ู‡ู…ุฉ ููŠ ุฏู‚ุงุฆู‚ +completion.workflow.job.strategy=ุงู„ู…ุตููˆูุฉ ูˆุงุณุชุฑุงุชูŠุฌูŠุฉ ุงู„ุฌุฏูˆู„ุฉ +completion.workflow.job.continue-on-error=ุฏุน ู‡ุฐู‡ ุงู„ู…ู‡ู…ุฉ ุชูุดู„ ุจู‡ุฏูˆุก +completion.workflow.job.container=ุญุงูˆูŠุฉ ู„ู‡ุฐู‡ ุงู„ู…ู‡ู…ุฉ +completion.workflow.job.services=ุญุงูˆูŠุงุช ุฎุฏู…ุฉ Sidecar +completion.workflow.job.uses=ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู‚ุงุจู„ ู„ุฅุนุงุฏุฉ ุงู„ุงุณุชุฎุฏุงู… ู„ู„ุงุชุตุงู„ +completion.workflow.job.with=ู…ุฏุฎู„ุงุช ู„ุณูŠุฑ ุงู„ุนู…ู„ ูŠุณู…ู‰ +completion.workflow.job.secrets=ุฃุณุฑุงุฑ ุชุณู…ู‰ ุณูŠุฑ ุงู„ุนู…ู„ +completion.workflow.defaultsRun.shell=ุงู„ุบู„ุงู ุงู„ุงูุชุฑุงุถูŠ ู„ุฎุทูˆุงุช ุงู„ุชุดุบูŠู„ +completion.workflow.defaultsRun.working-directory=ุฏู„ูŠู„ ุงู„ุนู…ู„ ุงู„ุงูุชุฑุงุถูŠ +completion.workflow.concurrency.group=ุงุณู… ุงู„ู‚ูู„ ู„ุนู…ู„ูŠุงุช ุงู„ุชุดุบูŠู„ ููŠ ู‚ุงุฆู…ุฉ ุงู„ุงู†ุชุธุงุฑ +completion.workflow.concurrency.cancel-in-progress=ุฅู„ุบุงุก ุนู…ู„ูŠุงุช ุงู„ุชุดุบูŠู„ ุงู„ู…ุทุงุจู‚ุฉ ุงู„ู‚ุฏูŠู…ุฉ +completion.workflow.environment.name=ุงุณู… ุงู„ุจูŠุฆุฉ +completion.workflow.environment.url=ุงู„ุจูŠุฆุฉ URL +completion.workflow.strategy.matrix=ู…ุญุงูˆุฑ ุงู„ู…ุตููˆูุฉ ูˆุงู„ู…ุชุบูŠุฑุงุช +completion.workflow.strategy.fail-fast=ุฅู„ุบุงุก ู…ุตููˆูุฉ ุงู„ุฃุดู‚ุงุก ุนู†ุฏ ุงู„ูุดู„ +completion.workflow.strategy.max-parallel=ุบุทุงุก ู…ุตููˆูุฉ ุงู„ุชูˆุงุฒูŠ +completion.workflow.matrix.include=ุฅุถุงูุฉ ู…ุฌู…ูˆุนุงุช ุงู„ู…ุตููˆูุฉ +completion.workflow.matrix.exclude=ุฅุฒุงู„ุฉ ู…ุฌู…ูˆุนุงุช ุงู„ู…ุตููˆูุฉ +completion.workflow.step.id=ู…ุนุฑู ุงู„ุฎุทูˆุฉ ู„ู„ู…ุฑุงุฌุน +completion.workflow.step.if=ุญุงู„ุฉ ุงู„ุฎุทูˆุฉ +completion.workflow.step.name=ุงุณู… ุนุฑุถ ุงู„ุฎุทูˆุฉ +completion.workflow.step.uses=ุงู„ุนู…ู„ ู„ู„ุชุดุบูŠู„ +completion.workflow.step.run=ุงู„ุจุฑู†ุงู…ุฌ ุงู„ู†ุตูŠ ุดู„ ู„ู„ุชุดุบูŠู„ +completion.workflow.step.shell=ุดู„ ู„ู‡ุฐู‡ ุงู„ุฎุทูˆุฉ ุงู„ุชุดุบูŠู„ +completion.workflow.step.with=ู…ุฏุฎู„ุงุช ุงู„ุนู…ู„ +completion.workflow.step.env=ู…ุชุบูŠุฑุงุช ุงู„ุจูŠุฆุฉ ุงู„ุฎุทูˆุฉ +completion.workflow.step.continue-on-error=ุฏุน ู‡ุฐู‡ ุงู„ุฎุทูˆุฉ ุชูุดู„ ุจู‡ุฏูˆุก +completion.workflow.step.timeout-minutes=ู…ู‡ู„ุฉ ุงู„ุฎุทูˆุฉ ููŠ ุฏู‚ุงุฆู‚ +completion.workflow.step.working-directory=ุฏู„ูŠู„ ุงู„ุนู…ู„ ุฎุทูˆุฉ +completion.workflow.container.image=ุตูˆุฑุฉ ุงู„ุญุงูˆูŠุฉ +completion.workflow.container.credentials=ุจูŠุงู†ุงุช ุงุนุชู…ุงุฏ ุงู„ุชุณุฌูŠู„ +completion.workflow.container.env=ู…ุชุบูŠุฑุงุช ุจูŠุฆุฉ ุงู„ุญุงูˆูŠุฉ +completion.workflow.container.ports=ู…ู†ุงูุฐ ู„ูุถุญ +completion.workflow.container.volumes=ู…ุฌู„ุฏุงุช ู„ู„ุชุฑูƒูŠุจ +completion.workflow.container.options=Docker ุฅู†ุดุงุก ุงู„ุฎูŠุงุฑุงุช +completion.workflow.service.image=ุตูˆุฑุฉ ุญุงูˆูŠุฉ ุงู„ุฎุฏู…ุฉ +completion.workflow.service.credentials=ุจูŠุงู†ุงุช ุงุนุชู…ุงุฏ ุงู„ุชุณุฌูŠู„ +completion.workflow.service.env=ู…ุชุบูŠุฑุงุช ุจูŠุฆุฉ ุงู„ุฎุฏู…ุฉ +completion.workflow.service.ports=ู…ู†ุงูุฐ ุงู„ุฎุฏู…ุฉ +completion.workflow.service.volumes=ุฃุญุฌุงู… ุงู„ุฎุฏู…ุฉ +completion.workflow.service.options=Docker ุฅู†ุดุงุก ุงู„ุฎูŠุงุฑุงุช +completion.workflow.credentials.username=ุงุณู… ู…ุณุชุฎุฏู… ุงู„ุชุณุฌูŠู„ +completion.workflow.credentials.password=ูƒู„ู…ุฉ ู…ุฑูˆุฑ ุงู„ุชุณุฌูŠู„ ุฃูˆ ุงู„ุฑู…ุฒ ุงู„ู…ู…ูŠุฒ +completion.workflow.inputType.string=ุฅุฏุฎุงู„ ุงู„ู†ุต +completion.workflow.inputType.boolean=ุฅุฏุฎุงู„ ุตุญูŠุญ ุฃูˆ ุฎุงุทุฆ +completion.workflow.inputType.choice=ุฅุฏุฎุงู„ ุงุฎุชูŠุงุฑ ุงู„ู‚ุงุฆู…ุฉ ุงู„ู…ู†ุณุฏู„ุฉ +completion.workflow.inputType.number=ุฅุฏุฎุงู„ ุงู„ุฑู‚ู… +completion.workflow.inputType.environment=ุฅุฏุฎุงู„ ู…ู†ุชู‚ูŠ ุงู„ุจูŠุฆุฉ +completion.workflow.boolean.true=ู†ุนู…. ู‚ู… ุจุชุดุบูŠู„ู‡. +completion.workflow.boolean.false=ู„ุงุŒ ุฃุจู‚ู‡ุง ู…ุธู„ู…ุฉ. +completion.workflow.runner.ubuntu-latest=ุฃุญุฏุซ ุนุฏุงุก Ubuntu +completion.workflow.runner.ubuntu-24.04=ุนุฏุงุก Ubuntu 24.04 +completion.workflow.runner.ubuntu-22.04=ุนุฏุงุก Ubuntu 22.04 +completion.workflow.runner.windows-latest=ุฃุญุฏุซ ุนุฏุงุก Windows +completion.workflow.runner.windows-2025=ู…ุดุบู„ Windows Server 2025 +completion.workflow.runner.windows-2022=ู…ุดุบู„ Windows Server 2022 +completion.workflow.runner.macos-latest=ุฃุญุฏุซ ุนุฏุงุก macOS +completion.workflow.runner.macos-15=macOS 15 ุนุฏุงุก +completion.workflow.runner.macos-14=macOS 14 ุนุฏุงุก +completion.workflow.runner.self-hosted=ุนุฏุงุก ุงู„ุฎุงุต ุจูƒ. ุงู„ุณูŠุฑูƒ ุงู„ุฎุงุต ุจูƒ. +completion.steps.outputs=ู…ุฌู…ูˆุนุฉ ุงู„ู…ุฎุฑุฌุงุช ุงู„ู…ุญุฏุฏุฉ ู„ู„ุฎุทูˆุฉ. +completion.steps.conclusion=ูŠุชู… ุชุทุจูŠู‚ ู†ุชูŠุฌุฉ ุงู„ุฎุทูˆุฉ ุงู„ู…ูƒุชู…ู„ุฉ ุจุนุฏ ู…ุชุงุจุนุฉ ุงู„ุฎุทุฃ. +completion.steps.outcome=ูŠุชู… ุชุทุจูŠู‚ ู†ุชูŠุฌุฉ ุงู„ุฎุทูˆุฉ ุงู„ู…ูƒุชู…ู„ุฉ ู‚ุจู„ ู…ุชุงุจุนุฉ ุงู„ุฎุทุฃ. +completion.jobs.outputs=ู…ุฌู…ูˆุนุฉ ุงู„ู…ุฎุฑุฌุงุช ุงู„ู…ุญุฏุฏุฉ ู„ู„ูˆุธูŠูุฉ. +completion.jobs.result=ู†ุชูŠุฌุฉ ุงู„ูˆุธูŠูุฉ. +settings.displayName=ุณูŠุฑ ุงู„ุนู…ู„ GitHub +settings.language.label=ู„ุบุฉ: +settings.language.system=IDE/ุงู„ู†ุธุงู… ุงู„ุงูุชุฑุงุถูŠ +settings.cache.title=ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ู„ู„ุนู…ู„ +settings.cache.column.key=ู…ูุชุงุญ ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช +settings.cache.column.name=ุงุณู… +settings.cache.column.kind=ุนุทูˆู +settings.cache.column.state=ูˆู„ุงูŠุฉ +settings.cache.column.expires=ุชู†ุชู‡ูŠ +settings.cache.kind.local=ู…ุญู„ูŠ +settings.cache.kind.remote=ุจุนูŠุฏ +settings.cache.state.resolved=ุญู„ู‡ุง +settings.cache.state.pending=ู‚ูŠุฏ ุงู„ุงู†ุชุธุงุฑ +settings.cache.state.expired=ู‚ุฏูŠู…ุฉ +settings.cache.state.suppressed=ู‚ู…ุนุช +settings.cache.refresh=ุฌุฏูˆู„ Refresh +settings.cache.deleteSelected=ุญุฐู ุงู„ู…ุญุฏุฏ +settings.cache.deleteAll=ุงุญุฐู ุงู„ูƒู„ +settings.cache.export=ูŠุตุฏู‘ุฑ +settings.cache.import=ูŠุณุชูˆุฑุฏ +settings.cache.summary=ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช: ุฅุฏุฎุงู„ุงุช {0}ุŒ ุชู… ุญู„ {1}ุŒ {2} ุนู† ุจุนุฏุŒ {3} ู‚ุฏูŠู…ุŒ {4} ูƒุชู… ุงู„ุตูˆุช. ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช: {5} KB. +settings.cache.noneSelected=ุญุฏุฏ ุตููˆู ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ุฃูˆู„ุงู‹. ุงู„ู…ูƒู†ุณุฉ ุชุฑูุถ ุงู„ุชุฎู…ูŠู†. +settings.cache.deleteSelected.done=ุชู… ุญุฐู ุฅุฏุฎุงู„ุงุช ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช {0}. ุณุญุงุจุฉ ุบุจุงุฑ ุตุบูŠุฑุฉ ุชุญุชูˆูŠ ุนู„ู‰. +settings.cache.deleteAll.confirm=ู‡ู„ ุชุฑูŠุฏ ุญุฐู ุฌู…ูŠุน ุฅุฏุฎุงู„ุงุช ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ู„ุณูŠุฑ ุงู„ุนู…ู„ GitHubุŸ +settings.cache.deleteAll.done=ุชู… ุญุฐู ูƒุงูุฉ ุฅุฏุฎุงู„ุงุช ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช. ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ุงู„ุขู† ู‡ุงุฏุฆุฉ ุจุดูƒู„ ู…ุซูŠุฑ ู„ู„ุฑูŠุจุฉ. +settings.cache.export.done=ุชู… ุชุตุฏูŠุฑ ุฅุฏุฎุงู„ุงุช ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช {0}. ุฃุฑุดูŠู ุตุบูŠุฑ ู„ู„ูˆุญุด ููŠ ู‚ูุต. +settings.cache.import.done=ุฅุฏุฎุงู„ุงุช ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ุงู„ู…ุณุชูˆุฑุฏุฉ. ุชุตุฑู ูˆุญุด ุงู„ุฃุฑุดูŠู. +settings.cache.import.unsupported=ู…ู„ู ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ู„ุณูŠุฑ ุงู„ุนู…ู„ GitHub ุบูŠุฑ ู…ุฏุนูˆู…. +settings.cache.import.brokenLine=ุฎุท ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ู„ุณูŠุฑ ุงู„ุนู…ู„ GitHub ู…ุนุทู„. +settings.cache.import.brokenKey=ู…ูุชุงุญ ุฐุงูƒุฑุฉ ุงู„ุชุฎุฒูŠู† ุงู„ู…ุคู‚ุช ู„ุณูŠุฑ ุงู„ุนู…ู„ GitHub ู…ุนุทู„. +settings.support.button=ุฏุนู… ู‡ุฐุง ุงู„ุจุฑู†ุงู…ุฌ ุงู„ู…ุณุงุนุฏ +settings.support.tooltip=ุงูุชุญ ุตูุญุฉ ุงู„ุฏุนู… +settings.support.line.0=ุชุบุฐูŠุฉ ูุฑู† ุงู„ุจู†ุงุก +settings.support.line.1=ุดุฑุงุก ุงู„ู‚ู‡ูˆุฉ ุงู„ู…ุญู„ู„ +settings.support.line.2=ุฑุนุงูŠุฉ ุนุฏุฏ ุฃู‚ู„ ู…ู† ุณูŠุฑ ุงู„ุนู…ู„ ุงู„ู…ุณูƒูˆู† +workflow.run.jobs.title=ูˆุธุงุฆู ุณูŠุฑ ุงู„ุนู…ู„ +workflow.run.jobs.root=ุชุดุบูŠู„ ุณูŠุฑ ุงู„ุนู…ู„ +workflow.run.jobs.description=ุดุฌุฑุฉ ูˆุธุงุฆู ุฅุฌุฑุงุกุงุช GitHub ูˆุณุฌู„ ุงู„ูˆุธุงุฆู ุงู„ู…ุญุฏุฏ +workflow.run.tree.done=ู…ู†ุชู‡ูŠ +workflow.run.tree.failed=ูุดู„ +workflow.run.tree.skipped=ุชู… ุชุฎุทูŠู‡ +workflow.run.tree.warn=ุชุญุฐูŠุฑ +workflow.run.tree.err=ูŠุฎุทุฆ +workflow.run.delete.tooltip=ุญุฐู ุงู„ุชุดุบูŠู„ +workflow.run.delete.noRun=ู„ุง ูŠูˆุฌุฏ ู…ุนุฑู ุชุดุบูŠู„ ุญุชู‰ ุงู„ุขู†. +workflow.run.delete.requested=ุญุฐู ุชุดุบูŠู„ {0}. +workflow.run.delete.done=ุชู… ุญุฐู ุชุดุบูŠู„ {0}. +workflow.run.delete.http=ุญุฐู HTTP {0}. ุนููˆุง. +workflow.run.delete.failed=ุญุฐู ู…ุชุนุซุฑ: {0} +workflow.run.rerun.all.tooltip=ุฃุนุฏ ุชุดุบูŠู„ ุณูŠุฑ ุงู„ุนู…ู„ +workflow.run.rerun.failed.tooltip=ุฅุนุงุฏุฉ ุชุดุบูŠู„ ุงู„ู…ู‡ุงู… ุงู„ูุงุดู„ุฉ +workflow.run.rerun.noRun=ู„ุง ูŠูˆุฌุฏ ู…ุนุฑู ุชุดุบูŠู„ ุญุชู‰ ุงู„ุขู†. +workflow.run.rerun.all.requested=ุทู„ุจ ุฅุนุงุฏุฉ ุงู„ุชุดุบูŠู„: {0}. +workflow.run.rerun.failed.requested=ุงู„ู…ู‡ุงู… ุงู„ูุงุดู„ุฉ ุงู„ู…ุทู„ูˆุจุฉ: {0}. +workflow.run.rerun.all.done=ุฅุนุงุฏุฉ ุงู„ุชุดุบูŠู„ ููŠ ู‚ุงุฆู…ุฉ ุงู„ุงู†ุชุธุงุฑ: {0}. +workflow.run.rerun.failed.done=ุงู„ู…ู‡ุงู… ุงู„ูุงุดู„ุฉ ุงู„ู…ูˆุฌูˆุฏุฉ ููŠ ู‚ุงุฆู…ุฉ ุงู„ุงู†ุชุธุงุฑ: {0}. +workflow.run.rerun.http=ุฃุนุฏ ุชุดุบูŠู„ HTTP {0}. ุนููˆุง. +workflow.run.rerun.failed=ูุดู„ุช ุฅุนุงุฏุฉ ุงู„ุชุดุบูŠู„: {0} +workflow.run.download.log.tooltip=ุญูุธ ุณุฌู„ ุงู„ูˆุธุงุฆู +workflow.run.download.artifacts.tooltip=ุชุญู…ูŠู„ ุงู„ุชุญู +workflow.run.download.noRun=ู„ุง ูŠูˆุฌุฏ ู…ุนุฑู ุชุดุบูŠู„ ุญุชู‰ ุงู„ุขู†. +workflow.run.download.log.requested=ุฌู„ุจ ุงู„ุณุฌู„ ู„ู€ {0}. +workflow.run.download.log.done=ุงู„ุณุฌู„ ุงู„ู…ุญููˆุธ: {0}. +workflow.run.download.artifacts.requested=ุฌู„ุจ ุงู„ุชุญู. +workflow.run.download.artifacts.empty=ู„ุง ุงู„ุชุญู. ูุฑุงุบ ุตุบูŠุฑ. +workflow.run.download.artifact.expired=ุงู†ุชู‡ุช ุตู„ุงุญูŠุฉ ุงู„ู‚ุทุนุฉ ุงู„ุฃุซุฑูŠุฉ: {0}. +workflow.run.download.artifact.done=ุงู„ู‚ุทุนุฉ ุงู„ุฃุซุฑูŠุฉ ุงู„ู…ุญููˆุธุฉ: {0} -> {1}. +workflow.run.download.failed=ุชุนุซุฑ ุงู„ุชู†ุฒูŠู„: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_cs.properties b/src/main/resources/messages/GitHubWorkflowBundle_cs.properties new file mode 100644 index 0000000..4e571f2 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_cs.properties @@ -0,0 +1,442 @@ +plugin.name=Pracovnรญ postup GitHub +plugin.description=Podpora souborลฏ pracovnรญho postupu akcรญ GitHub +group.GitHubWorkflow.Tools.text=Pracovnรญ postup GitHub +group.GitHubWorkflow.Tools.description=Nรกstroje pluginu GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Obnovit cache akcรญ +action.GitHubWorkflow.RefreshActionCache.description=Refresh vyล™eลกenรฉ vzdรกlenรฉ akce GitHub a znovu pouลพitelnรก metadata pracovnรญho postupu +action.GitHubWorkflow.RestoreActionWarnings.text=Obnovit varovรกnรญ akce +action.GitHubWorkflow.RestoreActionWarnings.description=Obnovte potlaฤenรก upozornฤ›nรญ na ovฤ›ล™enรญ platnosti vstupu a vรฝstupu +action.GitHubWorkflow.ClearActionCache.text=Vymaลพte mezipamฤ›ลฅ akcรญ +action.GitHubWorkflow.ClearActionCache.description=Vymaลพte akce GitHub uloลพenรฉ v mezipamฤ›ti a znovu pouลพitelnรก metadata pracovnรญho postupu +notification.cache.cleared=Vymazรกny poloลพky GitHub Workflow {0} uloลพenรฉ v mezipamฤ›ti. +notification.cache.refresh.started=Refreshing {0} uloลพil do mezipamฤ›ti vzdรกlenรฉ poloลพky pracovnรญho postupu GitHub. +notification.warnings.restored=Obnovena varovรกnรญ pro poloลพky pracovnรญho postupu {0} GitHub. +workflow.run.configuration.display=Pracovnรญ postup GitHub +workflow.run.configuration.description=Odeลกlete a sledujte bฤ›h pracovnรญho postupu akcรญ GitHub +workflow.run.configuration.name=Pracovnรญ postup GitHub: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=vlastnรญk +workflow.run.field.repo=รšloลพiลกtฤ› +workflow.run.field.workflow=Soubor pracovnรญho postupu +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Token env var zรกloลพnรญ +workflow.run.inputs.title=Vstupy workflow_dispatch (klรญฤ=hodnota) +workflow.run.error.apiUrl=GitHub API URL je vyลพadovรกno. +workflow.run.error.repository=Je vyลพadovรกno jmรฉno a vlastnรญk รบloลพiลกtฤ› GitHub. +workflow.run.error.workflow=Je vyลพadovรกn soubor pracovnรญho postupu. +workflow.run.error.ref=Je vyลพadovรกno oznaฤenรญ vฤ›tve nebo znaฤky. +workflow.run.error.inputs=GitHub workflow_dispatch podporuje maximรกlnฤ› 25 vstupลฏ. +workflow.run.gutter.stop=Zastavit bฤ›h pracovnรญho postupu +workflow.run.gutter.stop.text=Zastavit bฤ›h pracovnรญho postupu +workflow.run.gutter.stop.description=Zruลกte tento bฤ›h +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=spustit: +workflow.log.warning=varovรกnรญ: +workflow.log.error=chyba: +workflow.run.cancel.requested=Poลพadovanรฉ zruลกenรญ: {0}. +workflow.run.stop.before.id=Vyลพรกdรกno zastavenรญ. Zatรญm ลพรกdnรฉ ID spuลกtฤ›nรญ. +workflow.run.cancel.http=Zruลกit HTTP {0}. Uf. +workflow.run.cancel.failed=Zruลกit fizzled: {0} +workflow.run.interrupted=Pล™eruลกeno. +workflow.run.link=Spustit: {0} +workflow.run.discovery=Bฤ›h pล™ijat. ID loveckรฉho bฤ›hu. +workflow.run.discovery.none=Zatรญm ลพรกdnรฉ ID spuลกtฤ›nรญ. Karta Akce vรญ vรญce. +workflow.run.status=Stav: {0}{1} +workflow.run.job.main=Zamฤ›stnรกnรญ: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Stav: {0} {1}{2}{3} +workflow.run.logs.later=Protokoly se objevรญ, kdyลพ je GitHub publikuje. +workflow.run.job.logs.later=รšloha {0}: {1} +workflow.run.log.failed=Staลพenรญ protokolu se nezdaล™ilo: {0} +workflow.run.log.failed.job=Staลพenรญ protokolu pro {0}: {1} se nezdaล™ilo +workflow.run.job.url=URL: {0} +workflow.run.job.header=Zamฤ›stnรกnรญ: {0} +workflow.run.job.fallbackName=Prรกce {0} +workflow.run.overview=Workflow bฤ›ลพรญ {0} {1}/{2} hotovo, {3} bฤ›ลพรญ +workflow.run.state.ok=[OK] +workflow.run.state.fail=[CHYBA] +workflow.run.state.running=[Bฤšลฝร] +workflow.run.state.waiting=[ฤŒEKEJTE] +workflow.run.dispatch.verbs=Napouลกtฤ›nรญ|ล˜azenรญ do fronty|Pล™ivolรกnรญ|Spuลกtฤ›nรญ|Bootovรกnรญ +workflow.run.dispatch.objects=workflow|automat|pipeline|bฤ›h +workflow.run.dispatch={0} {1} {2}{3} pro {4} na {5}. +workflow.run.notification.auth=Odeslรกnรญ pracovnรญho postupu GitHub vyลพaduje ovฤ›ล™enรฝ รบฤet GitHub. Pล™idejte nebo obnovte รบฤty v {0}. +workflow.run.notification.openSettings=Otevล™ete Nastavenรญ GitHub +workflow.cache.progress.title=ล˜eลกenรญ akcรญ GitHub +workflow.cache.progress.text=Rozliลกuji {0} {1} +workflow.cache.kind.action=akce +workflow.cache.kind.workflow=pracovnรญ postup +inspection.parameter.input=vstup +inspection.parameter.secret=tajnรฉ +inspection.action.delete.invalid=Smazat neplatnรฝ {0} [{1}] +inspection.action.update.major=Aktualizovat akci [{0}] na [{1}] +inspection.warning.toggle=Pล™epnout upozornฤ›nรญ [{0}] pro [{1}] +inspection.warning.on=na +inspection.warning.off=vypnuto +inspection.statement.incomplete=Neรบplnรฉ prohlรกลกenรญ [{0}] +inspection.invalid.suffix.remove=Odebrat neplatnou pล™รญponu [{0}] +inspection.replace.with=Nahradit za [{0}] +inspection.invalid.remove=Odebrat neplatnรฉ [{0}] +inspection.workflow.syntax.unknownTopLevelKey=Neznรกmรฝ klรญฤ pracovnรญho postupu [{0}] +inspection.workflow.syntax.unknownEventKey=Neznรกmรก udรกlost pracovnรญho postupu [{0}] +inspection.workflow.syntax.unknownTriggerKey=Neznรกmรก spouลกtฤ›cรญ klรกvesa [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Neznรกmรฝ spouลกtฤ›cรญ filtr [{0}] +inspection.workflow.syntax.unknownTriggerValue=Neznรกmรก hodnota spouลกtฤ›ฤe [{0}] +inspection.workflow.syntax.unknownPermission=Neznรกmรฉ oprรกvnฤ›nรญ [{0}] +inspection.workflow.syntax.unknownPermissionValue=Neznรกmรก hodnota oprรกvnฤ›nรญ [{0}] +inspection.workflow.syntax.unknownJobKey=Neznรกmรฝ klรญฤ รบlohy [{0}] +inspection.workflow.syntax.unknownStepKey=Neznรกmรฝ krokovรฝ klรญฤ [{0}] +inspection.action.reload=Znovu naฤรญst [{0}] +inspection.action.unresolved=Nevyล™eลกeno [{0}] โ€“ zkontrolujte pล™รญstup k รบฤtu GitHub, oprรกvnฤ›nรญ soukromรฉho รบloลพiลกtฤ›, limity sazeb, chybฤ›jรญcรญ odkazy nebo chybฤ›jรญcรญ metadata akce/pracovnรญho postupu +inspection.action.jump=Pล™ejรญt na soubor [{0}] +inspection.output.unused=Nepouลพitรฉ [{0}] +inspection.secret.invalid.if=Odebrat [{0}] โ€“ Tajnรก tajemstvรญ nejsou platnรก v pล™รญkazech โ€žifโ€œ. +inspection.secret.replace.runtime=Nahraฤte [{0}] za [{1}] โ€“ pokud nenรญ poskytnuto za bฤ›hu +inspection.needs.invalid.job=Odebrat neplatnรฉ ID รบlohy [{0}] โ€“ toto ID รบlohy neodpovรญdรก ลพรกdnรฉ pล™edchozรญ รบloze +documentation.description=Popis: {0} +documentation.type=Typ: {0} +documentation.required=Poลพadovรกno: {0} +documentation.default=Vรฝchozรญ: {0} +documentation.deprecated=Zastaralรฉ: {0} +documentation.open.declaration=Otevล™enรก deklarace ({0}) +documentation.input.label=Vstup +documentation.secret.label=Tajemstvรญ +documentation.env.label=Promฤ›nnรก prostล™edรญ +documentation.matrix.label=Vlastnost matice +documentation.need.label=Potล™ebnรก prรกce +documentation.need.description=Pล™รญmรก pracovnรญ zรกvislost. +documentation.needOutput.label=Potล™ebnรฝ pracovnรญ vรฝstup +documentation.reusableJob.label=Opakovanฤ› pouลพitelnรก รบloha pracovnรญho postupu +documentation.reusableJob.description=รšloha deklarovanรก v tomto opakovanฤ› pouลพitelnรฉm pracovnรญm postupu. +documentation.reusableJobOutput.label=Opakovanฤ› pouลพitelnรฝ vรฝstup รบlohy pracovnรญho postupu +documentation.service.label=Servisnรญ kontejner +documentation.servicePort.label=Servisnรญ port +documentation.container.label=Pracovnรญ kontejner +documentation.symbol.label=Symbol pracovnรญho postupu +documentation.symbol.description=Vyล™eลกenรฝ vรฝraz workflow. +documentation.workflowOutput.label=Vรฝstup pracovnรญho postupu +documentation.jobOutput.label=Vรฝstup รบlohy +documentation.action.label=Akce +documentation.externalAction.label=Vnฤ›jลกรญ pลฏsobenรญ +documentation.reusableWorkflow.label=Opakovanฤ› pouลพitelnรฝ pracovnรญ postup +documentation.resolvedFrom=vyล™eลกeno z {0} +documentation.notResolved=zatรญm nevyล™eลกeno +documentation.inputs.title=Vstupy +documentation.outputs.title=Vรฝstupy +documentation.secrets.title=Tajemstvรญ +documentation.value.label=Hodnota +documentation.step.title=Krok {0} +documentation.name.label=Jmรฉno +documentation.uses.label=Pouลพitรญ +documentation.run.label=Spustit +documentation.description.label=Popis +documentation.step.label=Krok +documentation.source.label=Zdroj +documentation.stepOutput.label=Krokovรฝ vรฝstup +documentation.context.github=kontext github +documentation.context.github.description=Informace o aktuรกlnรญm bฤ›hu pracovnรญho postupu a udรกlosti. +documentation.context.gitea=kontext gitea +documentation.context.gitea.description=Alias kompatibilnรญ s Gitea pro kontext akcรญ GitHub. +documentation.context.inputs=kontext vstupลฏ +documentation.context.inputs.description=Pracovnรญ postup, odeslรกnรญ nebo vstupy akcรญ jsou k dispozici zde. +documentation.context.secrets=kontext tajemstvรญ +documentation.context.secrets.description=Tajnรฉ hodnoty dostupnรฉ pro tento pracovnรญ postup nebo opakovanฤ› pouลพitelnรฉ volรกnรญ pracovnรญho postupu. +documentation.context.env=kontext env +documentation.context.env.description=Promฤ›nnรฉ prostล™edรญ viditelnรฉ na tomto mรญstฤ›. +documentation.context.matrix=maticovรฝ kontext +documentation.context.matrix.description=Maticovรฉ hodnoty pro aktuรกlnรญ รบlohu. +documentation.context.steps=kontext krokลฏ +documentation.context.steps.description=Pล™edchozรญ kroky v aktuรกlnรญ รบloze, vฤetnฤ› vรฝstupลฏ a stavu. +documentation.context.needs=potล™ebuje kontext +documentation.context.needs.description=Pล™รญmรฉ pracovnรญ zรกvislosti a jejich vรฝstupy/vรฝsledky. +documentation.context.jobs=kontext pracovnรญch mรญst +documentation.context.jobs.description=Opakovanฤ› pouลพitelnรฉ รบlohy a vรฝstupy pracovnรญho postupu. +documentation.context.outputs=vรฝstupy +documentation.context.outputs.description=Vรฝstupnรญ hodnoty vystavenรฉ tรญmto krokem nebo รบlohou. +documentation.context.result=vรฝsledek +documentation.context.result.description=Vรฝsledek prรกce: รบspฤ›ch, neรบspฤ›ch, zruลกenรญ nebo pล™eskoฤenรญ. +documentation.context.outcome=vรฝsledek +documentation.context.outcome.description=Vรฝsledek kroku pล™ed pouลพitรญm chyby continue-on-error. +documentation.context.conclusion=zรกvฤ›r +documentation.context.conclusion.description=Vรฝsledek kroku po pouลพitรญ chyby continue-on-error. +error.report.action=Nahlรกsit vรฝjimku +error.report.description=Popis +error.report.steps=Kroky k reprodukci +error.report.sample=V pล™รญpadฤ› potล™eby uveฤte vzorek kรณdu +error.report.message=Zprรกva +error.report.runtime=Informace o bฤ›hu +error.report.pluginVersion=Verze pluginu: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=Stacktrace +completion.shell.bash=Bash shell. Pouลพรญvรก bash na bฤ›ลพcรญch Linux a macOS a Git pro bash Windows na bฤ›ลพcรญch Windows. +completion.shell.sh=Zรกloลพnรญ prostล™edรญ POSIX. +completion.shell.pwsh=Jรกdro PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Pล™รญkazovรฝ ล™รกdek Windows. +completion.shell.python=Spouลกtฤ›ฤ pล™รญkazลฏ Python. +completion.runner.name=Jmรฉno bฤ›ลพce provรกdฤ›jรญcรญho รบlohu. +completion.runner.os=Operaฤnรญ systรฉm bฤ›ลพce provรกdฤ›jรญcรญho รบlohu. Moลพnรฉ hodnoty jsou Linux, Windows nebo macOS. +completion.runner.arch=Architektura bฤ›ลพce provรกdฤ›jรญcรญho รบlohu. Moลพnรฉ hodnoty jsou X86, X64, ARM nebo ARM64. +completion.runner.temp=Cesta k doฤasnรฉmu adresรกล™i na runneru. Tento adresรกล™ se vyprรกzdnรญ na zaฤรกtku a na konci kaลพdรฉ รบlohy. Upozorลˆujeme, ลพe soubory nebudou odstranฤ›ny, pokud uลพivatelskรฝ รบฤet bฤ›ลพce nemรก oprรกvnฤ›nรญ k jejich odstranฤ›nรญ. +completion.runner.toolCache=Cesta k adresรกล™i obsahujรญcรญmu pล™edinstalovanรฉ nรกstroje pro bฤ›ลพce hostovanรฉ GitHub. +completion.runner.debug=Toto se nastavuje pouze v pล™รญpadฤ›, ลพe je povoleno protokolovรกnรญ ladฤ›nรญ, a vลพdy mรก hodnotu 1. +completion.runner.environment=Prostล™edรญ bฤ›ลพce vykonรกvajรญcรญho รบlohu. Moลพnรฉ hodnoty jsou github-hosted nebo self-hosted. +completion.job.status=Aktuรกlnรญ stav รบlohy. +completion.job.checkRunId=ID kontrolnรญho bฤ›hu aktuรกlnรญ รบlohy. +completion.job.container=Informace o kontejneru รบlohy. +completion.job.services=Servisnรญ kontejnery vytvoล™enรฉ pro รบlohu. +completion.job.workflowRef=รšplnรก reference souboru pracovnรญho postupu, kterรก definuje aktuรกlnรญ รบlohu. +completion.job.workflowSha=Potvrzenรญ SHA souboru pracovnรญho postupu, kterรฝ definuje aktuรกlnรญ รบlohu. +completion.job.workflowRepository=Vlastnรญk/รบloลพiลกtฤ› รบloลพiลกtฤ› obsahujรญcรญ soubor pracovnรญho postupu, kterรฝ definuje aktuรกlnรญ รบlohu. +completion.job.workflowFilePath=Cesta k souboru pracovnรญho postupu vzhledem ke koล™enovรฉmu adresรกล™i รบloลพiลกtฤ›. +completion.job.containerField=Pole kontejneru prรกce +completion.job.service=Pracovnรญ sluลพba +completion.job.serviceField=Obor pracovnรญch sluลพeb +completion.job.mappedServicePort=Mapovanรฝ servisnรญ port +completion.strategy.failFast=Zda se zruลกรญ vลกechny probรญhajรญcรญ รบlohy, pokud nฤ›kterรก maticovรก รบloha selลพe. +completion.strategy.jobIndex=Index aktuรกlnรญ รบlohy v matici zaloลพenรฝ na nule. +completion.strategy.jobTotal=Celkovรฝ poฤet รบloh v matici. +completion.strategy.maxParallel=Maximรกlnรญ poฤet maticovรฝch รบloh, kterรฉ mohou bฤ›ลพet souฤasnฤ›. +completion.context.inputs=Vstupy pracovnรญho postupu, jako je workflow_dispatch nebo workflow_call. +completion.context.secrets=Tajemstvรญ pracovnรญho postupu. +completion.context.job=Informace o aktuรกlnฤ› probรญhajรญcรญ รบloze. +completion.context.jobs=Pracovnรญ toky. +completion.context.matrix=Vlastnosti matice definovanรฉ pro aktuรกlnรญ รบlohu matice. +completion.context.strategy=Informace o strategii provรกdฤ›nรญ matice pro aktuรกlnรญ รบlohu. +completion.context.steps=Kroky s ID v aktuรกlnรญ prรกci. +completion.context.env=Promฤ›nnรฉ prostล™edรญ z รบloh a krokลฏ. +completion.context.vars=Vlastnรญ konfiguraฤnรญ promฤ›nnรฉ z rozsahu organizace, รบloลพiลกtฤ› a prostล™edรญ. +completion.context.needs=รšlohy, kterรฉ musรญ bรฝt dokonฤeny, neลพ mลฏลพe bรฝt tato รบloha spuลกtฤ›na, plus jejich vรฝstupy a vรฝsledky. +completion.context.github=Informace o bฤ›hu pracovnรญho postupu a udรกlostech z kontextu GitHub. +completion.context.gitea=Alias kompatibilnรญ s Gitea pro kontext akcรญ GitHub. +completion.context.runner=Informace o bฤ›ลพci, kterรฝ provรกdรญ aktuรกlnรญ รบlohu. +completion.secret.githubToken=Automaticky vytvoล™enรฝ token pro kaลพdรฉ spuลกtฤ›nรญ pracovnรญho postupu. +completion.remote.repository=Vzdรกlenรฉ รบloลพiลกtฤ› +completion.uses.local.workflow=Mรญstnรญ opakovanฤ› pouลพitelnรฝ pracovnรญ postup +completion.uses.local.action=Mรญstnรญ akce +completion.uses.ref.known=Znรกmรฝ odkaz na pracovnรญ postup +completion.uses.ref.remote=Vzdรกlenรก reference pracovnรญho postupu +completion.uses.remote.known=Znรกmรก vzdรกlenรก akce nebo opakovanฤ› pouลพitelnรฝ pracovnรญ postup +completion.workflow.syntax=Syntaxe pracovnรญho postupu akcรญ GitHub +completion.workflow.top.name=Zobrazovanรฝ nรกzev pracovnรญho postupu +completion.workflow.top.run-name=Nรกzev dynamickรฉho bฤ›hu +completion.workflow.top.on=Udรกlosti, kterรฉ spouลกtฤ›jรญ pracovnรญ postup +completion.workflow.top.permissions=Vรฝchozรญ oprรกvnฤ›nรญ GITHUB_TOKEN +completion.workflow.top.env=Promฤ›nnรฉ prostล™edรญ pro celรฝ pracovnรญ postup +completion.workflow.top.defaults=Vรฝchozรญ nastavenรญ รบlohy a kroku +completion.workflow.top.concurrency=Skupina soubฤ›ลพnosti a zruลกenรญ +completion.workflow.top.jobs=รšlohy, kterรฉ bฤ›ลพรญ v tomto pracovnรญm postupu +completion.workflow.event.branch_protection_rule=Ochrana vฤ›tve se zmฤ›nila +completion.workflow.event.check_run=Jeden kontrolnรญ bฤ›h byl zmฤ›nฤ›n +completion.workflow.event.check_suite=Zkontrolujte sadu zmฤ›nฤ›n +completion.workflow.event.create=Byla vytvoล™ena vฤ›tev nebo znaฤka +completion.workflow.event.delete=Vฤ›tev nebo znaฤka smazรกna +completion.workflow.event.deployment=Nasazenรญ vytvoล™eno +completion.workflow.event.deployment_status=Stav nasazenรญ se zmฤ›nil +completion.workflow.event.discussion=Diskuse se zmฤ›nila +completion.workflow.event.discussion_comment=Komentรกล™ v diskuzi zmฤ›nฤ›n +completion.workflow.event.fork=รšloลพiลกtฤ› rozvฤ›tvenรฉ +completion.workflow.event.gollum=Strรกnka Wiki zmฤ›nฤ›na +completion.workflow.event.image_version=Verze obrรกzku balรญฤku se zmฤ›nila +completion.workflow.event.issue_comment=Problรฉm nebo komentรกล™ PR se zmฤ›nil +completion.workflow.event.issues=Problรฉm se zmฤ›nil +completion.workflow.event.label=ล tรญtek se zmฤ›nil +completion.workflow.event.merge_group=Poลพadovรกna kontrola fronty slouฤenรญ +completion.workflow.event.milestone=Milnรญk se zmฤ›nil +completion.workflow.event.page_build=Sestavenรญ strรกnek probฤ›hlo +completion.workflow.event.project=Klasickรฝ projekt se zmฤ›nil +completion.workflow.event.project_card=Klasickรก karta projektu zmฤ›nฤ›na +completion.workflow.event.project_column=Sloupec klasickรฉho projektu zmฤ›nฤ›n +completion.workflow.event.public=รšloลพiลกtฤ› se stalo veล™ejnรฝm +completion.workflow.event.pull_request=Poลพadavek na staลพenรญ zmฤ›nฤ›n +completion.workflow.event.pull_request_review=Recenze PR zmฤ›nฤ›na +completion.workflow.event.pull_request_review_comment=Komentรกล™ recenze PR zmฤ›nฤ›n +completion.workflow.event.pull_request_target=Cรญlovรฝ kontext PR. Ostrรฉ noลพe. +completion.workflow.event.push=Potvrzenรญ nebo oznaฤenรญ bylo odeslรกno +completion.workflow.event.registry_package=Balรญฤek byl zveล™ejnฤ›n nebo aktualizovรกn +completion.workflow.event.release=Vydรกnรญ zmฤ›nฤ›no +completion.workflow.event.repository_dispatch=Vlastnรญ udรกlost API +completion.workflow.event.schedule=Cron klรญลกtฤ›. Hodinovรฝ stroj. +completion.workflow.event.status=Stav zรกvazku zmฤ›nฤ›n +completion.workflow.event.watch=รšloลพiลกtฤ› oznaฤeno hvฤ›zdiฤkou +completion.workflow.event.workflow_call=Opakovanฤ› pouลพitelnรฉ volรกnรญ pracovnรญho postupu +completion.workflow.event.workflow_dispatch=Tlaฤรญtko ruฤnรญho spuลกtฤ›nรญ +completion.workflow.event.workflow_run=Spuลกtฤ›nรญ pracovnรญho postupu se zmฤ›nilo +completion.workflow.eventFilter.types=Omezte typy aktivit +completion.workflow.eventFilter.branches=Pouze tyto vฤ›tve +completion.workflow.eventFilter.branches-ignore=Tyto vฤ›tve pล™eskoฤte +completion.workflow.eventFilter.tags=Pouze tyto znaฤky +completion.workflow.eventFilter.tags-ignore=Tyto znaฤky pล™eskoฤte +completion.workflow.eventFilter.paths=Pouze tyto cesty +completion.workflow.eventFilter.paths-ignore=Pล™eskoฤte tyto cesty +completion.workflow.eventFilter.workflows=Nรกzvy pracovnรญch postupลฏ ke sledovรกnรญ +completion.workflow.eventFilter.cron=Plรกn Cron. Drobnรฝ hodinovรฝ strojek. +completion.workflow.permission.actions=Workflow bฤ›ลพรญ a akฤnรญ artefakty +completion.workflow.permission.artifact-metadata=Zรกznamy metadat artefaktลฏ +completion.workflow.permission.attestations=Atestace artefaktลฏ +completion.workflow.permission.checks=Zkontrolujte bฤ›hy a sady +completion.workflow.permission.code-quality=Zprรกvy o kvalitฤ› kรณdu +completion.workflow.permission.contents=Obsah รบloลพiลกtฤ› +completion.workflow.permission.deployments=Nasazenรญ +completion.workflow.permission.discussions=Diskuse +completion.workflow.permission.id-token=tokeny OpenID Connect +completion.workflow.permission.issues=Problรฉmy +completion.workflow.permission.models=Modely GitHub +completion.workflow.permission.packages=Balรญฤky GitHub +completion.workflow.permission.pages=Strรกnky GitHub +completion.workflow.permission.pull-requests=Vytรกhnฤ›te poลพadavky +completion.workflow.permission.security-events=Skenovรกnรญ kรณdu a bezpeฤnostnรญ udรกlosti +completion.workflow.permission.statuses=Stavy zรกvazkลฏ +completion.workflow.permission.vulnerability-alerts=Upozornฤ›nรญ Dependabot +completion.workflow.permission.value.read=Pล™รญstup ke ฤtenรญ +completion.workflow.permission.value.write=Pล™รญstup pro zรกpis vฤetnฤ› ฤtenรญ +completion.workflow.permission.value.none=ลฝรกdnรฝ pล™รญstup +completion.workflow.permission.shorthand.read-all=Vลกechna oprรกvnฤ›nรญ ฤรญst. Velkรก deka. +completion.workflow.permission.shorthand.write-all=Vลกechna oprรกvnฤ›nรญ k zรกpisu. Velkรฉ kladivo. +completion.workflow.permission.shorthand.empty=Zakรกzat oprรกvnฤ›nรญ tokenu +completion.workflow.job.name=Zobrazovanรฝ nรกzev รบlohy +completion.workflow.job.permissions=Oprรกvnฤ›nรญ tokenu รบlohy +completion.workflow.job.needs=Prรกce na poฤkรกnรญ +completion.workflow.job.if=Pracovnรญ podmรญnka +completion.workflow.job.runs-on=Bฤ›ลพeckรฝ ลกtรญtek nebo skupina +completion.workflow.job.snapshot=Snรญmek bฤ›ลพce +completion.workflow.job.environment=Prostล™edรญ nasazenรญ +completion.workflow.job.concurrency=Zรกmek soubฤ›ลพnosti prรกce +completion.workflow.job.outputs=Vรฝstupy, kterรฉ mohou ฤรญst jinรฉ รบlohy +completion.workflow.job.env=Promฤ›nnรฉ pracovnรญho prostล™edรญ +completion.workflow.job.defaults=Vรฝchozรญ nastavenรญ รบlohy +completion.workflow.job.steps=Seznam krokลฏ. Skuteฤnรก prรกce. +completion.workflow.job.timeout-minutes=ฤŒasovรฝ limit รบlohy v minutรกch +completion.workflow.job.strategy=Matice a strategie plรกnovรกnรญ +completion.workflow.job.continue-on-error=Nechte tuto prรกci jemnฤ› propadnout +completion.workflow.job.container=Kontejner pro tuto prรกci +completion.workflow.job.services=Servisnรญ kontejnery postrannรญch vozรญkลฏ +completion.workflow.job.uses=Opakovanฤ› pouลพitelnรฝ pracovnรญ postup pro volรกnรญ +completion.workflow.job.with=Vstupy pro volanรฝ pracovnรญ postup +completion.workflow.job.secrets=Tajemstvรญ pro tzv. workflow +completion.workflow.defaultsRun.shell=Vรฝchozรญ shell pro kroky bฤ›hu +completion.workflow.defaultsRun.working-directory=Vรฝchozรญ pracovnรญ adresรกล™ +completion.workflow.concurrency.group=Nรกzev zรกmku pro bฤ›hy ve frontฤ› +completion.workflow.concurrency.cancel-in-progress=Zruลกit starลกรญ srovnรกvacรญ bฤ›hy +completion.workflow.environment.name=Nรกzev prostล™edรญ +completion.workflow.environment.url=Prostล™edรญ URL +completion.workflow.strategy.matrix=Maticovรฉ osy a varianty +completion.workflow.strategy.fail-fast=Zruลกit maticovรฉ sourozence pล™i selhรกnรญ +completion.workflow.strategy.max-parallel=Maticovรฝ paralelismus ฤepice +completion.workflow.matrix.include=Pล™idejte maticovรฉ kombinace +completion.workflow.matrix.exclude=Odstraลˆte maticovรฉ kombinace +completion.workflow.step.id=ID kroku pro reference +completion.workflow.step.if=Krokovรก podmรญnka +completion.workflow.step.name=Zobrazovanรฝ nรกzev kroku +completion.workflow.step.uses=Akce ke spuลกtฤ›nรญ +completion.workflow.step.run=Shell skript ke spuลกtฤ›nรญ +completion.workflow.step.shell=Shell pro tento krok bฤ›hu +completion.workflow.step.with=Akฤnรญ vstupy +completion.workflow.step.env=Krokovรฉ promฤ›nnรฉ prostล™edรญ +completion.workflow.step.continue-on-error=Nechte tento krok jemnฤ› selhat +completion.workflow.step.timeout-minutes=ฤŒasovรฝ limit kroku v minutรกch +completion.workflow.step.working-directory=Krok pracovnรญ adresรกล™ +completion.workflow.container.image=Obrรกzek kontejneru +completion.workflow.container.credentials=Registraฤnรญ รบdaje +completion.workflow.container.env=Promฤ›nnรฉ prostล™edรญ kontejneru +completion.workflow.container.ports=Porty k vystavenรญ +completion.workflow.container.volumes=Svazky k pล™ipojenรญ +completion.workflow.container.options=Docker vytvoล™it moลพnosti +completion.workflow.service.image=Obrรกzek kontejneru sluลพby +completion.workflow.service.credentials=Registraฤnรญ รบdaje +completion.workflow.service.env=Promฤ›nnรฉ prostล™edรญ sluลพby +completion.workflow.service.ports=Servisnรญ porty +completion.workflow.service.volumes=Objemy sluลพeb +completion.workflow.service.options=Docker vytvoล™it moลพnosti +completion.workflow.credentials.username=Registrovat uลพivatelskรฉ jmรฉno +completion.workflow.credentials.password=Heslo nebo token registru +completion.workflow.inputType.string=Zadรกvรกnรญ textu +completion.workflow.inputType.boolean=Sprรกvnรฝ nebo nepravdivรฝ vstup +completion.workflow.inputType.choice=Zadรกnรญ rozbalovacรญ nabรญdky +completion.workflow.inputType.number=Zadรกnรญ ฤรญsla +completion.workflow.inputType.environment=Vstup pro vรฝbฤ›r prostล™edรญ +completion.workflow.boolean.true=Ano. Zapnฤ›te to. +completion.workflow.boolean.false=Ne. Nechte to ve tmฤ›. +completion.workflow.runner.ubuntu-latest=Nejnovฤ›jลกรญ bฤ›ลพec Ubuntu +completion.workflow.runner.ubuntu-24.04=Bฤ›ลพec Ubuntu 24.04 +completion.workflow.runner.ubuntu-22.04=Bฤ›ลพec Ubuntu 22.04 +completion.workflow.runner.windows-latest=Nejnovฤ›jลกรญ bฤ›ลพec Windows +completion.workflow.runner.windows-2025=Bฤ›ลพec Windows Server 2025 +completion.workflow.runner.windows-2022=Bฤ›ลพec Windows Server 2022 +completion.workflow.runner.macos-latest=Nejnovฤ›jลกรญ bฤ›ลพec macOS +completion.workflow.runner.macos-15=macOS 15 bฤ›ลพec +completion.workflow.runner.macos-14=macOS 14 bฤ›ลพec +completion.workflow.runner.self-hosted=Vรกลก vlastnรญ bฤ›ลพec. Vรกลก cirkus. +completion.steps.outputs=Sada vรฝstupลฏ definovanรฝch pro krok. +completion.steps.conclusion=Pouลพije se vรฝsledek dokonฤenรฉho kroku po pokraฤovรกnรญ pล™i chybฤ›. +completion.steps.outcome=Pouลพije se vรฝsledek dokonฤenรฉho kroku pล™ed pokraฤovรกnรญm pล™i chybฤ›. +completion.jobs.outputs=Sada vรฝstupลฏ definovanรฝch pro รบlohu. +completion.jobs.result=Vรฝsledek prรกce. +settings.displayName=Pracovnรญ postup GitHub +settings.language.label=jazyk: +settings.language.system=IDE/vรฝchozรญ nastavenรญ systรฉmu +settings.cache.title=Mezipamฤ›ลฅ akcรญ +settings.cache.column.key=Klรญฤ mezipamฤ›ti +settings.cache.column.name=Jmรฉno +settings.cache.column.kind=Laskavรฝ +settings.cache.column.state=stรกt +settings.cache.column.expires=Platnost vyprลกรญ +settings.cache.kind.local=mรญstnรญ +settings.cache.kind.remote=vzdรกlenรฉ +settings.cache.state.resolved=vyล™eลกeno +settings.cache.state.pending=ฤekรก na vyล™รญzenรญ +settings.cache.state.expired=zatuchlรฝ +settings.cache.state.suppressed=potlaฤeno +settings.cache.refresh=Refresh stลฏl +settings.cache.deleteSelected=Smazat vybranรฉ +settings.cache.deleteAll=Smazat vลกe +settings.cache.export=Exportovat +settings.cache.import=Importovat +settings.cache.summary=Mezipamฤ›ลฅ: poloลพky {0}, {1} vyล™eลกeno, {2} vzdรกlenรฉ, {3} zastaralรฉ, {4} ztlumeno. Mezipamฤ›ลฅ: {5} KB. +settings.cache.noneSelected=Nejprve vyberte ล™รกdky mezipamฤ›ti. Koลกtฤ› odmรญtรก dohady. +settings.cache.deleteSelected.done=Smazanรฉ poloลพky mezipamฤ›ti {0}. Obsaลพen drobnรฝ oblak prachu. +settings.cache.deleteAll.confirm=Smazat vลกechny poloลพky mezipamฤ›ti GitHub Workflow? +settings.cache.deleteAll.done=Smazรกny vลกechny poloลพky mezipamฤ›ti. Cache je nynรญ podezล™ele tichรก. +settings.cache.export.done=Exportovanรฉ poloลพky mezipamฤ›ti {0}. Malรก archivnรญ ลกelma v kleci. +settings.cache.import.done=Importovanรฉ poloลพky mezipamฤ›ti. Archivnรญ bestie se chovala. +settings.cache.import.unsupported=Nepodporovanรฝ soubor mezipamฤ›ti GitHub Workflow. +settings.cache.import.brokenLine=Pล™eruลกenรฝ ล™รกdek mezipamฤ›ti GitHub Workflow. +settings.cache.import.brokenKey=Poลกkozenรฝ klรญฤ mezipamฤ›ti GitHub Workflow. +settings.support.button=Podporujte tento plugin +settings.support.tooltip=Otevล™ete strรกnku podpory +settings.support.line.0=Naplลˆte stavebnรญ pec +settings.support.line.1=Kupte si analyzรกtorovou kรกvu +settings.support.line.2=Sponzorujte mรฉnฤ› straลกidelnรฝch pracovnรญch postupลฏ +workflow.run.jobs.title=Pracovnรญ toky +workflow.run.jobs.root=Spuลกtฤ›nรญ pracovnรญho postupu +workflow.run.jobs.description=Strom รบloh GitHub Actions a vybranรฝ protokol รบloh +workflow.run.tree.done=hotovo +workflow.run.tree.failed=nepodaล™ilo +workflow.run.tree.skipped=pล™eskoฤeno +workflow.run.tree.warn=varovat +workflow.run.tree.err=chybovat +workflow.run.delete.tooltip=Smazat bฤ›h +workflow.run.delete.noRun=Zatรญm ลพรกdnรฉ ID spuลกtฤ›nรญ. +workflow.run.delete.requested=Mazรกnรญ bฤ›hu {0}. +workflow.run.delete.done=Spuลกtฤ›nรญ {0} bylo smazรกno. +workflow.run.delete.http=Smazat HTTP {0}. Uf. +workflow.run.delete.failed=Smazat fizzled: {0} +workflow.run.rerun.all.tooltip=Znovu spustit pracovnรญ postup +workflow.run.rerun.failed.tooltip=Znovu spusลฅte neรบspฤ›ลกnรฉ รบlohy +workflow.run.rerun.noRun=Zatรญm ลพรกdnรฉ ID spuลกtฤ›nรญ. +workflow.run.rerun.all.requested=Poลพadovanรฉ opฤ›tovnรฉ spuลกtฤ›nรญ: {0}. +workflow.run.rerun.failed.requested=Poลพadovanรฉ neรบspฤ›ลกnรฉ รบlohy: {0}. +workflow.run.rerun.all.done=Znovu spustit ve frontฤ›: {0}. +workflow.run.rerun.failed.done=Neรบspฤ›ลกnรฉ รบlohy ve frontฤ›: {0}. +workflow.run.rerun.http=Znovu spusลฅte HTTP {0}. Uf. +workflow.run.rerun.failed=Opฤ›tovnรฉ spuลกtฤ›nรญ zhaslo: {0} +workflow.run.download.log.tooltip=Uloลพit protokol รบlohy +workflow.run.download.artifacts.tooltip=Stรกhnฤ›te si artefakty +workflow.run.download.noRun=Zatรญm ลพรกdnรฉ ID spuลกtฤ›nรญ. +workflow.run.download.log.requested=Naฤรญtรกnรญ protokolu pro {0}. +workflow.run.download.log.done=Protokol byl uloลพen: {0}. +workflow.run.download.artifacts.requested=Vyzvedรกvรกnรญ artefaktลฏ. +workflow.run.download.artifacts.empty=ลฝรกdnรฉ artefakty. Drobnรก prรกzdnota. +workflow.run.download.artifact.expired=Platnost artefaktu vyprลกela: {0}. +workflow.run.download.artifact.done=Uloลพenรฝ artefakt: {0} -> {1}. +workflow.run.download.failed=Stahovรกnรญ selhalo: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_de.properties b/src/main/resources/messages/GitHubWorkflowBundle_de.properties new file mode 100644 index 0000000..f4997a3 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_de.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub-Workflow +plugin.description=Unterstรผtzung fรผr GitHub Actions-Workflowdateien +group.GitHubWorkflow.Tools.text=GitHub-Workflow +group.GitHubWorkflow.Tools.description=GitHub Workflow-Plugin-Tools +action.GitHubWorkflow.RefreshActionCache.text=Action-Cache aktualisieren +action.GitHubWorkflow.RefreshActionCache.description=Metadaten aufgelรถster entfernter GitHub Actions und wiederverwendbarer Workflows aktualisieren +action.GitHubWorkflow.RestoreActionWarnings.text=Aktionswarnungen wiederherstellen +action.GitHubWorkflow.RestoreActionWarnings.description=Unterdrรผckte Aktions-, Eingabe- und Ausgabevalidierungswarnungen wiederherstellen +action.GitHubWorkflow.ClearActionCache.text=Aktionscache leeren +action.GitHubWorkflow.ClearActionCache.description=Lรถschen Sie zwischengespeicherte GitHub-Aktionen und wiederverwendbare Workflow-Metadaten +notification.cache.cleared={0} zwischengespeicherte GitHub-Workflow-Eintrรคge gelรถscht. +notification.cache.refresh.started=Refreshing {0} zwischengespeicherte Remote-GitHub-Workflow-Eintrรคge. +notification.warnings.restored=Warnungen fรผr {0} GitHub Workflow-Eintrรคge wiederhergestellt. +workflow.run.configuration.display=GitHub-Workflow +workflow.run.configuration.description=Verteilen und befolgen Sie die Workflow-Ausfรผhrungen von GitHub-Aktionen +workflow.run.configuration.name=GitHub-Workflow: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=Besitzer +workflow.run.field.repo=Repo +workflow.run.field.workflow=Workflow-Datei +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Token-Env-Var-Fallback +workflow.run.inputs.title=workflow_dispatch-Eingaben (Schlรผssel=Wert) +workflow.run.error.apiUrl=GitHub API URL ist erforderlich. +workflow.run.error.repository=Besitzer und Name des GitHub-Repositorys sind erforderlich. +workflow.run.error.workflow=Workflow-Datei ist erforderlich. +workflow.run.error.ref=Branch- oder Tag-Referenz ist erforderlich. +workflow.run.error.inputs=GitHub workflow_dispatch unterstรผtzt maximal 25 Eingรคnge. +workflow.run.gutter.stop=Stoppen Sie die Workflow-Ausfรผhrung +workflow.run.gutter.stop.text=Stoppen Sie die Workflow-Ausfรผhrung +workflow.run.gutter.stop.description=Brechen Sie diesen Lauf ab +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=Lauf: +workflow.log.warning=Warnung: +workflow.log.error=Fehler: +workflow.run.cancel.requested=Abbruch angefordert: {0}. +workflow.run.stop.before.id=Stopp angefordert. Noch keine Lauf-ID. +workflow.run.cancel.http=Abbrechen HTTP {0}. Uff. +workflow.run.cancel.failed=Abbrechen fehlgeschlagen: {0} +workflow.run.interrupted=Unterbrochen. +workflow.run.link=Fรผhren Sie Folgendes aus: {0} +workflow.run.discovery=Lauf akzeptiert. Jagdlauf-ID. +workflow.run.discovery.none=Noch keine Lauf-ID. Auf der Registerkarte โ€žAktionenโ€œ erfahren Sie mehr. +workflow.run.status=Status: {0}{1} +workflow.run.job.main=Auftrag: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Status: {0} {1}{2}{3} +workflow.run.logs.later=Protokolle werden angezeigt, wenn GitHub sie verรถffentlicht. +workflow.run.job.logs.later=Auftrag {0}: {1} +workflow.run.log.failed=Protokoll-Download fehlgeschlagen: {0} +workflow.run.log.failed.job=Protokoll-Download fรผr {0} fehlgeschlagen: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Auftrag: {0} +workflow.run.job.fallbackName=Auftrag {0} +workflow.run.overview=Workflowlauf {0} {1}/{2} abgeschlossen, {3} lรคuft +workflow.run.state.ok=[OK] +workflow.run.state.fail=[FEHLER] +workflow.run.state.running=[LAUFEN] +workflow.run.state.waiting=[WARTEN] +workflow.run.dispatch.verbs=Vorbereitung|Warteschlange|Beschwรถrung|Start|Booten +workflow.run.dispatch.objects=Workflow|Automatisierung|Pipeline|Ausfรผhren +workflow.run.dispatch={0} {1} {2}{3} fรผr {4} auf {5}. +workflow.run.notification.auth=Fรผr den GitHub-Workflow-Versand ist ein authentifiziertes GitHub-Konto erforderlich. Fรผgen Sie Konten in {0} hinzu oder aktualisieren Sie sie. +workflow.run.notification.openSettings=ร–ffnen Sie die GitHub-Einstellungen +workflow.cache.progress.title=Auflรถsen von GitHub-Aktionen +workflow.cache.progress.text={0} {1} wird aufgelรถst +workflow.cache.kind.action=Aktion +workflow.cache.kind.workflow=Arbeitsablauf +inspection.parameter.input=Eingabe +inspection.parameter.secret=Geheimnis +inspection.action.delete.invalid=Ungรผltig: {0} [{1}] lรถschen +inspection.action.update.major=Aktion [{0}] auf [{1}] aktualisieren +inspection.warning.toggle=Warnungen umschalten [{0}] fรผr [{1}] +inspection.warning.on=auf +inspection.warning.off=aus +inspection.statement.incomplete=Unvollstรคndige Anweisung [{0}] +inspection.invalid.suffix.remove=Ungรผltiges Suffix [{0}] entfernen +inspection.replace.with=Ersetzen durch [{0}] +inspection.invalid.remove=Ungรผltiges [{0}] entfernen +inspection.workflow.syntax.unknownTopLevelKey=Unbekannter Workflow-Schlรผssel [{0}] +inspection.workflow.syntax.unknownEventKey=Unbekanntes Workflow-Ereignis [{0}] +inspection.workflow.syntax.unknownTriggerKey=Unbekannter Triggerschlรผssel [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Unbekannter Triggerfilter [{0}] +inspection.workflow.syntax.unknownTriggerValue=Unbekannter Triggerwert [{0}] +inspection.workflow.syntax.unknownPermission=Unbekannte Berechtigung [{0}] +inspection.workflow.syntax.unknownPermissionValue=Unbekannter Berechtigungswert [{0}] +inspection.workflow.syntax.unknownJobKey=Unbekannter Jobschlรผssel [{0}] +inspection.workflow.syntax.unknownStepKey=Unbekannter Schrittschlรผssel [{0}] +inspection.action.reload=Neu laden [{0}] +inspection.action.unresolved=Nicht gelรถst [{0}] โ€“ รœberprรผfen Sie den GitHub-Kontozugriff, private Repository-Berechtigungen, Ratenbeschrรคnkungen, fehlende Referenzen oder fehlende Aktions-/Workflow-Metadaten +inspection.action.jump=Zur Datei springen [{0}] +inspection.output.unused=Unbenutzt [{0}] +inspection.secret.invalid.if=Entfernen Sie [{0}] โ€“ Geheimnisse sind in โ€žifโ€œ-Anweisungen nicht gรผltig +inspection.secret.replace.runtime=Ersetzen Sie [{0}] durch [{1}] โ€“ wenn es zur Laufzeit nicht bereitgestellt wird +inspection.needs.invalid.job=Entfernen Sie die ungรผltige Job-ID [{0}] โ€“ diese Job-ID stimmt mit keinem vorherigen Job รผberein +documentation.description=Beschreibung: {0} +documentation.type=Typ: {0} +documentation.required=Erforderlich: {0} +documentation.default=Standard: {0} +documentation.deprecated=Veraltet: {0} +documentation.open.declaration=Deklaration รถffnen ({0}) +documentation.input.label=Eingabe +documentation.secret.label=Geheimnis +documentation.env.label=Umgebungsvariable +documentation.matrix.label=Matrixeigenschaft +documentation.need.label=Benรถtigter Job +documentation.need.description=Direkte Jobabhรคngigkeit. +documentation.needOutput.label=Erforderliche Auftragsausgabe +documentation.reusableJob.label=Wiederverwendbarer Workflow-Job +documentation.reusableJob.description=In diesem wiederverwendbaren Workflow deklarierter Job. +documentation.reusableJobOutput.label=Wiederverwendbare Workflow-Job-Ausgabe +documentation.service.label=Servicecontainer +documentation.servicePort.label=Service-Port +documentation.container.label=Jobcontainer +documentation.symbol.label=Workflow-Symbol +documentation.symbol.description=Workflow-Ausdruck behoben. +documentation.workflowOutput.label=Workflow-Ausgabe +documentation.jobOutput.label=Auftragsausgabe +documentation.action.label=Aktion +documentation.externalAction.label=ร„uรŸeres Handeln +documentation.reusableWorkflow.label=Wiederverwendbarer Workflow +documentation.resolvedFrom=behoben von {0} +documentation.notResolved=noch nicht gelรถst +documentation.inputs.title=Eingaben +documentation.outputs.title=Ausgรคnge +documentation.secrets.title=Geheimnisse +documentation.value.label=Wert +documentation.step.title=Schritt {0} +documentation.name.label=Bezeichnung +documentation.uses.label=Verwendungsmรถglichkeiten +documentation.run.label=Lauf +documentation.description.label=Beschreibung +documentation.step.label=Schritt +documentation.source.label=Quelle +documentation.stepOutput.label=Schrittausgabe +documentation.context.github=Github-Kontext +documentation.context.github.description=Informationen zur aktuellen Workflow-Ausfรผhrung und zum aktuellen Ereignis. +documentation.context.gitea=Gitea-Kontext +documentation.context.gitea.description=Gitea-kompatibler Alias fรผr den GitHub-Aktionskontext. +documentation.context.inputs=Eingabekontext +documentation.context.inputs.description=Workflow-, Versand- oder Aktionseingaben sind hier verfรผgbar. +documentation.context.secrets=Geheimnisse Kontext +documentation.context.secrets.description=Geheime Werte, die fรผr diesen Workflow oder wiederverwendbaren Workflow-Aufruf verfรผgbar sind. +documentation.context.env=env-Kontext +documentation.context.env.description=An dieser Stelle sichtbare Umgebungsvariablen. +documentation.context.matrix=Matrixkontext +documentation.context.matrix.description=Matrixwerte fรผr den aktuellen Job. +documentation.context.steps=Schritte Kontext +documentation.context.steps.description=Vorherige Schritte im aktuellen Job, einschlieรŸlich Ausgaben und Status. +documentation.context.needs=braucht Kontext +documentation.context.needs.description=Direkte Jobabhรคngigkeiten und deren Ausgaben/Ergebnisse. +documentation.context.jobs=Beschรคftigungskontext +documentation.context.jobs.description=Wiederverwendbare Workflow-Jobs und -Ausgaben. +documentation.context.outputs=Ausgรคnge +documentation.context.outputs.description=Von diesem Schritt oder Job bereitgestellte Ausgabewerte. +documentation.context.result=Ergebnis +documentation.context.result.description=Auftragsergebnis: Erfolg, Misserfolg, abgebrochen oder รผbersprungen. +documentation.context.outcome=Ergebnis +documentation.context.outcome.description=Schrittergebnis, bevor โ€žFortfahren bei Fehlerโ€œ angewendet wird. +documentation.context.conclusion=Fazit +documentation.context.conclusion.description=Schrittergebnis nach Anwendung der Fortsetzung bei Fehler. +error.report.action=Ausnahme melden +error.report.description=Beschreibung +error.report.steps=Schritte zum Reproduzieren +error.report.sample=Bitte stellen Sie ggf. ein Codebeispiel zur Verfรผgung +error.report.message=Nachricht +error.report.runtime=Laufzeitinformationen +error.report.pluginVersion=Plugin-Version: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=Stacktrace +completion.shell.bash=Bash-Shell. Verwendet Bash auf Linux- und macOS-Lรคufern und Git fรผr Windows-Bash auf Windows-Lรคufern. +completion.shell.sh=POSIX-Shell-Fallback. +completion.shell.pwsh=PowerShell-Kern. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Windows-Eingabeaufforderung. +completion.shell.python=Python-Befehlslรคufer. +completion.runner.name=Der Name des Runners, der den Job ausfรผhrt. +completion.runner.os=Das Betriebssystem des Lรคufers, der den Job ausfรผhrt. Mรถgliche Werte sind Linux, Windows oder macOS. +completion.runner.arch=Die Architektur des Runners, der den Job ausfรผhrt. Mรถgliche Werte sind X86, X64, ARM oder ARM64. +completion.runner.temp=Der Pfad zu einem temporรคren Verzeichnis auf dem Runner. Dieses Verzeichnis wird zu Beginn und am Ende jedes Jobs geleert. Beachten Sie, dass Dateien nicht entfernt werden, wenn das Benutzerkonto des Lรคufers nicht รผber die Berechtigung zum Lรถschen verfรผgt. +completion.runner.toolCache=Der Pfad zum Verzeichnis, das vorinstallierte Tools fรผr von GitHub gehostete Lรคufer enthรคlt. +completion.runner.debug=Dies wird nur festgelegt, wenn die Debug-Protokollierung aktiviert ist, und hat immer den Wert 1. +completion.runner.environment=Die Umgebung des Lรคufers, der den Job ausfรผhrt. Mรถgliche Werte sind โ€žgithub-hostedโ€œ oder โ€žself-hostedโ€œ. +completion.job.status=Der aktuelle Status des Jobs. +completion.job.checkRunId=Die Prรผflauf-ID des aktuellen Jobs. +completion.job.container=Informationen zum Container des Jobs. +completion.job.services=Die fรผr einen Job erstellten Servicecontainer. +completion.job.workflowRef=Die vollstรคndige Referenz der Workflowdatei, die den aktuellen Job definiert. +completion.job.workflowSha=Der Commit SHA der Workflowdatei, die den aktuellen Job definiert. +completion.job.workflowRepository=Der Eigentรผmer/Repository des Repositorys, das die Workflowdatei enthรคlt, die den aktuellen Job definiert. +completion.job.workflowFilePath=Der Workflow-Dateipfad relativ zum Repository-Stamm. +completion.job.containerField=Jobcontainerfeld +completion.job.service=Jobservice +completion.job.serviceField=Job-Service-Bereich +completion.job.mappedServicePort=Zugeordneter Service-Port +completion.strategy.failFast=Ob alle laufenden Jobs abgebrochen werden, wenn ein Matrixjob fehlschlรคgt. +completion.strategy.jobIndex=Der nullbasierte Index des aktuellen Jobs in der Matrix. +completion.strategy.jobTotal=Die Gesamtzahl der Jobs in der Matrix. +completion.strategy.maxParallel=Die maximale Anzahl von Matrixjobs, die gleichzeitig ausgefรผhrt werden kรถnnen. +completion.context.inputs=Workflow-Eingaben wie workflow_dispatch oder workflow_call. +completion.context.secrets=Workflow-Geheimnisse. +completion.context.job=Informationen zum aktuell ausgefรผhrten Job. +completion.context.jobs=Workflow-Jobs. +completion.context.matrix=Fรผr den aktuellen Matrixjob definierte Matrixeigenschaften. +completion.context.strategy=Informationen zur Matrixausfรผhrungsstrategie fรผr den aktuellen Job. +completion.context.steps=Schritte mit einer ID im aktuellen Job. +completion.context.env=Umgebungsvariablen von Jobs und Schritten. +completion.context.vars=Benutzerdefinierte Konfigurationsvariablen aus Organisations-, Repository- und Umgebungsbereichen. +completion.context.needs=Jobs, die abgeschlossen werden mรผssen, bevor dieser Job ausgefรผhrt werden kann, sowie deren Ausgaben und Ergebnisse. +completion.context.github=Workflow-Ausfรผhrungs- und Ereignisinformationen aus dem GitHub-Kontext. +completion.context.gitea=Gitea-kompatibler Alias fรผr den GitHub-Aktionskontext. +completion.context.runner=Informationen รผber den Runner, der den aktuellen Job ausfรผhrt. +completion.secret.githubToken=Automatisch erstelltes Token fรผr jeden Workflow-Lauf. +completion.remote.repository=Remote-Repository +completion.uses.local.workflow=Lokaler wiederverwendbarer Workflow +completion.uses.local.action=Lokale Aktion +completion.uses.ref.known=Bekannte Workflow-Referenz +completion.uses.ref.remote=Referenz zum Remote-Workflow +completion.uses.remote.known=Bekannte Remote-Aktion oder wiederverwendbarer Workflow +completion.workflow.syntax=GitHub Actions-Workflow-Syntax +completion.workflow.top.name=Anzeigename des Workflows +completion.workflow.top.run-name=Dynamischer Laufname +completion.workflow.top.on=Ereignisse, die den Workflow starten +completion.workflow.top.permissions=StandardmรครŸige GITHUB_TOKEN-Berechtigungen +completion.workflow.top.env=Workflowweite Umgebungsvariablen +completion.workflow.top.defaults=Standardeinstellungen fรผr Jobs und Schritte +completion.workflow.top.concurrency=Parallelitรคtsgruppe und Stornierung +completion.workflow.top.jobs=Jobs, die in diesem Workflow ausgefรผhrt werden +completion.workflow.event.branch_protection_rule=Zweigschutz geรคndert +completion.workflow.event.check_run=Einzelprรผflauf geรคndert +completion.workflow.event.check_suite=รœberprรผfen Sie, ob die Suite geรคndert wurde +completion.workflow.event.create=Zweig oder Tag erstellt +completion.workflow.event.delete=Zweig oder Tag gelรถscht +completion.workflow.event.deployment=Bereitstellung erstellt +completion.workflow.event.deployment_status=Bereitstellungsstatus geรคndert +completion.workflow.event.discussion=Diskussion geรคndert +completion.workflow.event.discussion_comment=Diskussionskommentar geรคndert +completion.workflow.event.fork=Repository gegabelt +completion.workflow.event.gollum=Wiki-Seite geรคndert +completion.workflow.event.image_version=Paket-Image-Version geรคndert +completion.workflow.event.issue_comment=Problem oder PR-Kommentar geรคndert +completion.workflow.event.issues=Problem geรคndert +completion.workflow.event.label=Beschriftung geรคndert +completion.workflow.event.merge_group=รœberprรผfung der Zusammenfรผhrungswarteschlange angefordert +completion.workflow.event.milestone=Meilenstein geรคndert +completion.workflow.event.page_build=Der Seitenaufbau wurde ausgefรผhrt +completion.workflow.event.project=Klassisches Projekt geรคndert +completion.workflow.event.project_card=Die klassische Projektkarte wurde geรคndert +completion.workflow.event.project_column=Die klassische Projektspalte wurde geรคndert +completion.workflow.event.public=Das Repository wurde รถffentlich +completion.workflow.event.pull_request=Pull-Anfrage geรคndert +completion.workflow.event.pull_request_review=PR-Rezension geรคndert +completion.workflow.event.pull_request_review_comment=Kommentar zur PR-Rezension geรคndert +completion.workflow.event.pull_request_target=PR-Zielkontext. Scharfe Messer. +completion.workflow.event.push=Commit oder Tag gepusht +completion.workflow.event.registry_package=Paket verรถffentlicht oder aktualisiert +completion.workflow.event.release=Verรถffentlichung geรคndert +completion.workflow.event.repository_dispatch=Benutzerdefiniertes API-Ereignis +completion.workflow.event.schedule=Cron-Zecke. Uhrwerk. +completion.workflow.event.status=Commit-Status geรคndert +completion.workflow.event.watch=Repository mit einem Stern versehen +completion.workflow.event.workflow_call=Wiederverwendbarer Workflow-Aufruf +completion.workflow.event.workflow_dispatch=Manueller Laufknopf +completion.workflow.event.workflow_run=Workflow-Lauf geรคndert +completion.workflow.eventFilter.types=Beschrรคnken Sie die Aktivitรคtsarten +completion.workflow.eventFilter.branches=Nur diese Zweige +completion.workflow.eventFilter.branches-ignore=รœberspringen Sie diese Zweige +completion.workflow.eventFilter.tags=Nur diese Tags +completion.workflow.eventFilter.tags-ignore=รœberspringen Sie diese Tags +completion.workflow.eventFilter.paths=Nur diese Wege +completion.workflow.eventFilter.paths-ignore=รœberspringen Sie diese Pfade +completion.workflow.eventFilter.workflows=Zu beobachtende Workflow-Namen +completion.workflow.eventFilter.cron=Cron-Zeitplan. Winziges Uhrwerk. +completion.workflow.permission.actions=Workflow-Ausfรผhrungen und Aktionsartefakte +completion.workflow.permission.artifact-metadata=Artefakt-Metadatensรคtze +completion.workflow.permission.attestations=Artefaktbescheinigungen +completion.workflow.permission.checks=รœberprรผfen Sie Lรคufe und Suiten +completion.workflow.permission.code-quality=Codequalitรคtsberichte +completion.workflow.permission.contents=Repository-Inhalte +completion.workflow.permission.deployments=Bereitstellungen +completion.workflow.permission.discussions=Diskussionen +completion.workflow.permission.id-token=OpenID Connect-Token +completion.workflow.permission.issues=Probleme +completion.workflow.permission.models=GitHub-Modelle +completion.workflow.permission.packages=GitHub-Pakete +completion.workflow.permission.pages=GitHub-Seiten +completion.workflow.permission.pull-requests=Pull-Anfragen +completion.workflow.permission.security-events=Code-Scanning und Sicherheitsereignisse +completion.workflow.permission.statuses=Commit-Status +completion.workflow.permission.vulnerability-alerts=Dependabot-Warnungen +completion.workflow.permission.value.read=Lesezugriff +completion.workflow.permission.value.write=Schreibzugriff, Lesen inklusive +completion.workflow.permission.value.none=Kein Zugriff +completion.workflow.permission.shorthand.read-all=Alle Berechtigungen gelesen. GroรŸe Decke. +completion.workflow.permission.shorthand.write-all=Alle Berechtigungen schreiben. GroรŸer Hammer. +completion.workflow.permission.shorthand.empty=Deaktivieren Sie Token-Berechtigungen +completion.workflow.job.name=Job-Anzeigename +completion.workflow.job.permissions=Job-Token-Berechtigungen +completion.workflow.job.needs=Jobs, auf die man warten muss +completion.workflow.job.if=Arbeitsbedingung +completion.workflow.job.runs-on=Lรคuferbezeichnung oder -gruppe +completion.workflow.job.snapshot=Lรคufer-Schnappschuss +completion.workflow.job.environment=Bereitstellungsumgebung +completion.workflow.job.concurrency=Job-Parallelitรคtssperre +completion.workflow.job.outputs=Gibt andere Jobs aus, die gelesen werden kรถnnen +completion.workflow.job.env=Jobumgebungsvariablen +completion.workflow.job.defaults=Job-Standardeinstellungen +completion.workflow.job.steps=Schrittliste. Die eigentliche Arbeit. +completion.workflow.job.timeout-minutes=Job-Timeout in Minuten +completion.workflow.job.strategy=Matrix- und Planungsstrategie +completion.workflow.job.continue-on-error=Lassen Sie diesen Job sanft scheitern +completion.workflow.job.container=Container fรผr diesen Job +completion.workflow.job.services=Beiwagen-Servicecontainer +completion.workflow.job.uses=Wiederverwendbarer Workflow zum Aufrufen +completion.workflow.job.with=Eingaben fรผr den aufgerufenen Workflow +completion.workflow.job.secrets=Geheimnisse fรผr den aufgerufenen Workflow +completion.workflow.defaultsRun.shell=Standard-Shell fรผr Ausfรผhrungsschritte +completion.workflow.defaultsRun.working-directory=Standardarbeitsverzeichnis +completion.workflow.concurrency.group=Sperrname fรผr Ausfรผhrungen in der Warteschlange +completion.workflow.concurrency.cancel-in-progress=Brechen Sie รคltere Matching-Lรคufe ab +completion.workflow.environment.name=Umgebungsname +completion.workflow.environment.url=Umgebung URL +completion.workflow.strategy.matrix=Matrixachsen und Varianten +completion.workflow.strategy.fail-fast=Matrix-Geschwister bei Fehler stornieren +completion.workflow.strategy.max-parallel=Matrixparallelitรคtskappe +completion.workflow.matrix.include=Fรผgen Sie Matrixkombinationen hinzu +completion.workflow.matrix.exclude=Matrixkombinationen entfernen +completion.workflow.step.id=Schritt-ID fรผr Referenzen +completion.workflow.step.if=Schrittbedingung +completion.workflow.step.name=Anzeigename des Schritts +completion.workflow.step.uses=Aktion zum Ausfรผhren +completion.workflow.step.run=Shell-Skript zum Ausfรผhren +completion.workflow.step.shell=Shell fรผr diesen Ausfรผhrungsschritt +completion.workflow.step.with=Aktionseingaben +completion.workflow.step.env=Schrittumgebungsvariablen +completion.workflow.step.continue-on-error=Lassen Sie diesen Schritt sanft scheitern +completion.workflow.step.timeout-minutes=Schritt-Timeout in Minuten +completion.workflow.step.working-directory=Step-Arbeitsverzeichnis +completion.workflow.container.image=Containerbild +completion.workflow.container.credentials=Registrierungsdaten +completion.workflow.container.env=Container-Umgebungsvariablen +completion.workflow.container.ports=Offenzulegende Ports +completion.workflow.container.volumes=Zu mountende Volumes +completion.workflow.container.options=Docker-Erstellungsoptionen +completion.workflow.service.image=Service-Container-Image +completion.workflow.service.credentials=Registrierungsdaten +completion.workflow.service.env=Dienstumgebungsvariablen +completion.workflow.service.ports=Service-Ports +completion.workflow.service.volumes=Servicemengen +completion.workflow.service.options=Docker-Erstellungsoptionen +completion.workflow.credentials.username=Registrierungsbenutzername +completion.workflow.credentials.password=Registrierungspasswort oder Token +completion.workflow.inputType.string=Texteingabe +completion.workflow.inputType.boolean=Wahre oder falsche Eingabe +completion.workflow.inputType.choice=Dropdown-Auswahleingabe +completion.workflow.inputType.number=Zahleneingabe +completion.workflow.inputType.environment=Eingabe der Umgebungsauswahl +completion.workflow.boolean.true=Ja. Schalten Sie es ein. +completion.workflow.boolean.false=Nein. Halten Sie es dunkel. +completion.workflow.runner.ubuntu-latest=Neuester Ubuntu-Lรคufer +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 Lรคufer +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 Lรคufer +completion.workflow.runner.windows-latest=Neuester Windows-Lรคufer +completion.workflow.runner.windows-2025=Windows Server 2025-Lรคufer +completion.workflow.runner.windows-2022=Windows Server 2022-Lรคufer +completion.workflow.runner.macos-latest=Neuester macOS-Lรคufer +completion.workflow.runner.macos-15=macOS 15 Lรคufer +completion.workflow.runner.macos-14=macOS 14 Lรคufer +completion.workflow.runner.self-hosted=Dein eigener Lรคufer. Dein Zirkus. +completion.steps.outputs=Der fรผr den Schritt definierte Satz von Ausgaben. +completion.steps.conclusion=Das Ergebnis eines abgeschlossenen Schritts nach der Fortsetzung bei Fehler wird angewendet. +completion.steps.outcome=Das Ergebnis eines abgeschlossenen Schritts, bevor โ€žContinue-on-Errorโ€œ angewendet wird. +completion.jobs.outputs=Der fรผr den Job definierte Satz von Ausgaben. +completion.jobs.result=Das Ergebnis der Arbeit. +settings.displayName=GitHub-Workflow +settings.language.label=Sprache: +settings.language.system=IDE/Systemstandard +settings.cache.title=Aktionscache +settings.cache.column.key=Cache-Schlรผssel +settings.cache.column.name=Bezeichnung +settings.cache.column.kind=Freundlich +settings.cache.column.state=Staat +settings.cache.column.expires=Lรคuft ab +settings.cache.kind.local=lokal +settings.cache.kind.remote=entfernt +settings.cache.state.resolved=gelรถst +settings.cache.state.pending=ausstehend +settings.cache.state.expired=abgestanden +settings.cache.state.suppressed=unterdrรผckt +settings.cache.refresh=Refresh-Tabelle +settings.cache.deleteSelected=Ausgewรคhlte lรถschen +settings.cache.deleteAll=Alles lรถschen +settings.cache.export=Exportieren +settings.cache.import=Importieren +settings.cache.summary=Cache: {0}-Eintrรคge, {1} aufgelรถst, {2} remote, {3} veraltet, {4} stummgeschaltet. Cache: {5} KB. +settings.cache.noneSelected=Wรคhlen Sie zuerst Cache-Zeilen aus. Der Besen verweigert Rรคtselraten. +settings.cache.deleteSelected.done={0}-Cache-Eintrรคge gelรถscht. Winzige Staubwolke enthalten. +settings.cache.deleteAll.confirm=Alle GitHub-Workflow-Cache-Eintrรคge lรถschen? +settings.cache.deleteAll.done=Alle Cache-Eintrรคge gelรถscht. Der Cache ist jetzt verdรคchtig ruhig. +settings.cache.export.done=Exportierte {0}-Cache-Eintrรคge. Kleines Archivtier im Kรคfig. +settings.cache.import.done=Importierte Cache-Eintrรคge. Das Archivbiest benahm sich. +settings.cache.import.unsupported=Nicht unterstรผtzte GitHub-Workflow-Cache-Datei. +settings.cache.import.brokenLine=Defekte GitHub-Workflow-Cache-Zeile. +settings.cache.import.brokenKey=Defekter GitHub-Workflow-Cache-Schlรผssel. +settings.support.button=Unterstรผtzen Sie dieses Plugin +settings.support.tooltip=ร–ffnen Sie die Support-Seite +settings.support.line.0=Beschicken Sie den Bauofen +settings.support.line.1=Kaufen Sie den Parser-Kaffee +settings.support.line.2=Fรถrdern Sie weniger hektische Arbeitsablรคufe +workflow.run.jobs.title=Workflow-Jobs +workflow.run.jobs.root=Workflow-Ausfรผhrung +workflow.run.jobs.description=GitHub Aktionen Jobbaum und ausgewรคhltes Jobprotokoll +workflow.run.tree.done=erledigt +workflow.run.tree.failed=fehlgeschlagen +workflow.run.tree.skipped=รผbersprungen +workflow.run.tree.warn=warnen +workflow.run.tree.err=รคhm +workflow.run.delete.tooltip=Lauf lรถschen +workflow.run.delete.noRun=Noch keine Lauf-ID. +workflow.run.delete.requested=Lรถschlauf {0}. +workflow.run.delete.done=Fรผhren Sie {0} gelรถscht aus. +workflow.run.delete.http=Lรถschen Sie HTTP {0}. Uff. +workflow.run.delete.failed=Lรถschen verpufft: {0} +workflow.run.rerun.all.tooltip=Fรผhren Sie den Workflow erneut aus +workflow.run.rerun.failed.tooltip=Fรผhren Sie fehlgeschlagene Jobs erneut aus +workflow.run.rerun.noRun=Noch keine Lauf-ID. +workflow.run.rerun.all.requested=Wiederholung angefordert: {0}. +workflow.run.rerun.failed.requested=Angeforderte fehlgeschlagene Jobs: {0}. +workflow.run.rerun.all.done=Wiederholung in der Warteschlange: {0}. +workflow.run.rerun.failed.done=Fehlgeschlagene Jobs in der Warteschlange: {0}. +workflow.run.rerun.http=Fรผhren Sie HTTP {0} erneut aus. Uff. +workflow.run.rerun.failed=Wiederholung scheiterte: {0} +workflow.run.download.log.tooltip=Jobprotokoll speichern +workflow.run.download.artifacts.tooltip=Laden Sie Artefakte herunter +workflow.run.download.noRun=Noch keine Lauf-ID. +workflow.run.download.log.requested=Protokoll fรผr {0} abrufen. +workflow.run.download.log.done=Protokoll gespeichert: {0}. +workflow.run.download.artifacts.requested=Artefakte abrufen. +workflow.run.download.artifacts.empty=Keine Artefakte. Winzige Leere. +workflow.run.download.artifact.expired=Artefakt abgelaufen: {0}. +workflow.run.download.artifact.done=Artefakt gespeichert: {0} -> {1}. +workflow.run.download.failed=Download fehlgeschlagen: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_es.properties b/src/main/resources/messages/GitHubWorkflowBundle_es.properties new file mode 100644 index 0000000..1a47e7b --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_es.properties @@ -0,0 +1,442 @@ +plugin.name=Flujo de trabajo GitHub +plugin.description=Compatibilidad con archivos de flujo de trabajo de acciones GitHub +group.GitHubWorkflow.Tools.text=Flujo de trabajo GitHub +group.GitHubWorkflow.Tools.description=Herramientas complementarias de flujo de trabajo GitHub +action.GitHubWorkflow.RefreshActionCache.text=Refresh cachรฉ de acciones +action.GitHubWorkflow.RefreshActionCache.description=Refresh resolviรณ acciones GitHub remotas y metadatos de flujo de trabajo reutilizables +action.GitHubWorkflow.RestoreActionWarnings.text=Restaurar advertencias de acciรณn +action.GitHubWorkflow.RestoreActionWarnings.description=Restaurar advertencias de validaciรณn de acciones, entradas y salidas suprimidas +action.GitHubWorkflow.ClearActionCache.text=Borrar cachรฉ de acciones +action.GitHubWorkflow.ClearActionCache.description=Borrar acciones GitHub almacenadas en cachรฉ y metadatos de flujo de trabajo reutilizables +notification.cache.cleared=Se borraron las entradas del flujo de trabajo GitHub en cachรฉ de {0}. +notification.cache.refresh.started=Refreshing {0} almacena en cachรฉ las entradas remotas del flujo de trabajo GitHub. +notification.warnings.restored=Advertencias restauradas para entradas de flujo de trabajo {0} GitHub. +workflow.run.configuration.display=Flujo de trabajo GitHub +workflow.run.configuration.description=Distribuir y seguir las ejecuciones del flujo de trabajo de acciones GitHub +workflow.run.configuration.name=Flujo de trabajo GitHub: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=propietario +workflow.run.field.repo=Repositorio +workflow.run.field.workflow=Archivo de flujo de trabajo +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Reserva de var de entorno de token +workflow.run.inputs.title=Entradas workflow_dispatch (clave=valor) +workflow.run.error.apiUrl=Se requiere GitHub API URL. +workflow.run.error.repository=Se requieren el propietario y el nombre del repositorio GitHub. +workflow.run.error.workflow=Se requiere un archivo de flujo de trabajo. +workflow.run.error.ref=Se requiere referencia de sucursal o etiqueta. +workflow.run.error.inputs=GitHub workflow_dispatch admite como mรกximo 25 entradas. +workflow.run.gutter.stop=Detener la ejecuciรณn del flujo de trabajo +workflow.run.gutter.stop.text=Detener la ejecuciรณn del flujo de trabajo +workflow.run.gutter.stop.description=Cancelar esta ejecuciรณn +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=ejecutar: +workflow.log.warning=advertencia: +workflow.log.error=error: +workflow.run.cancel.requested=Cancelaciรณn solicitada: {0}. +workflow.run.stop.before.id=Parada solicitada. Aรบn no hay identificaciรณn de ejecuciรณn. +workflow.run.cancel.http=Cancelar HTTP {0}. Uf. +workflow.run.cancel.failed=Cancelar fallรณ: {0} +workflow.run.interrupted=Interrumpido. +workflow.run.link=Ejecutar: {0} +workflow.run.discovery=Ejecutar aceptado. Identificaciรณn de carrera de caza. +workflow.run.discovery.none=Aรบn no hay identificaciรณn de ejecuciรณn. La pestaรฑa Acciones sabe mรกs. +workflow.run.status=Estado: {0}{1} +workflow.run.job.main=Trabajo: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Estado: {0} {1}{2}{3} +workflow.run.logs.later=Los registros aparecerรกn cuando GitHub los publique. +workflow.run.job.logs.later=Trabajo {0}: {1} +workflow.run.log.failed=Fallรณ la descarga del registro: {0} +workflow.run.log.failed.job=Error al descargar el registro para {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Trabajo: {0} +workflow.run.job.fallbackName=Trabajo {0} +workflow.run.overview=Ejecuciรณn del flujo de trabajo {0} {1}/{2} finalizada, {3} en ejecuciรณn +workflow.run.state.ok=[OK] +workflow.run.state.fail=[FALLO] +workflow.run.state.running=[EJECUTAR] +workflow.run.state.waiting=[ESPERAR] +workflow.run.dispatch.verbs=Preparaciรณn|Cola|Invocaciรณn|Lanzamiento|Arranque +workflow.run.dispatch.objects=flujo de trabajo|automatizaciรณn|canalizaciรณn|ejecuciรณn +workflow.run.dispatch={0} {1} {2}{3} para {4} en {5}. +workflow.run.notification.auth=El envรญo del flujo de trabajo GitHub necesita una cuenta GitHub autenticada. Agregue o actualice cuentas en {0}. +workflow.run.notification.openSettings=Abra la configuraciรณn de GitHub +workflow.cache.progress.title=Resolviendo acciones GitHub +workflow.cache.progress.text=Resolviendo {0} {1} +workflow.cache.kind.action=acciรณn +workflow.cache.kind.workflow=flujo de trabajo +inspection.parameter.input=entrada +inspection.parameter.secret=secreto +inspection.action.delete.invalid=Eliminar {0} [{1}] no vรกlido +inspection.action.update.major=Actualizar acciรณn [{0}] a [{1}] +inspection.warning.toggle=Alternar advertencias [{0}] para [{1}] +inspection.warning.on=en +inspection.warning.off=apagado +inspection.statement.incomplete=Declaraciรณn incompleta [{0}] +inspection.invalid.suffix.remove=Eliminar el sufijo no vรกlido [{0}] +inspection.replace.with=Reemplazar con [{0}] +inspection.invalid.remove=Eliminar no vรกlido [{0}] +inspection.workflow.syntax.unknownTopLevelKey=Clave de flujo de trabajo desconocida [{0}] +inspection.workflow.syntax.unknownEventKey=Evento de flujo de trabajo desconocido [{0}] +inspection.workflow.syntax.unknownTriggerKey=Tecla de activaciรณn desconocida [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Filtro de activaciรณn desconocido [{0}] +inspection.workflow.syntax.unknownTriggerValue=Valor de activaciรณn desconocido [{0}] +inspection.workflow.syntax.unknownPermission=Permiso desconocido [{0}] +inspection.workflow.syntax.unknownPermissionValue=Valor de permiso desconocido [{0}] +inspection.workflow.syntax.unknownJobKey=Clave de trabajo desconocida [{0}] +inspection.workflow.syntax.unknownStepKey=Tecla de paso desconocida [{0}] +inspection.action.reload=Recargar [{0}] +inspection.action.unresolved=Sin resolver [{0}]: verifique el acceso a la cuenta GitHub, los permisos del repositorio privado, los lรญmites de velocidad, las referencias faltantes o los metadatos de acciรณn/flujo de trabajo faltantes +inspection.action.jump=Saltar al archivo [{0}] +inspection.output.unused=Sin usar [{0}] +inspection.secret.invalid.if=Eliminar [{0}] - Los secretos no son vรกlidos en declaraciones "if" +inspection.secret.replace.runtime=Reemplace [{0}] con [{1}] - si no se proporciona en tiempo de ejecuciรณn +inspection.needs.invalid.job=Eliminar el ID de trabajo no vรกlido [{0}]: este ID de trabajo no coincide con ningรบn trabajo anterior +documentation.description=Descripciรณn: {0} +documentation.type=Tipo: {0} +documentation.required=Requerido: {0} +documentation.default=Valor predeterminado: {0} +documentation.deprecated=En desuso: {0} +documentation.open.declaration=Declaraciรณn abierta ({0}) +documentation.input.label=Entrada +documentation.secret.label=secreto +documentation.env.label=variable de entorno +documentation.matrix.label=Propiedad de matriz +documentation.need.label=trabajo necesario +documentation.need.description=Dependencia laboral directa. +documentation.needOutput.label=Salida de trabajo necesaria +documentation.reusableJob.label=Trabajo de flujo de trabajo reutilizable +documentation.reusableJob.description=Trabajo declarado en este flujo de trabajo reutilizable. +documentation.reusableJobOutput.label=Salida de trabajo de flujo de trabajo reutilizable +documentation.service.label=Contenedor de servicio +documentation.servicePort.label=Puerto de servicio +documentation.container.label=contenedor de trabajo +documentation.symbol.label=Sรญmbolo de flujo de trabajo +documentation.symbol.description=Expresiรณn de flujo de trabajo resuelta. +documentation.workflowOutput.label=Salida del flujo de trabajo +documentation.jobOutput.label=Salida del trabajo +documentation.action.label=acciรณn +documentation.externalAction.label=Acciรณn exterior +documentation.reusableWorkflow.label=Flujo de trabajo reutilizable +documentation.resolvedFrom=resuelto desde {0} +documentation.notResolved=aรบn no resuelto +documentation.inputs.title=Entradas +documentation.outputs.title=Salidas +documentation.secrets.title=Secretos +documentation.value.label=Valor +documentation.step.title=Paso {0} +documentation.name.label=Nombre +documentation.uses.label=Usos +documentation.run.label=correr +documentation.description.label=Descripciรณn +documentation.step.label=paso +documentation.source.label=Fuente +documentation.stepOutput.label=Salida de paso +documentation.context.github=contexto de github +documentation.context.github.description=Informaciรณn sobre la ejecuciรณn y el evento del flujo de trabajo actual. +documentation.context.gitea=contexto gitea +documentation.context.gitea.description=Alias compatible con Gitea para el contexto de acciones GitHub. +documentation.context.inputs=contexto de entradas +documentation.context.inputs.description=Entradas de flujo de trabajo, despacho o acciรณn disponibles aquรญ. +documentation.context.secrets=contexto secretos +documentation.context.secrets.description=Valores secretos disponibles para este flujo de trabajo o llamada de flujo de trabajo reutilizable. +documentation.context.env=contexto ambiental +documentation.context.env.description=Variables de entorno visibles en esta ubicaciรณn. +documentation.context.matrix=contexto matricial +documentation.context.matrix.description=Valores de matriz para el trabajo actual. +documentation.context.steps=contexto de pasos +documentation.context.steps.description=Pasos anteriores del trabajo actual, incluidos los resultados y el estado. +documentation.context.needs=necesita contexto +documentation.context.needs.description=Dependencias laborales directas y sus productos/resultados. +documentation.context.jobs=contexto laboral +documentation.context.jobs.description=Trabajos y resultados de flujo de trabajo reutilizables. +documentation.context.outputs=salidas +documentation.context.outputs.description=Valores de salida expuestos por este paso o trabajo. +documentation.context.result=resultado +documentation.context.result.description=Resultado del trabajo: รฉxito, fracaso, cancelado u omitido. +documentation.context.outcome=resultado +documentation.context.outcome.description=Resultado del paso antes de que se aplique continuar en caso de error. +documentation.context.conclusion=conclusiรณn +documentation.context.conclusion.description=Resultado del paso despuรฉs de aplicar continuar en caso de error. +error.report.action=Informe de excepciรณn +error.report.description=Descripciรณn +error.report.steps=Pasos para reproducirse +error.report.sample=Proporcione un ejemplo de cรณdigo si corresponde +error.report.message=Mensaje +error.report.runtime=Informaciรณn de tiempo de ejecuciรณn +error.report.pluginVersion=Versiรณn del complemento: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=seguimiento de pila +completion.shell.bash=Carcasa Bash. Utiliza bash en ejecutores Linux y macOS, y Git para bash Windows en ejecutores Windows. +completion.shell.sh=Reserva de shell POSIX. +completion.shell.pwsh=Nรบcleo PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Sรญmbolo del sistema Windows. +completion.shell.python=Ejecutor de comandos Python. +completion.runner.name=El nombre del corredor que ejecuta el trabajo. +completion.runner.os=El sistema operativo del corredor que ejecuta el trabajo. Los valores posibles son Linux, Windows o macOS. +completion.runner.arch=La arquitectura del corredor que ejecuta el trabajo. Los valores posibles son X86, X64, ARM o ARM64. +completion.runner.temp=La ruta a un directorio temporal en el ejecutor. Este directorio se vacรญa al principio y al final de cada trabajo. Tenga en cuenta que los archivos no se eliminarรกn si la cuenta de usuario del corredor no tiene permiso para eliminarlos. +completion.runner.toolCache=La ruta al directorio que contiene las herramientas preinstaladas para los ejecutores alojados en GitHub. +completion.runner.debug=Esto se establece solo si el registro de depuraciรณn estรก habilitado y siempre tiene el valor de 1. +completion.runner.environment=El entorno del corredor que ejecuta el trabajo. Los valores posibles estรกn alojados en github o autohospedados. +completion.job.status=El estado actual del trabajo. +completion.job.checkRunId=El ID de ejecuciรณn de verificaciรณn del trabajo actual. +completion.job.container=Informaciรณn sobre el contenedor del trabajo. +completion.job.services=Los contenedores de servicios creados para un trabajo. +completion.job.workflowRef=La referencia completa del archivo de flujo de trabajo que define el trabajo actual. +completion.job.workflowSha=La confirmaciรณn SHA del archivo de flujo de trabajo que define el trabajo actual. +completion.job.workflowRepository=El propietario/repositorio del repositorio que contiene el archivo de flujo de trabajo que define el trabajo actual. +completion.job.workflowFilePath=La ruta del archivo de flujo de trabajo, relativa a la raรญz del repositorio. +completion.job.containerField=Campo contenedor de trabajo +completion.job.service=servicio de empleo +completion.job.serviceField=campo de servicio de trabajo +completion.job.mappedServicePort=Puerto de servicio mapeado +completion.strategy.failFast=Si todos los trabajos en curso se cancelan si falla algรบn trabajo de matriz. +completion.strategy.jobIndex=El รญndice de base cero del trabajo actual en la matriz. +completion.strategy.jobTotal=El nรบmero total de trabajos en la matriz. +completion.strategy.maxParallel=El nรบmero mรกximo de trabajos matriciales que se pueden ejecutar simultรกneamente. +completion.context.inputs=Entradas de flujo de trabajo, como workflow_dispatch o workflow_call. +completion.context.secrets=Secretos del flujo de trabajo. +completion.context.job=Informaciรณn sobre el trabajo actualmente en ejecuciรณn. +completion.context.jobs=Trabajos de flujo de trabajo. +completion.context.matrix=Propiedades de matriz definidas para el trabajo de matriz actual. +completion.context.strategy=Informaciรณn de la estrategia de ejecuciรณn de la matriz para el trabajo actual. +completion.context.steps=Pasos con una identificaciรณn en el trabajo actual. +completion.context.env=Variables de entorno de trabajos y pasos. +completion.context.vars=Variables de configuraciรณn personalizadas de los รกmbitos de organizaciรณn, repositorio y entorno. +completion.context.needs=Trabajos que deben completarse antes de que este trabajo pueda ejecutarse, ademรกs de sus salidas y resultados. +completion.context.github=Informaciรณn de eventos y ejecuciรณn del flujo de trabajo del contexto GitHub. +completion.context.gitea=Alias compatible con Gitea para el contexto de acciones GitHub. +completion.context.runner=Informaciรณn sobre el corredor que ejecuta el trabajo actual. +completion.secret.githubToken=Token creado automรกticamente para cada ejecuciรณn de flujo de trabajo. +completion.remote.repository=repositorio remoto +completion.uses.local.workflow=Flujo de trabajo local reutilizable +completion.uses.local.action=Acciรณn local +completion.uses.ref.known=Referencia de flujo de trabajo conocida +completion.uses.ref.remote=Referencia de flujo de trabajo remoto +completion.uses.remote.known=Acciรณn remota conocida o flujo de trabajo reutilizable +completion.workflow.syntax=Sintaxis del flujo de trabajo de acciones GitHub +completion.workflow.top.name=Nombre para mostrar del flujo de trabajo +completion.workflow.top.run-name=Nombre de ejecuciรณn dinรกmica +completion.workflow.top.on=Eventos que inician el flujo de trabajo +completion.workflow.top.permissions=Permisos GITHUB_TOKEN predeterminados +completion.workflow.top.env=Variables de entorno de todo el flujo de trabajo +completion.workflow.top.defaults=Configuraciรณn predeterminada de trabajos y pasos +completion.workflow.top.concurrency=Grupo de concurrencia y cancelaciรณn. +completion.workflow.top.jobs=Trabajos que se ejecutan en este flujo de trabajo +completion.workflow.event.branch_protection_rule=La protecciรณn de sucursales cambiรณ +completion.workflow.event.check_run=Se cambiรณ la ejecuciรณn de verificaciรณn รบnica +completion.workflow.event.check_suite=Conjunto de cheques cambiado +completion.workflow.event.create=Rama o etiqueta creada +completion.workflow.event.delete=Rama o etiqueta eliminada +completion.workflow.event.deployment=Implementaciรณn creada +completion.workflow.event.deployment_status=El estado de implementaciรณn cambiรณ +completion.workflow.event.discussion=La discusiรณn cambiรณ +completion.workflow.event.discussion_comment=El comentario de la discusiรณn cambiรณ. +completion.workflow.event.fork=Repositorio bifurcado +completion.workflow.event.gollum=La pรกgina wiki cambiรณ +completion.workflow.event.image_version=La versiรณn de la imagen del paquete cambiรณ +completion.workflow.event.issue_comment=Problema o comentario PR cambiado +completion.workflow.event.issues=Problema cambiado +completion.workflow.event.label=Etiqueta cambiada +completion.workflow.event.merge_group=Se solicitรณ verificaciรณn de cola de fusiรณn +completion.workflow.event.milestone=Hito cambiado +completion.workflow.event.page_build=Se ejecutรณ la creaciรณn de pรกginas +completion.workflow.event.project=Proyecto clรกsico cambiado +completion.workflow.event.project_card=Tarjeta de proyecto clรกsica cambiada +completion.workflow.event.project_column=La columna del proyecto clรกsico cambiรณ +completion.workflow.event.public=El repositorio se hizo pรบblico +completion.workflow.event.pull_request=La solicitud de extracciรณn cambiรณ +completion.workflow.event.pull_request_review=Revisiรณn de PR cambiada +completion.workflow.event.pull_request_review_comment=El comentario de revisiรณn de PR cambiรณ +completion.workflow.event.pull_request_target=Contexto de destino PR. Cuchillos afilados. +completion.workflow.event.push=Confirmar o etiquetar empujado +completion.workflow.event.registry_package=Paquete publicado o actualizado +completion.workflow.event.release=Lanzamiento cambiado +completion.workflow.event.repository_dispatch=Evento API personalizado +completion.workflow.event.schedule=tic cron. Aparato de relojerรญa. +completion.workflow.event.status=El estado de confirmaciรณn cambiรณ +completion.workflow.event.watch=Repositorio destacado +completion.workflow.event.workflow_call=Llamada de flujo de trabajo reutilizable +completion.workflow.event.workflow_dispatch=Botรณn de ejecuciรณn manual +completion.workflow.event.workflow_run=La ejecuciรณn del flujo de trabajo cambiรณ +completion.workflow.eventFilter.types=Limitar tipos de actividad +completion.workflow.eventFilter.branches=Sรณlo estas sucursales +completion.workflow.eventFilter.branches-ignore=Salta estas ramas +completion.workflow.eventFilter.tags=Sรณlo estas etiquetas +completion.workflow.eventFilter.tags-ignore=Salta estas etiquetas +completion.workflow.eventFilter.paths=Sรณlo estos caminos +completion.workflow.eventFilter.paths-ignore=Salta estos caminos +completion.workflow.eventFilter.workflows=Nombres de flujos de trabajo para observar +completion.workflow.eventFilter.cron=Horario cron. Pequeรฑo mecanismo de relojerรญa. +completion.workflow.permission.actions=Ejecuciones de flujo de trabajo y artefactos de acciรณn +completion.workflow.permission.artifact-metadata=Registros de metadatos de artefactos +completion.workflow.permission.attestations=Certificaciones de artefactos +completion.workflow.permission.checks=Consulta carreras y suites. +completion.workflow.permission.code-quality=Informes de calidad del cรณdigo +completion.workflow.permission.contents=Contenidos del repositorio +completion.workflow.permission.deployments=Implementaciones +completion.workflow.permission.discussions=Discusiones +completion.workflow.permission.id-token=Fichas OpenID Connect +completion.workflow.permission.issues=Problemas +completion.workflow.permission.models=Modelos GitHub +completion.workflow.permission.packages=Paquetes GitHub +completion.workflow.permission.pages=Pรกginas GitHub +completion.workflow.permission.pull-requests=Solicitudes de extracciรณn +completion.workflow.permission.security-events=Escaneo de cรณdigo y eventos de seguridad +completion.workflow.permission.statuses=estados de confirmaciรณn +completion.workflow.permission.vulnerability-alerts=Alertas Dependabot +completion.workflow.permission.value.read=Acceso de lectura +completion.workflow.permission.value.write=Acceso de escritura, lectura incluida. +completion.workflow.permission.value.none=Sin acceso +completion.workflow.permission.shorthand.read-all=Todos los permisos leรญdos. Gran manta. +completion.workflow.permission.shorthand.write-all=Todos los permisos escriben. Gran martillo. +completion.workflow.permission.shorthand.empty=Deshabilitar permisos de tokens +completion.workflow.job.name=Nombre para mostrar del trabajo +completion.workflow.job.permissions=Permisos de token de trabajo +completion.workflow.job.needs=Trabajos que esperar +completion.workflow.job.if=condiciรณn de trabajo +completion.workflow.job.runs-on=Etiqueta o grupo de corredor +completion.workflow.job.snapshot=Instantรกnea del corredor +completion.workflow.job.environment=Entorno de implementaciรณn +completion.workflow.job.concurrency=Bloqueo de simultaneidad de trabajos +completion.workflow.job.outputs=Salidas que otros trabajos pueden leer +completion.workflow.job.env=Variables del entorno laboral +completion.workflow.job.defaults=Configuraciรณn predeterminada del trabajo +completion.workflow.job.steps=Lista de pasos. El trabajo real. +completion.workflow.job.timeout-minutes=Tiempo de espera del trabajo en minutos +completion.workflow.job.strategy=Matriz y estrategia de programaciรณn. +completion.workflow.job.continue-on-error=Deja que este trabajo fracase suavemente +completion.workflow.job.container=Contenedor para este trabajo +completion.workflow.job.services=Contenedores de servicio con sidecar +completion.workflow.job.uses=Flujo de trabajo reutilizable para llamar +completion.workflow.job.with=Entradas para el flujo de trabajo llamado +completion.workflow.job.secrets=Secretos para el flujo de trabajo llamado +completion.workflow.defaultsRun.shell=Shell predeterminado para los pasos de ejecuciรณn +completion.workflow.defaultsRun.working-directory=Directorio de trabajo predeterminado +completion.workflow.concurrency.group=Bloquear nombre para ejecuciones en cola +completion.workflow.concurrency.cancel-in-progress=Cancelar ejecuciones coincidentes anteriores +completion.workflow.environment.name=Nombre del entorno +completion.workflow.environment.url=Entorno URL +completion.workflow.strategy.matrix=Ejes matriciales y variantes. +completion.workflow.strategy.fail-fast=Cancelar hermanos de matriz en caso de falla +completion.workflow.strategy.max-parallel=Lรญmite de paralelismo matricial +completion.workflow.matrix.include=Agregar combinaciones de matrices +completion.workflow.matrix.exclude=Eliminar combinaciones de matrices +completion.workflow.step.id=ID de paso para referencias +completion.workflow.step.if=Condiciรณn de paso +completion.workflow.step.name=Nombre para mostrar del paso +completion.workflow.step.uses=Acciรณn para ejecutar +completion.workflow.step.run=Script de Shell para ejecutar +completion.workflow.step.shell=Shell para este paso de ejecuciรณn +completion.workflow.step.with=Entradas de acciรณn +completion.workflow.step.env=Variables de entorno de paso +completion.workflow.step.continue-on-error=Deja que este paso falle suavemente. +completion.workflow.step.timeout-minutes=Tiempo de espera del paso en minutos +completion.workflow.step.working-directory=Paso directorio de trabajo +completion.workflow.container.image=Imagen del contenedor +completion.workflow.container.credentials=Credenciales de registro +completion.workflow.container.env=Variables de entorno del contenedor +completion.workflow.container.ports=Puertos para exponer +completion.workflow.container.volumes=Volรบmenes para montar +completion.workflow.container.options=Docker crear opciones +completion.workflow.service.image=Imagen del contenedor de servicios +completion.workflow.service.credentials=Credenciales de registro +completion.workflow.service.env=Variables de entorno de servicio +completion.workflow.service.ports=Puertos de servicio +completion.workflow.service.volumes=Volรบmenes de servicio +completion.workflow.service.options=Docker crear opciones +completion.workflow.credentials.username=Nombre de usuario del registro +completion.workflow.credentials.password=Contraseรฑa o token de registro +completion.workflow.inputType.string=Entrada de texto +completion.workflow.inputType.boolean=Entrada verdadera o falsa +completion.workflow.inputType.choice=Entrada de elecciรณn desplegable +completion.workflow.inputType.number=Entrada de nรบmeros +completion.workflow.inputType.environment=Entrada del selector de entorno +completion.workflow.boolean.true=Sรญ. Enciรฉndelo. +completion.workflow.boolean.false=No. Mantenlo oscuro. +completion.workflow.runner.ubuntu-latest=รšltimo corredor Ubuntu +completion.workflow.runner.ubuntu-24.04=Corredor Ubuntu 24.04 +completion.workflow.runner.ubuntu-22.04=Corredor Ubuntu 22.04 +completion.workflow.runner.windows-latest=รšltimo corredor Windows +completion.workflow.runner.windows-2025=Corredor Windows Servidor 2025 +completion.workflow.runner.windows-2022=Corredor Windows Servidor 2022 +completion.workflow.runner.macos-latest=รšltimo corredor macOS +completion.workflow.runner.macos-15=Corredor macOS 15 +completion.workflow.runner.macos-14=Corredor macOS 14 +completion.workflow.runner.self-hosted=Tu propio corredor. Tu circo. +completion.steps.outputs=El conjunto de salidas definidas para el paso. +completion.steps.conclusion=Se aplica el resultado de un paso completado despuรฉs de continuar en caso de error. +completion.steps.outcome=Se aplica el resultado de un paso completado antes de continuar en caso de error. +completion.jobs.outputs=El conjunto de salidas definidas para el trabajo. +completion.jobs.result=El resultado del trabajo. +settings.displayName=Flujo de trabajo GitHub +settings.language.label=Idioma: +settings.language.system=IDE/valor predeterminado del sistema +settings.cache.title=Cachรฉ de acciones +settings.cache.column.key=clave de cachรฉ +settings.cache.column.name=Nombre +settings.cache.column.kind=amable +settings.cache.column.state=Estado +settings.cache.column.expires=Vence +settings.cache.kind.local=locales +settings.cache.kind.remote=remoto +settings.cache.state.resolved=resuelto +settings.cache.state.pending=pendiente +settings.cache.state.expired=rancio +settings.cache.state.suppressed=reprimido +settings.cache.refresh=Tabla Refresh +settings.cache.deleteSelected=Eliminar seleccionado +settings.cache.deleteAll=eliminar todo +settings.cache.export=Exportar +settings.cache.import=Importar +settings.cache.summary=Cachรฉ: entradas {0}, {1} resuelta, {2} remota, {3} obsoleta, {4} silenciada. Cachรฉ: {5} KB. +settings.cache.noneSelected=Seleccione primero las filas de cachรฉ. La escoba rechaza las conjeturas. +settings.cache.deleteSelected.done=Entradas de cachรฉ {0} eliminadas. Pequeรฑa nube de polvo contenida. +settings.cache.deleteAll.confirm=ยฟEliminar todas las entradas de cachรฉ del flujo de trabajo GitHub? +settings.cache.deleteAll.done=Se eliminaron todas las entradas de cachรฉ. El cachรฉ ahora estรก sospechosamente silencioso. +settings.cache.export.done=Entradas de cachรฉ {0} exportadas. Pequeรฑa bestia de archivo enjaulada. +settings.cache.import.done=Entradas de cachรฉ importadas. La bestia del archivo se portรณ bien. +settings.cache.import.unsupported=Archivo de cachรฉ de flujo de trabajo GitHub no compatible. +settings.cache.import.brokenLine=Lรญnea de cachรฉ del flujo de trabajo GitHub rota. +settings.cache.import.brokenKey=Clave de cachรฉ del flujo de trabajo GitHub rota. +settings.support.button=Admite este complemento +settings.support.tooltip=Abra la pรกgina de soporte +settings.support.line.0=Alimentar el horno de construcciรณn +settings.support.line.1=Compra el cafรฉ analizador. +settings.support.line.2=Patrocine menos flujos de trabajo embrujados +workflow.run.jobs.title=Trabajos de flujo de trabajo +workflow.run.jobs.root=Ejecuciรณn del flujo de trabajo +workflow.run.jobs.description=GitHub รrbol de trabajos de acciones y registro de trabajos seleccionados +workflow.run.tree.done=hecho +workflow.run.tree.failed=fallรณ +workflow.run.tree.skipped=saltado +workflow.run.tree.warn=advertir +workflow.run.tree.err=errar +workflow.run.delete.tooltip=Eliminar ejecuciรณn +workflow.run.delete.noRun=Aรบn no hay identificaciรณn de ejecuciรณn. +workflow.run.delete.requested=Eliminando ejecute {0}. +workflow.run.delete.done=Ejecute {0} eliminado. +workflow.run.delete.http=Elimine HTTP {0}. Uf. +workflow.run.delete.failed=Eliminar fallรณ: {0} +workflow.run.rerun.all.tooltip=Volver a ejecutar el flujo de trabajo +workflow.run.rerun.failed.tooltip=Volver a ejecutar trabajos fallidos +workflow.run.rerun.noRun=Aรบn no hay identificaciรณn de ejecuciรณn. +workflow.run.rerun.all.requested=Nueva ejecuciรณn solicitada: {0}. +workflow.run.rerun.failed.requested=Trabajos fallidos solicitados: {0}. +workflow.run.rerun.all.done=Nueva ejecuciรณn en cola: {0}. +workflow.run.rerun.failed.done=Trabajos fallidos en cola: {0}. +workflow.run.rerun.http=Vuelva a ejecutar HTTP {0}. Uf. +workflow.run.rerun.failed=La repeticiรณn fracasรณ: {0} +workflow.run.download.log.tooltip=Guardar registro de trabajo +workflow.run.download.artifacts.tooltip=Descargar artefactos +workflow.run.download.noRun=Aรบn no hay identificaciรณn de ejecuciรณn. +workflow.run.download.log.requested=Obteniendo registro para {0}. +workflow.run.download.log.done=Registro guardado: {0}. +workflow.run.download.artifacts.requested=Buscando artefactos. +workflow.run.download.artifacts.empty=Sin artefactos. Pequeรฑo vacรญo. +workflow.run.download.artifact.expired=Artefacto caducado: {0}. +workflow.run.download.artifact.done=Artefacto guardado: {0} -> {1}. +workflow.run.download.failed=Descarga fallida: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_fr.properties b/src/main/resources/messages/GitHubWorkflowBundle_fr.properties new file mode 100644 index 0000000..8a66e19 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_fr.properties @@ -0,0 +1,442 @@ +plugin.name=Flux de travail GitHub +plugin.description=Prise en charge des fichiers de workflow d''actions GitHub +group.GitHubWorkflow.Tools.text=Flux de travail GitHub +group.GitHubWorkflow.Tools.description=Outils du plug-in GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Cache d''actions Refresh +action.GitHubWorkflow.RefreshActionCache.description=Refresh a rรฉsolu les actions GitHub ร  distance et les mรฉtadonnรฉes de flux de travail rรฉutilisables +action.GitHubWorkflow.RestoreActionWarnings.text=Avertissements relatifs aux actions de restauration +action.GitHubWorkflow.RestoreActionWarnings.description=Restaurer les avertissements de validation d''action, d''entrรฉe et de sortie supprimรฉs +action.GitHubWorkflow.ClearActionCache.text=Effacer le cache des actions +action.GitHubWorkflow.ClearActionCache.description=Effacer les actions GitHub mises en cache et les mรฉtadonnรฉes de flux de travail rรฉutilisables +notification.cache.cleared=Suppression des entrรฉes de flux de travail GitHub mises en cache par {0}. +notification.cache.refresh.started=Refreshing {0} a mis en cache les entrรฉes de flux de travail GitHub distantes. +notification.warnings.restored=Avertissements restaurรฉs pour les entrรฉes de workflow {0} GitHub. +workflow.run.configuration.display=Flux de travail GitHub +workflow.run.configuration.description=Rรฉpartir et suivre les exรฉcutions du workflow Actions GitHub +workflow.run.configuration.name=Flux de travail GitHubย : {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=Propriรฉtaire +workflow.run.field.repo=Rรฉfรฉrentiel +workflow.run.field.workflow=Fichier de flux de travail +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Remplacement de la variable d''environnement du jeton +workflow.run.inputs.title=Entrรฉes workflow_dispatch (clรฉ=valeur) +workflow.run.error.apiUrl=GitHub API URL est requis. +workflow.run.error.repository=Le propriรฉtaire et le nom du rรฉfรฉrentiel GitHub sont requis. +workflow.run.error.workflow=Le fichier de flux de travail est requis. +workflow.run.error.ref=Une rรฉfรฉrence de branche ou de balise est requise. +workflow.run.error.inputs=GitHub workflow_dispatch prend en charge au maximum 25 entrรฉes. +workflow.run.gutter.stop=Arrรชter l''exรฉcution du workflow +workflow.run.gutter.stop.text=Arrรชter l''exรฉcution du workflow +workflow.run.gutter.stop.description=Annuler cette course +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=exรฉcuterย : +workflow.log.warning=avertissementย : +workflow.log.error=erreur : +workflow.run.cancel.requested=Annulation demandรฉeย : {0}. +workflow.run.stop.before.id=Arrรชt demandรฉ. Aucun identifiant d''exรฉcution pour l''instant. +workflow.run.cancel.http=Annulez HTTP {0}. Ouf. +workflow.run.cancel.failed=Annulation รฉchouรฉeย :ย {0} +workflow.run.interrupted=Interrompu. +workflow.run.link=Exรฉcuterย :ย {0} +workflow.run.discovery=Exรฉcution acceptรฉe. Identifiant de la course de chasse. +workflow.run.discovery.none=Aucun identifiant d''exรฉcution pour l''instant. L''onglet Actions en sait plus. +workflow.run.status=Statutย : {0}{1} +workflow.run.job.main=Tรขcheย : {0} {1} [{2}{3}{4}] +workflow.run.job.status=Statut : {0} {1}{2}{3} +workflow.run.logs.later=Les journaux apparaรฎtront lorsque GitHub les publiera. +workflow.run.job.logs.later=Tรขche {0}ย :ย {1} +workflow.run.log.failed=ร‰chec du tรฉlรฉchargement du journalย : {0} +workflow.run.log.failed.job=ร‰chec du tรฉlรฉchargement du journal pour {0}ย :ย {1} +workflow.run.job.url=URLย :ย {0} +workflow.run.job.header=Travailย :ย {0} +workflow.run.job.fallbackName=Tรขche {0} +workflow.run.overview=Exรฉcution du workflow {0} {1}/{2} terminรฉe, {3} en cours d''exรฉcution +workflow.run.state.ok=[D''accord] +workflow.run.state.fail=[ร‰CHEC] +workflow.run.state.running=[EXร‰CUTER] +workflow.run.state.waiting=[ATTENDRE] +workflow.run.dispatch.verbs=Amorรงage|Mise en file d''attente|Invocation|Lancement|Dรฉmarrage +workflow.run.dispatch.objects=flux de travail|automatisation|pipeline|exรฉcution +workflow.run.dispatch={0} {1} {2}{3} pour {4} sur {5}. +workflow.run.notification.auth=La rรฉpartition du flux de travail GitHub nรฉcessite un compte GitHub authentifiรฉ. Ajoutez ou actualisez des comptes dans {0}. +workflow.run.notification.openSettings=Ouvrez les paramรจtres GitHub +workflow.cache.progress.title=Rรฉsolution des actions GitHub +workflow.cache.progress.text=Rรฉsolution de {0} {1} +workflow.cache.kind.action=action GitHub +workflow.cache.kind.workflow=flux de travail +inspection.parameter.input=entrรฉe +inspection.parameter.secret=secret GitHub +inspection.action.delete.invalid=Supprimer {0} [{1}] non valide +inspection.action.update.major=Mettre ร  jour l''action [{0}] vers [{1}] +inspection.warning.toggle=Activer/dรฉsactiver les avertissements [{0}] pour [{1}] +inspection.warning.on=sur +inspection.warning.off=รฉteint +inspection.statement.incomplete=Dรฉclaration incomplรจte [{0}] +inspection.invalid.suffix.remove=Supprimer le suffixe non valide [{0}] +inspection.replace.with=Remplacer par [{0}] +inspection.invalid.remove=Supprimer [{0}] non valide +inspection.workflow.syntax.unknownTopLevelKey=Clรฉ de flux de travail inconnue [{0}] +inspection.workflow.syntax.unknownEventKey=ร‰vรฉnement de flux de travail inconnu [{0}] +inspection.workflow.syntax.unknownTriggerKey=Clรฉ de dรฉclenchement inconnue [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Filtre de dรฉclenchement inconnu [{0}] +inspection.workflow.syntax.unknownTriggerValue=Valeur de dรฉclenchement inconnue [{0}] +inspection.workflow.syntax.unknownPermission=Autorisation inconnue [{0}] +inspection.workflow.syntax.unknownPermissionValue=Valeur d''autorisation inconnue [{0}] +inspection.workflow.syntax.unknownJobKey=Clรฉ de travail inconnue [{0}] +inspection.workflow.syntax.unknownStepKey=Clรฉ d''รฉtape inconnue [{0}] +inspection.action.reload=Recharger [{0}] +inspection.action.unresolved=Non rรฉsolu [{0}]ย -ย vรฉrifiez l''accรจs au compte GitHub, les autorisations du rรฉfรฉrentiel privรฉ, les limites de dรฉbit, les rรฉfรฉrences manquantes ou les mรฉtadonnรฉes d''action/workflow manquantes +inspection.action.jump=Aller au fichier [{0}] +inspection.output.unused=Inutilisรฉ [{0}] +inspection.secret.invalid.if=Supprimer [{0}] - Les secrets ne sont pas valides dans les instructions `if` +inspection.secret.replace.runtime=Remplacez [{0}] par [{1}] - s''il n''est pas fourni au moment de l''exรฉcution +inspection.needs.invalid.job=Supprimer l''ID de travail non valide [{0}] - cet ID de travail ne correspond ร  aucun travail prรฉcรฉdent +documentation.description=Descriptionย : {0} +documentation.type=Type : {0} +documentation.required=Obligatoireย :ย {0} +documentation.default=Valeur par dรฉfautย : {0} +documentation.deprecated=Obsolรจteย :ย {0} +documentation.open.declaration=Dรฉclaration ouverte ({0}) +documentation.input.label=Entrรฉe +documentation.secret.label=Secret GitHub +documentation.env.label=Variable d''environnement +documentation.matrix.label=Propriรฉtรฉ matricielle +documentation.need.label=Emploi recherchรฉ +documentation.need.description=Dรฉpendance directe ร  l''emploi. +documentation.needOutput.label=Rรฉsultat du travail requis +documentation.reusableJob.label=Tรขche de flux de travail rรฉutilisable +documentation.reusableJob.description=Tรขche dรฉclarรฉe dans ce workflow rรฉutilisable. +documentation.reusableJobOutput.label=Sortie de tรขches de flux de travail rรฉutilisables +documentation.service.label=Conteneur de services +documentation.servicePort.label=Port de service +documentation.container.label=Conteneur de tรขches +documentation.symbol.label=Symbole de flux de travail +documentation.symbol.description=Expression de flux de travail rรฉsolue. +documentation.workflowOutput.label=Sortie du flux de travail +documentation.jobOutput.label=Rรฉsultat du travail +documentation.action.label=Action GitHub +documentation.externalAction.label=Action extรฉrieure +documentation.reusableWorkflow.label=Flux de travail rรฉutilisable +documentation.resolvedFrom=rรฉsolu ร  partir de {0} +documentation.notResolved=pas encore rรฉsolu +documentation.inputs.title=Entrรฉes +documentation.outputs.title=Sorties +documentation.secrets.title=Secrets GitHub +documentation.value.label=Valeur +documentation.step.title=ร‰tape {0} +documentation.name.label=Nom +documentation.uses.label=Utilisations +documentation.run.label=Courir +documentation.description.label=Descriptif +documentation.step.label=ร‰tape +documentation.source.label=Origine +documentation.stepOutput.label=Sortie pas ร  pas +documentation.context.github=contexte github +documentation.context.github.description=Informations sur l''exรฉcution et l''รฉvรฉnement du flux de travail en cours. +documentation.context.gitea=contexte du gรฎte +documentation.context.gitea.description=Alias compatible Gitea pour le contexte d''actions GitHub. +documentation.context.inputs=contexte des entrรฉes +documentation.context.inputs.description=Entrรฉes de workflow, de rรฉpartition ou dโ€™action disponibles ici. +documentation.context.secrets=contexte secret +documentation.context.secrets.description=Valeurs secrรจtes disponibles pour ce workflow ou cet appel de workflow rรฉutilisable. +documentation.context.env=contexte d''environnement +documentation.context.env.description=Variables d''environnement visibles ร  cet emplacement. +documentation.context.matrix=contexte matriciel +documentation.context.matrix.description=Valeurs matricielles pour le travail en cours. +documentation.context.steps=contexte des รฉtapes +documentation.context.steps.description=ร‰tapes prรฉcรฉdentes de la tรขche en cours, y compris les sorties et l''รฉtat. +documentation.context.needs=a besoin de contexte +documentation.context.needs.description=Dรฉpendances professionnelles directes et leurs extrants/rรฉsultats. +documentation.context.jobs=contexte d''emploi +documentation.context.jobs.description=Travaux et sorties de flux de travail rรฉutilisables. +documentation.context.outputs=sorties +documentation.context.outputs.description=Valeurs de sortie exposรฉes par cette รฉtape ou cette tรขche. +documentation.context.result=rรฉsultat +documentation.context.result.description=Rรฉsultat de la tรขcheย : rรฉussite, รฉchec, annulรฉ ou ignorรฉ. +documentation.context.outcome=rรฉsultat +documentation.context.outcome.description=Le rรฉsultat de lโ€™รฉtape avant lโ€™application de la poursuite sur erreur. +documentation.context.conclusion=conclusion d''รฉtape +documentation.context.conclusion.description=Le rรฉsultat de lโ€™รฉtape aprรจs lโ€™application de la poursuite en cas dโ€™erreur. +error.report.action=Signaler une exception +error.report.description=Descriptif +error.report.steps=ร‰tapes pour reproduire +error.report.sample=Veuillez fournir un exemple de code le cas รฉchรฉant +error.report.message=Message reรงu +error.report.runtime=Informations sur l''exรฉcution +error.report.pluginVersion=Version du pluginย : {0} +error.report.ide=IDEย :ย {0} +error.report.os=OSย :ย {0} +error.report.stacktrace=Trace de pile +completion.shell.bash=Coque Bash. Utilise bash sur les coureurs Linux et macOS, et Git pour bash Windows sur les coureurs Windows. +completion.shell.sh=Remplacement du shell POSIX. +completion.shell.pwsh=Noyau PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Invite de commande Windows. +completion.shell.python=Exรฉcuteur de commandes Python. +completion.runner.name=Le nom du coureur exรฉcutant le travail. +completion.runner.os=Le systรจme d''exploitation du programme d''exรฉcution qui exรฉcute le travail. Les valeurs possibles sont Linux, Windows ou macOS. +completion.runner.arch=L''architecture du coureur exรฉcutant le travail. Les valeurs possibles sont X86, X64, ARM ou ARM64. +completion.runner.temp=Le chemin d''accรจs ร  un rรฉpertoire temporaire sur le coureur. Ce rรฉpertoire est vidรฉ au dรฉbut et ร  la fin de chaque tรขche. Notez que les fichiers ne seront pas supprimรฉs si le compte utilisateur du coureur n''a pas l''autorisation de les supprimer. +completion.runner.toolCache=Le chemin d''accรจs au rรฉpertoire contenant les outils prรฉinstallรฉs pour les exรฉcuteurs hรฉbergรฉs par GitHub. +completion.runner.debug=Ceci est dรฉfini uniquement si la journalisation du dรฉbogage est activรฉe et a toujours la valeur 1. +completion.runner.environment=L''environnement du coureur exรฉcutant le travail. Les valeurs possibles sont hรฉbergรฉes sur github ou auto-hรฉbergรฉes. +completion.job.status=L''รฉtat actuel du travail. +completion.job.checkRunId=ID d''exรฉcution de contrรดle du travail en cours. +completion.job.container=Informations sur le conteneur du travail. +completion.job.services=Conteneurs de services crรฉรฉs pour une tรขche. +completion.job.workflowRef=La rรฉfรฉrence complรจte du fichier de workflow qui dรฉfinit la tรขche en cours. +completion.job.workflowSha=Le commit SHA du fichier de workflow qui dรฉfinit la tรขche en cours. +completion.job.workflowRepository=Le propriรฉtaire/dรฉpรดt du rรฉfรฉrentiel contenant le fichier de workflow qui dรฉfinit la tรขche en cours. +completion.job.workflowFilePath=Chemin d''accรจs au fichier de workflow, par rapport ร  la racine du rรฉfรฉrentiel. +completion.job.containerField=Champ Conteneur de travaux +completion.job.service=Service d''emploi +completion.job.serviceField=Domaine du service d''emploi +completion.job.mappedServicePort=Port de service mappรฉ +completion.strategy.failFast=Indique si toutes les tรขches en cours sont annulรฉes en cas d''รฉchec d''une tรขche matricielle. +completion.strategy.jobIndex=Index de base zรฉro du travail actuel dans la matrice. +completion.strategy.jobTotal=Le nombre total d''emplois dans la matrice. +completion.strategy.maxParallel=Nombre maximum de tรขches matricielles pouvant รชtre exรฉcutรฉes simultanรฉment. +completion.context.inputs=Entrรฉes de workflow, telles que workflow_dispatch ou workflow_call. +completion.context.secrets=Secrets de flux de travail. +completion.context.job=Informations sur la tรขche en cours d''exรฉcution. +completion.context.jobs=Travaux de flux de travail. +completion.context.matrix=Propriรฉtรฉs de matrice dรฉfinies pour le travail matriciel actuel. +completion.context.strategy=Informations sur la stratรฉgie dโ€™exรฉcution matricielle pour la tรขche en cours. +completion.context.steps=ร‰tapes avec un identifiant dans le travail en cours. +completion.context.env=Variables d''environnement des tรขches et des รฉtapes. +completion.context.vars=Variables de configuration personnalisรฉes issues des รฉtendues d''organisation, de rรฉfรฉrentiel et d''environnement. +completion.context.needs=Travaux qui doivent รชtre terminรฉs avant que ce travail puisse s''exรฉcuter, ainsi que leurs sorties et rรฉsultats. +completion.context.github=Informations sur l''exรฉcution et les รฉvรฉnements du workflow ร  partir du contexte GitHub. +completion.context.gitea=Alias compatible Gitea pour le contexte d''actions GitHub. +completion.context.runner=Informations sur le coureur exรฉcutant la tรขche en cours. +completion.secret.githubToken=Jeton crรฉรฉ automatiquement pour chaque exรฉcution de flux de travail. +completion.remote.repository=Dรฉpรดt distant +completion.uses.local.workflow=Flux de travail local rรฉutilisable +completion.uses.local.action=Action locale +completion.uses.ref.known=Rรฉfรฉrence de workflow connue +completion.uses.ref.remote=Rรฉfรฉrence du workflow ร  distance +completion.uses.remote.known=Action ร  distance connue ou workflow rรฉutilisable +completion.workflow.syntax=Syntaxe du flux de travail des actions GitHub +completion.workflow.top.name=Nom d''affichage du flux de travail +completion.workflow.top.run-name=Nom d''exรฉcution dynamique +completion.workflow.top.on=ร‰vรฉnements qui dรฉmarrent le flux de travail +completion.workflow.top.permissions=Autorisations GITHUB_TOKEN par dรฉfaut +completion.workflow.top.env=Variables d''environnement ร  l''รฉchelle du workflow +completion.workflow.top.defaults=Paramรจtres de tรขche et d''รฉtape par dรฉfaut +completion.workflow.top.concurrency=Groupe de concurrence et annulation +completion.workflow.top.jobs=Tรขches exรฉcutรฉes dans ce flux de travail +completion.workflow.event.branch_protection_rule=La protection des branches a รฉtรฉ modifiรฉe +completion.workflow.event.check_run=Le cycle de contrรดle unique a รฉtรฉ modifiรฉ +completion.workflow.event.check_suite=Suite de chรจques modifiรฉe +completion.workflow.event.create=Branche ou tag crรฉรฉ +completion.workflow.event.delete=Branche ou tag supprimรฉ +completion.workflow.event.deployment=Dรฉploiement crรฉรฉ +completion.workflow.event.deployment_status=Statut de dรฉploiement modifiรฉ +completion.workflow.event.discussion=Discussion modifiรฉe +completion.workflow.event.discussion_comment=Commentaire de discussion modifiรฉ +completion.workflow.event.fork=Rรฉfรฉrentiel forkรฉ +completion.workflow.event.gollum=La page wiki a รฉtรฉ modifiรฉe +completion.workflow.event.image_version=La version de l''image du package a รฉtรฉ modifiรฉe +completion.workflow.event.issue_comment=Problรจme ou commentaire PR modifiรฉ +completion.workflow.event.issues=Problรจme modifiรฉ +completion.workflow.event.label=ร‰tiquette modifiรฉe +completion.workflow.event.merge_group=Vรฉrification de la file d''attente de fusion demandรฉe +completion.workflow.event.milestone=Jalon modifiรฉ +completion.workflow.event.page_build=La crรฉation des pages a รฉtรฉ exรฉcutรฉe +completion.workflow.event.project=Le projet classique a changรฉ +completion.workflow.event.project_card=Carte de projet classique modifiรฉe +completion.workflow.event.project_column=La colonne du projet classique a รฉtรฉ modifiรฉe +completion.workflow.event.public=Le rรฉfรฉrentiel est devenu public +completion.workflow.event.pull_request=La demande de tirage a รฉtรฉ modifiรฉe +completion.workflow.event.pull_request_review=L''avis PR a รฉtรฉ modifiรฉ +completion.workflow.event.pull_request_review_comment=Le commentaire de l''avis PR a รฉtรฉ modifiรฉ +completion.workflow.event.pull_request_target=Contexte cible PR. Couteaux tranchants. +completion.workflow.event.push=Validation ou tag poussรฉ +completion.workflow.event.registry_package=Package publiรฉ ou mis ร  jour +completion.workflow.event.release=Version modifiรฉe +completion.workflow.event.repository_dispatch=ร‰vรฉnement API personnalisรฉ +completion.workflow.event.schedule=Tique Cron. Mรฉcanisme d''horlogerie. +completion.workflow.event.status=Statut de validation modifiรฉ +completion.workflow.event.watch=Rรฉfรฉrentiel favori +completion.workflow.event.workflow_call=Appel de workflow rรฉutilisable +completion.workflow.event.workflow_dispatch=Bouton d''exรฉcution manuelle +completion.workflow.event.workflow_run=L''exรฉcution du workflow a รฉtรฉ modifiรฉe +completion.workflow.eventFilter.types=Limiter les types d''activitรฉs +completion.workflow.eventFilter.branches=Seules ces branches +completion.workflow.eventFilter.branches-ignore=Ignorer ces branches +completion.workflow.eventFilter.tags=Seulement ces balises +completion.workflow.eventFilter.tags-ignore=Ignorer ces balises +completion.workflow.eventFilter.paths=Seulement ces chemins +completion.workflow.eventFilter.paths-ignore=Ignorer ces chemins +completion.workflow.eventFilter.workflows=Noms des workflows ร  surveiller +completion.workflow.eventFilter.cron=Calendrier Cron. Petite horloge. +completion.workflow.permission.actions=Exรฉcutions de workflow et artefacts d''action +completion.workflow.permission.artifact-metadata=Enregistrements de mรฉtadonnรฉes d''artefacts +completion.workflow.permission.attestations=Attestations d''artefacts +completion.workflow.permission.checks=Vรฉrifier les exรฉcutions et les suites +completion.workflow.permission.code-quality=Rapports sur la qualitรฉ du code +completion.workflow.permission.contents=Contenu du rรฉfรฉrentiel +completion.workflow.permission.deployments=Dรฉploiements +completion.workflow.permission.discussions=Discussions GitHub +completion.workflow.permission.id-token=Jetons OpenID Connect +completion.workflow.permission.issues=Problรจmes +completion.workflow.permission.models=Modรจles GitHub +completion.workflow.permission.packages=Forfaits GitHub +completion.workflow.permission.pages=Pages GitHub +completion.workflow.permission.pull-requests=Demandes de tirage +completion.workflow.permission.security-events=Analyse de code et รฉvรฉnements de sรฉcuritรฉ +completion.workflow.permission.statuses=Statuts de validation +completion.workflow.permission.vulnerability-alerts=Alertes Dependabot +completion.workflow.permission.value.read=Accรจs en lecture +completion.workflow.permission.value.write=Accรจs en รฉcriture, lecture incluse +completion.workflow.permission.value.none=Pas d''accรจs +completion.workflow.permission.shorthand.read-all=Toutes les autorisations sont lues. Grande couverture. +completion.workflow.permission.shorthand.write-all=Toutes les autorisations รฉcrivent. Gros marteau. +completion.workflow.permission.shorthand.empty=Dรฉsactiver les autorisations de jeton +completion.workflow.job.name=Nom d''affichage du travail +completion.workflow.job.permissions=Autorisations du jeton de tรขche +completion.workflow.job.needs=Des emplois ร  attendre +completion.workflow.job.if=Conditions de travail +completion.workflow.job.runs-on=ร‰tiquette ou groupe de coureurs +completion.workflow.job.snapshot=Instantanรฉ du coureur +completion.workflow.job.environment=Environnement de dรฉploiement +completion.workflow.job.concurrency=Verrouillage de la concurrence des tรขches +completion.workflow.job.outputs=Sorties que d''autres travaux peuvent lire +completion.workflow.job.env=Variables d''environnement de travail +completion.workflow.job.defaults=Paramรจtres par dรฉfaut du travail +completion.workflow.job.steps=Liste des รฉtapes. Le travail proprement dit. +completion.workflow.job.timeout-minutes=Dรฉlai d''expiration de la tรขche en minutes +completion.workflow.job.strategy=Stratรฉgie matricielle et de planification +completion.workflow.job.continue-on-error=Laisse ce travail รฉchouer doucement +completion.workflow.job.container=Conteneur pour ce travail +completion.workflow.job.services=Conteneurs de service side-car +completion.workflow.job.uses=Workflow rรฉutilisable ร  appeler +completion.workflow.job.with=Entrรฉes pour le workflow appelรฉ +completion.workflow.job.secrets=Secrets du flux de travail appelรฉ +completion.workflow.defaultsRun.shell=Shell par dรฉfaut pour les รฉtapes d''exรฉcution +completion.workflow.defaultsRun.working-directory=Rรฉpertoire de travail par dรฉfaut +completion.workflow.concurrency.group=Nom du verrou pour les exรฉcutions en file d''attente +completion.workflow.concurrency.cancel-in-progress=Annuler les anciennes exรฉcutions correspondantes +completion.workflow.environment.name=Nom de l''environnement +completion.workflow.environment.url=Environnement URL +completion.workflow.strategy.matrix=Axes matriciels et variantes +completion.workflow.strategy.fail-fast=Annuler les frรจres et sล“urs de la matrice en cas d''รฉchec +completion.workflow.strategy.max-parallel=Capuchon de parallรฉlisme matriciel +completion.workflow.matrix.include=Ajouter des combinaisons de matrices +completion.workflow.matrix.exclude=Supprimer les combinaisons matricielles +completion.workflow.step.id=ID d''รฉtape pour les rรฉfรฉrences +completion.workflow.step.if=ร‰tat de l''รฉtape +completion.workflow.step.name=Nom d''affichage de l''รฉtape +completion.workflow.step.uses=Action ร  exรฉcuter +completion.workflow.step.run=Script Shell ร  exรฉcuter +completion.workflow.step.shell=Shell pour cette รฉtape d''exรฉcution +completion.workflow.step.with=Entrรฉes d''action +completion.workflow.step.env=Variables d''environnement d''รฉtape +completion.workflow.step.continue-on-error=Que cette รฉtape รฉchoue doucement +completion.workflow.step.timeout-minutes=Dรฉlai d''expiration de l''รฉtape en minutes +completion.workflow.step.working-directory=Rรฉpertoire de travail de l''รฉtape +completion.workflow.container.image=Image du conteneur +completion.workflow.container.credentials=Informations d''identification du registre +completion.workflow.container.env=Variables d''environnement de conteneur +completion.workflow.container.ports=Ports ร  exposer +completion.workflow.container.volumes=Volumes ร  monter +completion.workflow.container.options=Docker crรฉer des options +completion.workflow.service.image=Image du conteneur de services +completion.workflow.service.credentials=Informations d''identification du registre +completion.workflow.service.env=Variables d''environnement de service +completion.workflow.service.ports=Ports de service +completion.workflow.service.volumes=Volumes de services +completion.workflow.service.options=Docker crรฉer des options +completion.workflow.credentials.username=Nom d''utilisateur du registre +completion.workflow.credentials.password=Mot de passe ou jeton du registre +completion.workflow.inputType.string=Saisie de texte +completion.workflow.inputType.boolean=Entrรฉe vraie ou fausse +completion.workflow.inputType.choice=Entrรฉe de choix dรฉroulante +completion.workflow.inputType.number=Saisie du numรฉro +completion.workflow.inputType.environment=Entrรฉe du sรฉlecteur d''environnement +completion.workflow.boolean.true=Oui. Allumez-le. +completion.workflow.boolean.false=Non. Gardez-le dans l''obscuritรฉ. +completion.workflow.runner.ubuntu-latest=Dernier coureur Ubuntu +completion.workflow.runner.ubuntu-24.04=Coureur Ubuntu 24.04 +completion.workflow.runner.ubuntu-22.04=Coureur Ubuntu 22.04 +completion.workflow.runner.windows-latest=Dernier coureur Windows +completion.workflow.runner.windows-2025=Exรฉcuteur Windows Serveur 2025 +completion.workflow.runner.windows-2022=Exรฉcuteur Windows Serveur 2022 +completion.workflow.runner.macos-latest=Dernier coureur macOS +completion.workflow.runner.macos-15=macOS 15 coureur +completion.workflow.runner.macos-14=macOS 14 coureurs +completion.workflow.runner.self-hosted=Votre propre coureur. Votre cirque. +completion.steps.outputs=L''ensemble des sorties dรฉfinies pour l''รฉtape. +completion.steps.conclusion=Le rรฉsultat d''une รฉtape terminรฉe aprรจs la poursuite d''une erreur est appliquรฉ. +completion.steps.outcome=Le rรฉsultat dโ€™une รฉtape terminรฉe avant lโ€™application de la poursuite sur erreur. +completion.jobs.outputs=L''ensemble des sorties dรฉfinies pour le travail. +completion.jobs.result=Le rรฉsultat du travail. +settings.displayName=Flux de travail GitHub +settings.language.label=Langueย : +settings.language.system=IDE/systรจme par dรฉfaut +settings.cache.title=Cache d''actions +settings.cache.column.key=Clรฉ de cache +settings.cache.column.name=Nom +settings.cache.column.kind=Genre +settings.cache.column.state=ร‰tat +settings.cache.column.expires=Expire +settings.cache.kind.local=locale +settings.cache.kind.remote=ร  distance +settings.cache.state.resolved=rรฉsolu +settings.cache.state.pending=en attente +settings.cache.state.expired=pรฉrimรฉ +settings.cache.state.suppressed=supprimรฉ +settings.cache.refresh=Table Refresh +settings.cache.deleteSelected=Supprimer la sรฉlection +settings.cache.deleteAll=Supprimer tout +settings.cache.export=Exporter +settings.cache.import=Importer +settings.cache.summary=Cacheย : entrรฉes {0}, {1} rรฉsolu, {2} distant, {3} obsolรจte, {4} dรฉsactivรฉ. Cache : {5} KB. +settings.cache.noneSelected=Sรฉlectionnez d''abord les lignes du cache. Le balai refuse les conjectures. +settings.cache.deleteSelected.done=Entrรฉes de cache {0} supprimรฉes. Minuscule nuage de poussiรจre contenu. +settings.cache.deleteAll.confirm=Supprimer toutes les entrรฉes du cache du workflow GitHubย ? +settings.cache.deleteAll.done=Supprimรฉ toutes les entrรฉes du cache. La cache est maintenant รฉtrangement silencieuse. +settings.cache.export.done=Entrรฉes de cache {0} exportรฉes. Petite bรชte d''archives en cage. +settings.cache.import.done=Entrรฉes de cache importรฉes. La bรชte des archives sโ€™est bien comportรฉe. +settings.cache.import.unsupported=Fichier cache de flux de travail GitHub non pris en charge. +settings.cache.import.brokenLine=Ligne de cache du flux de travail GitHub cassรฉe. +settings.cache.import.brokenKey=Clรฉ de cache du flux de travail GitHub cassรฉe. +settings.support.button=Supporte ce plugin +settings.support.tooltip=Ouvrez la page d''assistance +settings.support.line.0=Alimenter le four de construction +settings.support.line.1=Acheter le cafรฉ analyseur +settings.support.line.2=Parrainez moins de flux de travail hantรฉs +workflow.run.jobs.title=Tรขches de flux de travail +workflow.run.jobs.root=Exรฉcution du flux de travail +workflow.run.jobs.description=GitHub Arborescence des tรขches d''actions et journal des tรขches sรฉlectionnรฉes +workflow.run.tree.done=fait +workflow.run.tree.failed=รฉchouรฉ +workflow.run.tree.skipped=ignorรฉ +workflow.run.tree.warn=avertir +workflow.run.tree.err=euh +workflow.run.delete.tooltip=Supprimer l''exรฉcution +workflow.run.delete.noRun=Aucun identifiant d''exรฉcution pour l''instant. +workflow.run.delete.requested=Suppression de l''exรฉcution {0}. +workflow.run.delete.done=Exรฉcutez {0} supprimรฉ. +workflow.run.delete.http=Supprimez HTTP {0}. Ouf. +workflow.run.delete.failed=Supprimer l''erreurย :ย {0} +workflow.run.rerun.all.tooltip=Rรฉexรฉcuter le flux de travail +workflow.run.rerun.failed.tooltip=Rรฉexรฉcuter les tรขches ayant รฉchouรฉ +workflow.run.rerun.noRun=Aucun identifiant d''exรฉcution pour l''instant. +workflow.run.rerun.all.requested=Rรฉexรฉcution demandรฉeย : {0}. +workflow.run.rerun.failed.requested=Tรขches ayant รฉchouรฉ demandรฉesย : {0}. +workflow.run.rerun.all.done=Rรฉexรฉcution en file d''attenteย : {0}. +workflow.run.rerun.failed.done=Travaux ayant รฉchouรฉ mis en file d''attenteย : {0}. +workflow.run.rerun.http=Rรฉexรฉcutez HTTP {0}. Ouf. +workflow.run.rerun.failed=La rรฉexรฉcution a รฉchouรฉย :ย {0} +workflow.run.download.log.tooltip=Enregistrer le journal des travaux +workflow.run.download.artifacts.tooltip=Tรฉlรฉcharger des artefacts +workflow.run.download.noRun=Aucun identifiant d''exรฉcution pour l''instant. +workflow.run.download.log.requested=Rรฉcupรฉration du journal pour {0}. +workflow.run.download.log.done=Journal enregistrรฉย : {0}. +workflow.run.download.artifacts.requested=Rรฉcupรฉrer des artefacts. +workflow.run.download.artifacts.empty=Aucun artefact. Petit vide. +workflow.run.download.artifact.expired=Artefact expirรฉย : {0}. +workflow.run.download.artifact.done=Artefact enregistrรฉย : {0} -> {1}. +workflow.run.download.failed=Tรฉlรฉchargement รฉchouรฉย : {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_hi.properties b/src/main/resources/messages/GitHubWorkflowBundle_hi.properties new file mode 100644 index 0000000..1c8debc --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_hi.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ +plugin.description=GitHub เค•เฅเคฐเคฟเคฏเคพเคเค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคซเคผเคพเค‡เคฒเฅ‹เค‚ เค•เฅ‡ เคฒเคฟเค เคธเคฎเคฐเฅเคฅเคจ +group.GitHubWorkflow.Tools.text=GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ +group.GitHubWorkflow.Tools.description=GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคชเฅเคฒเค—เค‡เคจ เค‰เคชเค•เคฐเคฃ +action.GitHubWorkflow.RefreshActionCache.text=Refresh เคเค•เฅเคถเคจ เค•เฅˆเคถ +action.GitHubWorkflow.RefreshActionCache.description=Refresh เคจเฅ‡ เคฆเฅ‚เคฐเคธเฅเคฅ GitHub เค•เฅเคฐเคฟเคฏเคพเค“เค‚ เค”เคฐ เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฎเฅ‡เคŸเคพเคกเฅ‡เคŸเคพ เค•เคพ เคธเคฎเคพเคงเคพเคจ เค•เคฟเคฏเคพ +action.GitHubWorkflow.RestoreActionWarnings.text=เค•เคพเคฐเฅเคฐเคตเคพเคˆ เคšเฅ‡เคคเคพเคตเคจเคฟเคฏเคพเค เคชเฅเคจเคฐเฅเคธเฅเคฅเคพเคชเคฟเคค เค•เคฐเฅ‡เค‚ +action.GitHubWorkflow.RestoreActionWarnings.description=เคฆเคฌเคพเคˆ เค—เคˆ เค•เคพเคฐเฅเคฐเคตเคพเคˆ, เค‡เคจเคชเฅเคŸ เค”เคฐ เค†เค‰เคŸเคชเฅเคŸ เคธเคคเฅเคฏเคพเคชเคจ เคšเฅ‡เคคเคพเคตเคจเคฟเคฏเฅ‹เค‚ เค•เฅ‹ เคชเฅเคจเคฐเฅเคธเฅเคฅเคพเคชเคฟเคค เค•เคฐเฅ‡เค‚ +action.GitHubWorkflow.ClearActionCache.text=เคเค•เฅเคถเคจ เค•เฅˆเคถ เคธเคพเคซเคผ เค•เคฐเฅ‡เค‚ +action.GitHubWorkflow.ClearActionCache.description=เค•เฅˆเคถเฅเคก GitHub เค•เฅเคฐเคฟเคฏเคพเคเค เค”เคฐ เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฎเฅ‡เคŸเคพเคกเฅ‡เคŸเคพ เคธเคพเคซเคผ เค•เคฐเฅ‡เค‚ +notification.cache.cleared={0} เค•เฅˆเคถเฅเคก GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคชเฅเคฐเคตเคฟเคทเฅเคŸเคฟเคฏเคพเค เคธเคพเคซเคผ เค•เฅ€ เค—เคˆเค‚เฅค +notification.cache.refresh.started=Refreshing {0} เค•เฅˆเคถเฅเคก เคฐเคฟเคฎเฅ‹เคŸ GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคชเฅเคฐเคตเคฟเคทเฅเคŸเคฟเคฏเคพเคเฅค +notification.warnings.restored={0} GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคชเฅเคฐเคตเคฟเคทเฅเคŸเคฟเคฏเฅ‹เค‚ เค•เฅ‡ เคฒเคฟเค เคชเฅเคจเคฐเฅเคธเฅเคฅเคพเคชเคฟเคค เคšเฅ‡เคคเคพเคตเคจเคฟเคฏเคพเคเฅค +workflow.run.configuration.display=GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ +workflow.run.configuration.description=เคชเฅเคฐเฅ‡เคทเคฃ เค”เคฐ GitHub เค•เฅเคฐเคฟเคฏเคพเคเค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฐเคจ เค•เคพ เคชเคพเคฒเคจ เค•เคฐเฅ‡เค‚ +workflow.run.configuration.name=GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=เคฎเคพเคฒเคฟเค• +workflow.run.field.repo=เคญเคฃเฅเคกเคพเคฐ +workflow.run.field.workflow=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคซเคผเคพเค‡เคฒ +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=เคŸเฅ‹เค•เคจ เคเคจเคตเฅ€ เคตเคฐ เคซเคผเฅ‰เคฒเคฌเฅˆเค• +workflow.run.inputs.title=workflow_dispatch เค‡เคจเคชเฅเคŸ (เค•เฅเค‚เคœเฅ€=เคฎเคพเคจ) +workflow.run.error.apiUrl=GitHub API URL เค†เคตเคถเฅเคฏเค• เคนเฅˆ. +workflow.run.error.repository=GitHub เคฐเคฟเคชเฅ‰เคœเคฟเคŸเคฐเฅ€ เคธเฅเคตเคพเคฎเฅ€ เค”เคฐ เคจเคพเคฎ เค†เคตเคถเฅเคฏเค• เคนเฅˆเฅค +workflow.run.error.workflow=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคซเคผเคพเค‡เคฒ เค†เคตเคถเฅเคฏเค• เคนเฅˆ. +workflow.run.error.ref=เคถเคพเค–เคพ เคฏเคพ เคŸเฅˆเค— เคฐเฅ‡เคซเคฐเฅ€ เค†เคตเคถเฅเคฏเค• เคนเฅˆ. +workflow.run.error.inputs=GitHub workflow_dispatch เค…เคงเคฟเค•เคคเคฎ 25 เค‡เคจเคชเฅเคŸ เค•เคพ เคธเคฎเคฐเฅเคฅเคจ เค•เคฐเคคเคพ เคนเฅˆเฅค +workflow.run.gutter.stop=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคšเคฒเคพเคจเคพ เคฌเค‚เคฆ เค•เคฐเฅ‡เค‚ +workflow.run.gutter.stop.text=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคšเคฒเคพเคจเคพ เคฌเค‚เคฆ เค•เคฐเฅ‡เค‚ +workflow.run.gutter.stop.description=เคฏเคน เคฐเคจ เคฐเคฆเฅเคฆ เค•เคฐเฅ‡เค‚ +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=เคšเคฒเคพเคเค: +workflow.log.warning=เคšเฅ‡เคคเคพเคตเคจเฅ€: +workflow.log.error=เคคเฅเคฐเฅเคŸเคฟ: +workflow.run.cancel.requested=เคฐเคฆเฅเคฆ เค•เคฐเคจเฅ‡ เค•เคพ เค…เคจเฅเคฐเฅ‹เคง: {0}เฅค +workflow.run.stop.before.id=เคฐเฅเค•เคจเฅ‡ เค•เคพ เค…เคจเฅเคฐเฅ‹เคง เค•เคฟเคฏเคพ เค—เคฏเคพ. เค…เคญเฅ€ เคคเค• เค•เฅ‹เคˆ เคฐเคจ เค†เคˆเคกเฅ€ เคจเคนเฅ€เค‚ เคนเฅˆ. +workflow.run.cancel.http=HTTP {0} เคฐเคฆเฅเคฆ เค•เคฐเฅ‡เค‚เฅค เค‰เคซเคผ. +workflow.run.cancel.failed=เคฐเคฆเฅเคฆ เคฐเคฆเฅเคฆ เค•เคฐเฅ‡เค‚: {0} +workflow.run.interrupted=เคฌเคพเคงเคฟเคค. +workflow.run.link=เคšเคฒเคพเคเค: {0} +workflow.run.discovery=เคญเคพเค—เฅ‹ เคธเฅเคตเฅ€เค•เคพเคฐ เค•เคฟเคฏเคพ เค—เคฏเคพ. เคถเคฟเค•เคพเคฐ เคฐเคจ เค†เคˆเคกเฅ€. +workflow.run.discovery.none=เค…เคญเฅ€ เคคเค• เค•เฅ‹เคˆ เคฐเคจ เค†เคˆเคกเฅ€ เคจเคนเฅ€เค‚ เคนเฅˆ. เค•เฅเคฐเคฟเคฏเคพเคเค เคŸเฅˆเคฌ เค…เคงเคฟเค• เคœเคพเคจเคคเคพ เคนเฅˆเฅค +workflow.run.status=เคธเฅเคฅเคฟเคคเคฟ: {0}{1} +workflow.run.job.main=เคจเฅŒเค•เคฐเฅ€: {0} {1} [{2}{3}{4}] +workflow.run.job.status=เคธเฅเคฅเคฟเคคเคฟ: {0} {1}{2}{3} +workflow.run.logs.later=เคœเคฌ GitHub เค‰เคจเฅเคนเฅ‡เค‚ เคชเฅเคฐเค•เคพเคถเคฟเคค เค•เคฐเฅ‡เค—เคพ เคคเฅ‹ เคฒเฅ‰เค— เคฆเคฟเค–เคพเคˆ เคฆเฅ‡เค‚เค—เฅ‡เฅค +workflow.run.job.logs.later=เคœเฅ‰เคฌ {0}: {1} +workflow.run.log.failed=เคฒเฅ‰เค— เคกเคพเค‰เคจเคฒเฅ‹เคก เคตเคฟเคซเคฒ: {0} +workflow.run.log.failed.job={0}: {1} เค•เฅ‡ เคฒเคฟเค เคฒเฅ‰เค— เคกเคพเค‰เคจเคฒเฅ‹เคก เคตเคฟเคซเคฒ เคฐเคนเคพ +workflow.run.job.url=URL: {0} +workflow.run.job.header=เคจเฅŒเค•เคฐเฅ€: {0} +workflow.run.job.fallbackName=เคจเฅŒเค•เคฐเฅ€ {0} +workflow.run.overview=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฐเคจ {0} {1}/{2} เคชเฅ‚เคฐเคพ เคนเฅ‹ เค—เคฏเคพ, {3} เคšเคฒ เคฐเคนเคพ เคนเฅˆ +workflow.run.state.ok=[เค เฅ€เค• เคนเฅˆ] +workflow.run.state.fail=[เคตเคฟเคซเคฒ] +workflow.run.state.running=[เคญเคพเค—เฅ‹] +workflow.run.state.waiting=[เคชเฅเคฐเคคเฅ€เค•เฅเคทเคพ เค•เคฐเฅ‡เค‚] +workflow.run.dispatch.verbs=เคชเฅเคฐเคพเค‡เคฎเคฟเค‚เค—|เค•เฅเคฏเฅ‚เค‡เค‚เค—|เคธเคฎเคจเคฟเค‚เค—|เคฒเฅ‰เคจเฅเคšเคฟเค‚เค—|เคฌเฅ‚เคŸเคฟเค‚เค— +workflow.run.dispatch.objects=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹|เคธเฅเคตเคšเคพเคฒเคจ|เคชเคพเค‡เคชเคฒเคพเค‡เคจ|เคฐเคจ +workflow.run.dispatch={0} {1} {2}{3} {4} เค•เฅ‡ เคฒเคฟเค {5} เคชเคฐเฅค +workflow.run.notification.auth=GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคชเฅเคฐเฅ‡เคทเคฃ เค•เฅ‡ เคฒเคฟเค เคเค• เคชเฅเคฐเคฎเคพเคฃเคฟเคค GitHub เค–เคพเคคเฅ‡ เค•เฅ€ เค†เคตเคถเฅเคฏเค•เคคเคพ เคนเฅ‹เคคเฅ€ เคนเฅˆเฅค {0} เคฎเฅ‡เค‚ เค–เคพเคคเฅ‡ เคœเฅ‹เคกเคผเฅ‡เค‚ เคฏเคพ เคคเคพเคœเคผเคพ เค•เคฐเฅ‡เค‚เฅค +workflow.run.notification.openSettings=GitHub เคธเฅ‡เคŸเคฟเค‚เค—เฅเคธ เค–เฅ‹เคฒเฅ‡เค‚ +workflow.cache.progress.title=GitHub เค•เฅเคฐเคฟเคฏเคพเค“เค‚ เค•เคพ เคธเคฎเคพเคงเคพเคจ +workflow.cache.progress.text={0} {1} เค•เคพ เคธเคฎเคพเคงเคพเคจ +workflow.cache.kind.action=เค•เคพเคฐเฅเคฐเคตเคพเคˆ +workflow.cache.kind.workflow=เค•เคพเคฐเฅเคฏเคชเฅเคฐเคตเคพเคน +inspection.parameter.input=เค‡เคจเคชเฅเคŸ +inspection.parameter.secret=เคฐเคนเคธเฅเคฏ +inspection.action.delete.invalid=เค…เคฎเคพเคจเฅเคฏ {0} [{1}] เคนเคŸเคพเคเค‚ +inspection.action.update.major=เค•เคพเคฐเฅเคฐเคตเคพเคˆ [{0}] เค•เฅ‹ [{1}] เคฎเฅ‡เค‚ เค…เคชเคกเฅ‡เคŸ เค•เคฐเฅ‡เค‚ +inspection.warning.toggle=[{1}] เค•เฅ‡ เคฒเคฟเค เคšเฅ‡เคคเคพเคตเคจเคฟเคฏเคพเค [{0}] เคŸเฅ‰เค—เคฒ เค•เคฐเฅ‡เค‚ +inspection.warning.on=เคชเคฐ +inspection.warning.off=เคฌเค‚เคฆ +inspection.statement.incomplete=เค…เคงเฅ‚เคฐเคพ เคตเคฟเคตเคฐเคฃ [{0}] +inspection.invalid.suffix.remove=เค…เคฎเคพเคจเฅเคฏ เคชเฅเคฐเคคเฅเคฏเคฏ เคนเคŸเคพเคเค [{0}] +inspection.replace.with=[{0}] เคธเฅ‡ เคฌเคฆเคฒเฅ‡เค‚ +inspection.invalid.remove=เค…เคฎเคพเคจเฅเคฏ เคนเคŸเคพเคเค‚ [{0}] +inspection.workflow.syntax.unknownTopLevelKey=เค…เคœเฅเคžเคพเคค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅเค‚เคœเฅ€ [{0}] +inspection.workflow.syntax.unknownEventKey=เค…เคœเฅเคžเคพเคค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค‡เคตเฅ‡เค‚เคŸ [{0}] +inspection.workflow.syntax.unknownTriggerKey=เค…เคœเฅเคžเคพเคค เคŸเฅเคฐเคฟเค—เคฐ เค•เฅเค‚เคœเฅ€ [{0}] +inspection.workflow.syntax.unknownTriggerFilter=เค…เคœเฅเคžเคพเคค เคŸเฅเคฐเคฟเค—เคฐ เคซเคผเคฟเคฒเฅเคŸเคฐ [{0}] +inspection.workflow.syntax.unknownTriggerValue=เค…เคœเฅเคžเคพเคค เคŸเฅเคฐเคฟเค—เคฐ เคฎเคพเคจ [{0}] +inspection.workflow.syntax.unknownPermission=เค…เคœเฅเคžเคพเคค เค…เคจเฅเคฎเคคเคฟ [{0}] +inspection.workflow.syntax.unknownPermissionValue=เค…เคœเฅเคžเคพเคค เค…เคจเฅเคฎเคคเคฟ เคฎเคพเคจ [{0}] +inspection.workflow.syntax.unknownJobKey=เค…เคœเฅเคžเคพเคค เค•เคพเคฐเฅเคฏ เค•เฅเค‚เคœเฅ€ [{0}] +inspection.workflow.syntax.unknownStepKey=เค…เคœเฅเคžเคพเคค เคšเคฐเคฃ เค•เฅเค‚เคœเฅ€ [{0}] +inspection.action.reload=เคชเฅเคจเคƒ เคฒเฅ‹เคก เค•เคฐเฅ‡เค‚ [{0}] +inspection.action.unresolved=เค…เคจเคธเฅเคฒเคเคพ [{0}] - GitHub เค–เคพเคคเคพ เคชเคนเฅเค‚เคš, เคจเคฟเคœเฅ€ เคฐเคฟเคชเฅ‰เคœเคฟเคŸเคฐเฅ€ เค…เคจเฅเคฎเคคเคฟเคฏเคพเค, เคฆเคฐ เคธเฅ€เคฎเคพ, เค—เฅเคฎ เคฐเฅ‡เคซเคฐเฅ€, เคฏเคพ เค—เฅเคฎ เค•เคพเคฐเฅเคฐเคตเคพเคˆ/เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฎเฅ‡เคŸเคพเคกเฅ‡เคŸเคพ เค•เฅ€ เคœเคพเคเคš เค•เคฐเฅ‡เค‚ +inspection.action.jump=เคซเคผเคพเค‡เคฒ เคชเคฐ เคœเคพเคเค‚ [{0}] +inspection.output.unused=เค…เคชเฅเคฐเคฏเฅเค•เฅเคค [{0}] +inspection.secret.invalid.if=[{0}] เคนเคŸเคพเคเค‚ - เคฐเคนเคธเฅเคฏ `if` เค•เคฅเคจเฅ‹เค‚ เคฎเฅ‡เค‚ เคฎเคพเคจเฅเคฏ เคจเคนเฅ€เค‚ เคนเฅˆเค‚ +inspection.secret.replace.runtime=[{0}] เค•เฅ‹ [{1}] เคธเฅ‡ เคฌเคฆเคฒเฅ‡เค‚ - เคฏเคฆเคฟ เคฏเคน เคฐเคจเคŸเคพเค‡เคฎ เคชเคฐ เคชเฅเคฐเคฆเคพเคจ เคจเคนเฅ€เค‚ เค•เคฟเคฏเคพ เค—เคฏเคพ เคนเฅˆ +inspection.needs.invalid.job=เค…เคฎเคพเคจเฅเคฏ เคœเฅ‰เคฌเค†เคˆเคกเฅ€ เคนเคŸเคพเคเค‚ [{0}] - เคฏเคน เคœเฅ‰เคฌเค†เคˆเคกเฅ€ เค•เคฟเคธเฅ€ เคญเฅ€ เคชเคฟเค›เคฒเฅ€ เคจเฅŒเค•เคฐเฅ€ เคธเฅ‡ เคฎเฅ‡เคฒ เคจเคนเฅ€เค‚ เค–เคพเคคเฅ€ เคนเฅˆ +documentation.description=เคตเคฟเคตเคฐเคฃ: {0} +documentation.type=เคชเฅเคฐเค•เคพเคฐ: {0} +documentation.required=เค†เคตเคถเฅเคฏเค•: {0} +documentation.default=เคกเคฟเคซเคผเฅ‰เคฒเฅเคŸ: {0} +documentation.deprecated=เค…เคธเฅเคตเฅ€เค•เฅƒเคค: {0} +documentation.open.declaration=เค–เฅเคฒเฅ€ เค˜เฅ‹เคทเคฃเคพ ({0}) +documentation.input.label=เค‡เคจเคชเฅเคŸ +documentation.secret.label=เค—เฅเคชเฅเคค +documentation.env.label=เคชเคฐเฅเคฏเคพเคตเคฐเคฃ เคšเคฐ +documentation.matrix.label=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคธเค‚เคชเคคเฅเคคเคฟ +documentation.need.label=เคจเฅŒเค•เคฐเฅ€ เคšเคพเคนเคฟเค +documentation.need.description=เคชเฅเคฐเคคเฅเคฏเค•เฅเคท เคจเฅŒเค•เคฐเฅ€ เคชเคฐ เคจเคฟเคฐเฅเคญเคฐเคคเคพ. +documentation.needOutput.label=เคœเฅ‰เคฌ เค†เค‰เคŸเคชเฅเคŸ เค•เฅ€ เคœเคฐเฅ‚เคฐเคค เคนเฅˆ +documentation.reusableJob.label=เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เคพเคฐเฅเคฏ +documentation.reusableJob.description=เค‡เคธ เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฎเฅ‡เค‚ เค•เคพเคฐเฅเคฏ เค˜เฅ‹เคทเคฟเคค เค•เคฟเคฏเคพ เค—เคฏเคพเฅค +documentation.reusableJobOutput.label=เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคœเฅ‰เคฌ เค†เค‰เคŸเคชเฅเคŸ +documentation.service.label=เคธเฅ‡เคตเคพ เค•เค‚เคŸเฅ‡เคจเคฐ +documentation.servicePort.label=เคธเฅ‡เคตเคพ เคฌเค‚เคฆเคฐเค—เคพเคน +documentation.container.label=เคœเฅ‰เคฌ เค•เค‚เคŸเฅ‡เคจเคฐ +documentation.symbol.label=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคชเฅเคฐเคคเฅ€เค• +documentation.symbol.description=เคธเคฎเคพเคงเคพเคจเคฟเคค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค…เคญเคฟเคตเฅเคฏเค•เฅเคคเคฟ. +documentation.workflowOutput.label=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค†เค‰เคŸเคชเฅเคŸ +documentation.jobOutput.label=เคจเฅŒเค•เคฐเฅ€ เค†เค‰เคŸเคชเฅเคŸ +documentation.action.label=เค•เคพเคฐเฅเคฐเคตเคพเคˆ +documentation.externalAction.label=เคฌเคพเคนเฅเคฏ เค•เฅเคฐเคฟเคฏเคพ +documentation.reusableWorkflow.label=เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เค•เคพเคฐเฅเคฏเคชเฅเคฐเคตเคพเคน +documentation.resolvedFrom={0} เคธเฅ‡ เคนเคฒ เค•เคฟเคฏเคพ เค—เคฏเคพ +documentation.notResolved=เค…เคญเฅ€ เคคเค• เคนเคฒ เคจเคนเฅ€เค‚ เคนเฅเค† +documentation.inputs.title=เค‡เคจเคชเฅเคŸ +documentation.outputs.title=เค†เค‰เคŸเคชเฅเคŸ +documentation.secrets.title=เคฐเคนเคธเฅเคฏ +documentation.value.label=เคฎเฅ‚เคฒเฅเคฏ +documentation.step.title=เคšเคฐเคฃ {0} +documentation.name.label=เคจเคพเคฎ +documentation.uses.label=เค‰เคชเคฏเฅ‹เค— +documentation.run.label=เคญเคพเค—เฅ‹ +documentation.description.label=เคตเคฟเคตเคฐเคฃ +documentation.step.label=เค•เคฆเคฎ +documentation.source.label=เคธเฅเคฐเฅ‹เคค +documentation.stepOutput.label=เคšเคฐเคฃ เค†เค‰เคŸเคชเฅเคŸ +documentation.context.github=github เคชเฅเคฐเคธเค‚เค— +documentation.context.github.description=เคตเคฐเฅเคคเคฎเคพเคจ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฐเคจ เค”เคฐ เค‡เคตเฅ‡เค‚เคŸ เค•เฅ‡ เคฌเคพเคฐเฅ‡ เคฎเฅ‡เค‚ เคœเคพเคจเค•เคพเคฐเฅ€. +documentation.context.gitea=เค—เฅ€เคคเคพ เคชเฅเคฐเคธเค‚เค— +documentation.context.gitea.description=GitHub เค•เฅเคฐเคฟเคฏเคพเคเค เคธเค‚เคฆเคฐเฅเคญ เค•เฅ‡ เคฒเคฟเค Gitea-เคธเค‚เค—เคค เค‰เคชเคจเคพเคฎเฅค +documentation.context.inputs=เค‡เคจเคชเฅเคŸ เคธเค‚เคฆเคฐเฅเคญ +documentation.context.inputs.description=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹, เคชเฅเคฐเฅ‡เคทเคฃ, เคฏเคพ เค•เคพเคฐเฅเคฐเคตเคพเคˆ เค‡เคจเคชเฅเคŸ เคฏเคนเคพเค‚ เค‰เคชเคฒเคฌเฅเคง เคนเฅˆเค‚เฅค +documentation.context.secrets=เคฐเคนเคธเฅเคฏ เคชเฅเคฐเคธเค‚เค— +documentation.context.secrets.description=เค‡เคธ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฏเคพ เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅ‰เคฒ เค•เฅ‡ เคฒเคฟเค เค—เฅเคชเฅเคค เคฎเคพเคจ เค‰เคชเคฒเคฌเฅเคง เคนเฅˆเค‚เฅค +documentation.context.env=env เคธเค‚เคฆเคฐเฅเคญ +documentation.context.env.description=เค‡เคธ เคธเฅเคฅเคพเคจ เคชเคฐ เคชเคฐเฅเคฏเคพเคตเคฐเคฃ เคšเคฐ เคฆเคฟเค–เคพเคˆ เคฆเฅ‡ เคฐเคนเฅ‡ เคนเฅˆเค‚เฅค +documentation.context.matrix=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคธเค‚เคฆเคฐเฅเคญ +documentation.context.matrix.description=เคตเคฐเฅเคคเคฎเคพเคจ เคจเฅŒเค•เคฐเฅ€ เค•เฅ‡ เคฒเคฟเค เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคฎเคพเคจ. +documentation.context.steps=เคšเคฐเคฃเฅ‹เค‚ เค•เคพ เคธเค‚เคฆเคฐเฅเคญ +documentation.context.steps.description=เค†เค‰เคŸเคชเฅเคŸ เค”เคฐ เคธเฅเคฅเคฟเคคเคฟ เคธเคนเคฟเคค เคตเคฐเฅเคคเคฎเคพเคจ เค•เคพเคฐเฅเคฏ เคฎเฅ‡เค‚ เคชเคฟเค›เคฒเฅ‡ เคšเคฐเคฃเฅค +documentation.context.needs=เคธเค‚เคฆเคฐเฅเคญ เค•เฅ€ เค†เคตเคถเฅเคฏเค•เคคเคพ เคนเฅˆ +documentation.context.needs.description=เคชเฅเคฐเคคเฅเคฏเค•เฅเคท เคจเฅŒเค•เคฐเฅ€ เคจเคฟเคฐเฅเคญเคฐเคคเคพ เค”เคฐ เค‰เคจเค•เฅ‡ เค†เค‰เคŸเคชเฅเคŸ/เคชเคฐเคฟเคฃเคพเคฎเฅค +documentation.context.jobs=เคจเฅŒเค•เคฐเคฟเคฏเฅ‹เค‚ เค•เคพ เคธเค‚เคฆเคฐเฅเคญ +documentation.context.jobs.description=เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคจเฅŒเค•เคฐเคฟเคฏเคพเค‚ เค”เคฐ เค†เค‰เคŸเคชเฅเคŸเฅค +documentation.context.outputs=เค†เค‰เคŸเคชเฅเคŸ +documentation.context.outputs.description=เค‡เคธ เคšเคฐเคฃ เคฏเคพ เค•เคพเคฐเฅเคฏ เคฆเฅเคตเคพเคฐเคพ เค†เค‰เคŸเคชเฅเคŸ เคฎเคพเคจ เค‰เคœเคพเค—เคฐ เคนเฅ‹เคคเฅ‡ เคนเฅˆเค‚เฅค +documentation.context.result=เคชเคฐเคฟเคฃเคพเคฎ +documentation.context.result.description=เคจเฅŒเค•เคฐเฅ€ เค•เคพ เคชเคฐเคฟเคฃเคพเคฎ: เคธเคซเคฒเคคเคพ, เคตเคฟเคซเคฒเคคเคพ, เคฐเคฆเฅเคฆ, เคฏเคพ เค›เฅ‹เคกเคผ เคฆเคฟเคฏเคพ เค—เคฏเคพเฅค +documentation.context.outcome=เคชเคฐเคฟเคฃเคพเคฎ +documentation.context.outcome.description=เค•เค‚เคŸเคฟเคจเฅเคฏเฅ‚-เค‘เคจ-เคเคฐเคฐ เคฒเคพเค—เฅ‚ เคนเฅ‹เคจเฅ‡ เคธเฅ‡ เคชเคนเคฒเฅ‡ เคšเคฐเคฃ เคชเคฐเคฟเคฃเคพเคฎเฅค +documentation.context.conclusion=เคจเคฟเคทเฅเค•เคฐเฅเคท +documentation.context.conclusion.description=เค•เค‚เคŸเคฟเคจเฅเคฏเฅ‚-เค‘เคจ-เคเคฐเคฐ เคฒเคพเค—เฅ‚ เคนเฅ‹เคจเฅ‡ เค•เฅ‡ เคฌเคพเคฆ เคšเคฐเคฃ เคชเคฐเคฟเคฃเคพเคฎเฅค +error.report.action=เค…เคชเคตเคพเคฆ เค•เฅ€ เคฐเคฟเคชเฅ‹เคฐเฅเคŸ เค•เคฐเฅ‡เค‚ +error.report.description=เคตเคฟเคตเคฐเคฃ +error.report.steps=เคชเฅเคจเคฐเฅเคคเฅเคชเคพเคฆเคจ เค•เฅ‡ เคšเคฐเคฃ +error.report.sample=เคฏเคฆเคฟ เคฒเคพเค—เฅ‚ เคนเฅ‹ เคคเฅ‹ เค•เฅƒเคชเคฏเคพ เค•เฅ‹เคก เคจเคฎเฅ‚เคจเคพ เคชเฅเคฐเคฆเคพเคจ เค•เคฐเฅ‡เค‚ +error.report.message=เคธเค‚เคฆเฅ‡เคถ +error.report.runtime=เคฐเคจเคŸเคพเค‡เคฎ เคธเฅ‚เคšเคจเคพ +error.report.pluginVersion=เคชเฅเคฒเค—เค‡เคจ เคธเค‚เคธเฅเค•เคฐเคฃ: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=เคธเฅเคŸเฅˆเค•เคŸเฅเคฐเฅ‡เคธ +completion.shell.bash=Bash เคถเฅ‡เคฒเฅค Linux เค”เคฐ macOS เคงเคพเคตเค•เฅ‹เค‚ เคชเคฐ เคฌเฅˆเคถ เค•เคพ เค‰เคชเคฏเฅ‹เค— เค•เคฐเคคเคพ เคนเฅˆ, เค”เคฐ Windows เค•เฅ‡ เคฒเคฟเค Git Windows เคงเคพเคตเค•เฅ‹เค‚ เคชเคฐ เคฌเฅˆเคถ เค•เคพ เค‰เคชเคฏเฅ‹เค— เค•เคฐเคคเคพ เคนเฅˆเฅค +completion.shell.sh=POSIX เคถเฅ‡เคฒ เคซเคผเฅ‰เคฒเคฌเฅˆเค•เฅค +completion.shell.pwsh=PowerShell เค•เฅ‹เคฐเฅค +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Windows เค•เคฎเคพเค‚เคก เคชเฅเคฐเฅ‰เคฎเฅเคชเฅเคŸเฅค +completion.shell.python=Python เค•เคฎเคพเค‚เคก เคฐเคจเคฐเฅค +completion.runner.name=เค•เคพเคฐเฅเคฏ เคจเคฟเคทเฅเคชเคพเคฆเคฟเคค เค•เคฐเคจเฅ‡ เคตเคพเคฒเฅ‡ เคงเคพเคตเค• เค•เคพ เคจเคพเคฎ. +completion.runner.os=เค•เคพเคฐเฅเคฏ เคจเคฟเคทเฅเคชเคพเคฆเคฟเคค เค•เคฐเคจเฅ‡ เคตเคพเคฒเฅ‡ เคงเคพเคตเค• เค•เคพ เค‘เคชเคฐเฅ‡เคŸเคฟเค‚เค— เคธเคฟเคธเฅเคŸเคฎเฅค เคธเค‚เคญเคพเคตเคฟเคค เคฎเคพเคจ Linux, Windows, เคฏเคพ macOS เคนเฅˆเค‚เฅค +completion.runner.arch=เค•เคพเคฐเฅเคฏ เคจเคฟเคทเฅเคชเคพเคฆเคฟเคค เค•เคฐเคจเฅ‡ เคตเคพเคฒเฅ‡ เคงเคพเคตเค• เค•เฅ€ เคตเคพเคธเฅเคคเฅเค•เคฒเคพเฅค เคธเค‚เคญเคพเคตเคฟเคค เคฎเคพเคจ X86, X64, ARM, เคฏเคพ ARM64 เคนเฅˆเค‚เฅค +completion.runner.temp=เคฐเคจเคฐ เคชเคฐ เคเค• เค…เคธเฅเคฅเคพเคฏเฅ€ เคจเคฟเคฐเฅเคฆเฅ‡เคถเคฟเค•เคพ เค•เคพ เคชเคฅ. เคฏเคน เคจเคฟเคฐเฅเคฆเฅ‡เคถเคฟเค•เคพ เคชเฅเคฐเคคเฅเคฏเฅ‡เค• เค•เคพเคฐเฅเคฏ เค•เฅ‡ เค†เคฐเค‚เคญ เค”เคฐ เค…เค‚เคค เคฎเฅ‡เค‚ เค–เคพเคฒเฅ€ เค•เคฐ เคฆเฅ€ เคœเคพเคคเฅ€ เคนเฅˆเฅค เคงเฅเคฏเคพเคจ เคฆเฅ‡เค‚ เค•เคฟ เคฏเคฆเคฟ เคงเคพเคตเค• เค•เฅ‡ เค‰เคชเคฏเฅ‹เค—เค•เคฐเฅเคคเคพ เค–เคพเคคเฅ‡ เค•เฅ‡ เคชเคพเคธ เค‰เคจเฅเคนเฅ‡เค‚ เคนเคŸเคพเคจเฅ‡ เค•เฅ€ เค…เคจเฅเคฎเคคเคฟ เคจเคนเฅ€เค‚ เคนเฅˆ เคคเฅ‹ เคซเคผเคพเค‡เคฒเฅ‡เค‚ เคจเคนเฅ€เค‚ เคนเคŸเคพเคˆ เคœเคพเคเค‚เค—เฅ€เฅค +completion.runner.toolCache=GitHub-เคนเฅ‹เคธเฅเคŸ เค•เคฟเค เค—เค เคงเคพเคตเค•เฅ‹เค‚ เค•เฅ‡ เคฒเคฟเค เคชเฅ‚เคฐเฅเคตเคธเฅเคฅเคพเคชเคฟเคค เคŸเฅ‚เคฒ เคตเคพเคฒเฅ€ เคจเคฟเคฐเฅเคฆเฅ‡เคถเคฟเค•เคพ เค•เคพ เคชเคฅเฅค +completion.runner.debug=เคฏเคน เค•เฅ‡เคตเคฒ เคคเคญเฅ€ เคธเฅ‡เคŸ เค•เคฟเคฏเคพ เคœเคพเคคเคพ เคนเฅˆ เคœเคฌ เคกเคฟเคฌเค— เคฒเฅ‰เค—เคฟเค‚เค— เคธเค•เฅเคทเคฎ เคนเฅ‹ เค”เคฐ เค‡เคธเค•เคพ เคฎเคพเคจ เคนเคฎเฅ‡เคถเคพ 1 เคนเฅ‹เฅค +completion.runner.environment=เค•เคพเคฐเฅเคฏ เคจเคฟเคทเฅเคชเคพเคฆเคฟเคค เค•เคฐเคจเฅ‡ เคตเคพเคฒเฅ‡ เคงเคพเคตเค• เค•เคพ เคตเคพเคคเคพเคตเคฐเคฃเฅค เคธเค‚เคญเคพเคตเคฟเคค เคฎเคพเคจ เคœเฅ€เคฅเคฌ-เคนเฅ‹เคธเฅเคŸเฅ‡เคก เคฏเคพ เคธเฅเคตเคฏเค‚-เคนเฅ‹เคธเฅเคŸเฅ‡เคก เคนเฅˆเค‚เฅค +completion.job.status=เคจเฅŒเค•เคฐเฅ€ เค•เฅ€ เคตเคฐเฅเคคเคฎเคพเคจ เคธเฅเคฅเคฟเคคเคฟ. +completion.job.checkRunId=เคตเคฐเฅเคคเคฎเคพเคจ เค•เคพเคฐเฅเคฏ เค•เฅ€ เคšเฅ‡เค• เคฐเคจ เค†เคˆเคกเฅ€เฅค +completion.job.container=เค•เคพเคฐเฅเคฏ เค•เฅ‡ เค•เค‚เคŸเฅ‡เคจเคฐ เค•เฅ‡ เคฌเคพเคฐเฅ‡ เคฎเฅ‡เค‚ เคœเคพเคจเค•เคพเคฐเฅ€. +completion.job.services=เค•เคฟเคธเฅ€ เค•เคพเคฐเฅเคฏ เค•เฅ‡ เคฒเคฟเค เคฌเคจเคพเค เค—เค เคธเฅ‡เคตเคพ เค•เค‚เคŸเฅ‡เคจเคฐ. +completion.job.workflowRef=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคซเคผเคพเค‡เคฒ เค•เคพ เคชเฅ‚เคฐเฅเคฃ เคฐเฅ‡เคซเคฐเฅ€ เคœเฅ‹ เคตเคฐเฅเคคเคฎเคพเคจ เค•เคพเคฐเฅเคฏ เค•เฅ‹ เคชเคฐเคฟเคญเคพเคทเคฟเคค เค•เคฐเคคเคพ เคนเฅˆเฅค +completion.job.workflowSha=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคซเคผเคพเค‡เคฒ เค•เฅ€ เคชเฅเคฐเคคเคฟเคฌเคฆเฅเคงเคคเคพ SHA เคœเฅ‹ เคตเคฐเฅเคคเคฎเคพเคจ เค•เคพเคฐเฅเคฏ เค•เฅ‹ เคชเคฐเคฟเคญเคพเคทเคฟเคค เค•เคฐเคคเฅ€ เคนเฅˆเฅค +completion.job.workflowRepository=เคฐเคฟเคชเฅ‰เคœเคฟเคŸเคฐเฅ€ เค•เคพ เคธเฅเคตเคพเคฎเฅ€/เคฐเฅ‡เคชเฅ‹ เคœเคฟเคธเคฎเฅ‡เค‚ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคซเคผเคพเค‡เคฒ เคนเฅˆ เคœเฅ‹ เคตเคฐเฅเคคเคฎเคพเคจ เค•เคพเคฐเฅเคฏ เค•เฅ‹ เคชเคฐเคฟเคญเคพเคทเคฟเคค เค•เคฐเคคเฅ€ เคนเฅˆเฅค +completion.job.workflowFilePath=เคฐเคฟเคชเฅ‰เคœเคฟเคŸเคฐเฅ€ เคฐเฅ‚เคŸ เค•เฅ‡ เคธเคพเคชเฅ‡เค•เฅเคท เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคซเคผเคพเค‡เคฒ เคชเคฅเฅค +completion.job.containerField=เคœเฅ‰เคฌ เค•เค‚เคŸเฅ‡เคจเคฐ เคซเคผเฅ€เคฒเฅเคก +completion.job.service=เคจเฅŒเค•เคฐเฅ€ เคธเฅ‡เคตเคพ +completion.job.serviceField=เคจเฅŒเค•เคฐเฅ€ เคธเฅ‡เคตเคพ เค•เฅเคทเฅ‡เคคเฅเคฐ +completion.job.mappedServicePort=เคฎเฅˆเคช เค•เคฟเคฏเคพ เค—เคฏเคพ เคธเคฐเฅเคตเคฟเคธ เคชเฅ‹เคฐเฅเคŸ +completion.strategy.failFast=เคฏเคฆเคฟ เค•เฅ‹เคˆ เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เค•เคพเคฐเฅเคฏ เคตเคฟเคซเคฒ เคนเฅ‹ เคœเคพเคคเคพ เคนเฅˆ เคคเฅ‹ เค•เฅเคฏเคพ เคธเคญเฅ€ เคชเฅเคฐเค—เคคเคฟเคฐเคค เค•เคพเคฐเฅเคฏ เคฐเคฆเฅเคฆ เค•เคฐ เคฆเคฟเค เคœเคพเคคเฅ‡ เคนเฅˆเค‚เฅค +completion.strategy.jobIndex=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคฎเฅ‡เค‚ เคตเคฐเฅเคคเคฎเคพเคจ เค•เคพเคฐเฅเคฏ เค•เคพ เคถเฅ‚เคจเฅเคฏ-เค†เคงเคพเคฐเคฟเคค เคธเฅ‚เคšเค•เคพเค‚เค•เฅค +completion.strategy.jobTotal=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคฎเฅ‡เค‚ เคจเฅŒเค•เคฐเคฟเคฏเฅ‹เค‚ เค•เฅ€ เค•เฅเคฒ เคธเค‚เค–เฅเคฏเคพ. +completion.strategy.maxParallel=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคจเฅŒเค•เคฐเคฟเคฏเฅ‹เค‚ เค•เฅ€ เค…เคงเคฟเค•เคคเคฎ เคธเค‚เค–เฅเคฏเคพ เคœเฅ‹ เคเค• เคธเคพเคฅ เคšเคฒ เคธเค•เคคเฅ€ เคนเฅˆเค‚เฅค +completion.context.inputs=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค‡เคจเคชเฅเคŸ, เคœเฅˆเคธเฅ‡ workflow_dispatch เคฏเคพ workflow_callเฅค +completion.context.secrets=เค•เคพเคฐเฅเคฏเคชเฅเคฐเคตเคพเคน เคฐเคนเคธเฅเคฏ. +completion.context.job=เคตเคฐเฅเคคเคฎเคพเคจ เคฎเฅ‡เค‚ เคšเคฒ เคฐเคนเฅ‡ เคœเฅ‰เคฌ เค•เฅ€ เคœเคพเคจเค•เคพเคฐเฅ€. +completion.context.jobs=เค•เคพเคฐเฅเคฏเคชเฅเคฐเคตเคพเคน เคจเฅŒเค•เคฐเคฟเคฏเคพเค. +completion.context.matrix=เคตเคฐเฅเคคเคฎเคพเคจ เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เค•เคพเคฐเฅเคฏ เค•เฅ‡ เคฒเคฟเค เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เค—เฅเคฃ เคชเคฐเคฟเคญเคพเคทเคฟเคคเฅค +completion.context.strategy=เคตเคฐเฅเคคเคฎเคพเคจ เคจเฅŒเค•เคฐเฅ€ เค•เฅ‡ เคฒเคฟเค เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคจเคฟเคทเฅเคชเคพเคฆเคจ เคฐเคฃเคจเฅ€เคคเคฟ เค•เฅ€ เคœเคพเคจเค•เคพเคฐเฅ€เฅค +completion.context.steps=เคตเคฐเฅเคคเคฎเคพเคจ เคจเฅŒเค•เคฐเฅ€ เคฎเฅ‡เค‚ เคเค• เค†เคˆเคกเฅ€ เค•เฅ‡ เคธเคพเคฅ เค•เคฆเคฎ. +completion.context.env=เคจเฅŒเค•เคฐเคฟเคฏเฅ‹เค‚ เค”เคฐ เคšเคฐเคฃเฅ‹เค‚ เคธเฅ‡ เคชเคฐเฅเคฏเคพเคตเคฐเคฃ เคšเคฐเฅค +completion.context.vars=เคธเค‚เค—เค เคจ, เคฐเคฟเคชเฅ‰เคœเคฟเคŸเคฐเฅ€ เค”เคฐ เคชเคฐเฅเคฏเคพเคตเคฐเคฃ เคธเฅเค•เฅ‹เคช เคธเฅ‡ เค•เคธเฅเคŸเคฎ เค•เฅ‰เคจเฅเคซเคผเคฟเค—เคฐเฅ‡เคถเคจ เคšเคฐเฅค +completion.context.needs=เคตเฅ‡ เค•เคพเคฐเฅเคฏ เคœเฅ‹ เค‡เคธ เค•เคพเคฐเฅเคฏ เค•เฅ‡ เคšเคฒเคจเฅ‡ เคธเฅ‡ เคชเคนเคฒเฅ‡ เคชเฅ‚เคฐเฅ‡ เคนเฅ‹เคจเฅ‡ เคšเคพเคนเคฟเค, เคธเคพเคฅ เคนเฅ€ เค‰เคจเค•เฅ‡ เค†เค‰เคŸเคชเฅเคŸ เค”เคฐ เคชเคฐเคฟเคฃเคพเคฎ เคญเฅ€เฅค +completion.context.github=GitHub เคธเค‚เคฆเคฐเฅเคญ เคธเฅ‡ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฐเคจ เค”เคฐ เคˆเคตเฅ‡เค‚เคŸ เคœเคพเคจเค•เคพเคฐเฅ€เฅค +completion.context.gitea=GitHub เค•เฅเคฐเคฟเคฏเคพเคเค เคธเค‚เคฆเคฐเฅเคญ เค•เฅ‡ เคฒเคฟเค Gitea-เคธเค‚เค—เคค เค‰เคชเคจเคพเคฎเฅค +completion.context.runner=เคตเคฐเฅเคคเคฎเคพเคจ เค•เคพเคฐเฅเคฏ เคจเคฟเคทเฅเคชเคพเคฆเคฟเคค เค•เคฐเคจเฅ‡ เคตเคพเคฒเฅ‡ เคงเคพเคตเค• เค•เฅ‡ เคฌเคพเคฐเฅ‡ เคฎเฅ‡เค‚ เคœเคพเคจเค•เคพเคฐเฅ€เฅค +completion.secret.githubToken=เคชเฅเคฐเคคเฅเคฏเฅ‡เค• เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฐเคจ เค•เฅ‡ เคฒเคฟเค เคธเฅเคตเคšเคพเคฒเคฟเคค เคฐเฅ‚เคช เคธเฅ‡ เคŸเฅ‹เค•เคจ เคฌเคจเคพเคฏเคพ เค—เคฏเคพเฅค +completion.remote.repository=เคฆเฅ‚เคฐเคธเฅเคฅ เคญเค‚เคกเคพเคฐ +completion.uses.local.workflow=เคธเฅเคฅเคพเคจเฅ€เคฏ เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ +completion.uses.local.action=เคธเฅเคฅเคพเคจเฅ€เคฏ เค•เคพเคฐเฅเคฐเคตเคพเคˆ +completion.uses.ref.known=เคœเฅเคžเคพเคค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคธเค‚เคฆเคฐเฅเคญ +completion.uses.ref.remote=เคฆเฅ‚เคฐเคธเฅเคฅ เค•เคพเคฐเฅเคฏเคชเฅเคฐเคตเคพเคน เคธเค‚เคฆเคฐเฅเคญ +completion.uses.remote.known=เคœเฅเคžเคพเคค เคฆเฅ‚เคฐเคธเฅเคฅ เค•เฅเคฐเคฟเคฏเคพ เคฏเคพ เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ +completion.workflow.syntax=GitHub เค•เฅเคฐเคฟเคฏเคพเคเค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคธเคฟเค‚เคŸเฅˆเค•เฅเคธ +completion.workflow.top.name=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคชเฅเคฐเคฆเคฐเฅเคถเคจ เคจเคพเคฎ +completion.workflow.top.run-name=เค—เคคเคฟเคถเฅ€เคฒ เคฐเคจ เคจเคพเคฎ +completion.workflow.top.on=เคตเฅ‡ เค˜เคŸเคจเคพเคเค เคœเฅ‹ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคชเฅเคฐเคพเคฐเค‚เคญ เค•เคฐเคคเฅ€ เคนเฅˆเค‚ +completion.workflow.top.permissions=เคกเคฟเคซเคผเฅ‰เคฒเฅเคŸ GITHUB_TOKEN เค…เคจเฅเคฎเคคเคฟเคฏเคพเค +completion.workflow.top.env=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹-เคตเฅเคฏเคพเคชเฅ€ เคชเคฐเฅเคฏเคพเคตเคฐเคฃ เคšเคฐ +completion.workflow.top.defaults=เคกเคฟเคซเคผเฅ‰เคฒเฅเคŸ เค•เคพเคฐเฅเคฏ เค”เคฐ เคšเคฐเคฃ เคธเฅ‡เคŸเคฟเค‚เค— +completion.workflow.top.concurrency=เคธเคฎเคตเคฐเฅเคคเฅ€ เคธเคฎเฅ‚เคน เค”เคฐ เคฐเคฆเฅเคฆเฅ€เค•เคฐเคฃ +completion.workflow.top.jobs=เค‡เคธ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฎเฅ‡เค‚ เคšเคฒเคจเฅ‡ เคตเคพเคฒเฅ€ เคจเฅŒเค•เคฐเคฟเคฏเคพเค +completion.workflow.event.branch_protection_rule=เคถเคพเค–เคพ เคธเฅเคฐเค•เฅเคทเคพ เคฌเคฆเคฒ เค—เคˆ +completion.workflow.event.check_run=เคธเคฟเค‚เค—เคฒ เคšเฅ‡เค• เคฐเคจ เคฌเคฆเคฒเคพ เค—เคฏเคพ +completion.workflow.event.check_suite=เคšเฅ‡เค• เคธเฅเค‡เคŸ เคฌเคฆเคฒเคพ เค—เคฏเคพ +completion.workflow.event.create=เคถเคพเค–เคพ เคฏเคพ เคŸเฅˆเค— เคฌเคจเคพเคฏเคพ เค—เคฏเคพ +completion.workflow.event.delete=เคถเคพเค–เคพ เคฏเคพ เคŸเฅˆเค— เคนเคŸเคพ เคฆเคฟเคฏเคพ เค—เคฏเคพ +completion.workflow.event.deployment=เคชเคฐเคฟเคจเคฟเคฏเฅ‹เคœเคจ เคฌเคจเคพเคฏเคพ เค—เคฏเคพ +completion.workflow.event.deployment_status=เคชเคฐเคฟเคจเคฟเคฏเฅ‹เคœเคจ เคธเฅเคฅเคฟเคคเคฟ เคฌเคฆเคฒ เค—เคˆ +completion.workflow.event.discussion=เคšเคฐเฅเคšเคพ เคฌเคฆเคฒ เค—เคˆ +completion.workflow.event.discussion_comment=เคšเคฐเฅเคšเคพ เคŸเคฟเคชเฅเคชเคฃเฅ€ เคฌเคฆเคฒ เค—เคˆ +completion.workflow.event.fork=เคญเค‚เคกเคพเคฐ เคฆเฅเคตเคฟเคญเคพเคœเคฟเคค +completion.workflow.event.gollum=เคตเคฟเค•เฅ€ เคชเฅ‡เคœ เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.event.image_version=เคชเฅˆเค•เฅ‡เคœ เค›เคตเคฟ เคธเค‚เคธเฅเค•เคฐเคฃ เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.event.issue_comment=เค…เค‚เค• เคฏเคพ PR เคŸเคฟเคชเฅเคชเคฃเฅ€ เคฌเคฆเคฒ เค—เคˆ +completion.workflow.event.issues=เคฎเฅเคฆเฅเคฆเคพ เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.event.label=เคฒเฅ‡เคฌเคฒ เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.event.merge_group=เคฎเคฐเฅเคœ เค•เคคเคพเคฐ เคœเคพเค‚เคš เค•เคพ เค…เคจเฅเคฐเฅ‹เคง เค•เคฟเคฏเคพ เค—เคฏเคพ +completion.workflow.event.milestone=เคฎเฅ€เคฒ เค•เคพ เคชเคคเฅเคฅเคฐ เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.event.page_build=เคชเฅ‡เคœ เคจเคฟเคฐเฅเคฎเคพเคฃ เคšเคฒเคพ +completion.workflow.event.project=เค•เฅเคฒเคพเคธเคฟเค• เคชเฅเคฐเฅ‹เคœเฅ‡เค•เฅเคŸ เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.event.project_card=เค•เฅเคฒเคพเคธเคฟเค• เคชเฅเคฐเฅ‹เคœเฅ‡เค•เฅเคŸ เค•เคพเคฐเฅเคก เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.event.project_column=เค•เฅเคฒเคพเคธเคฟเค• เคชเฅเคฐเฅ‹เคœเฅ‡เค•เฅเคŸ เค•เฅ‰เคฒเคฎ เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.event.public=เคญเค‚เคกเคพเคฐ เคธเคพเคฐเฅเคตเคœเคจเคฟเค• เคนเฅ‹ เค—เคฏเคพ +completion.workflow.event.pull_request=เคชเฅเคฒ เค…เคจเฅเคฐเฅ‹เคง เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.event.pull_request_review=PR เคธเคฎเฅ€เค•เฅเคทเคพ เคฌเคฆเคฒ เค—เคˆ +completion.workflow.event.pull_request_review_comment=PR เคธเคฎเฅ€เค•เฅเคทเคพ เคŸเคฟเคชเฅเคชเคฃเฅ€ เคฌเคฆเคฒ เค—เคˆ +completion.workflow.event.pull_request_target=PR เคฒเค•เฅเคทเฅเคฏ เคธเค‚เคฆเคฐเฅเคญเฅค เคคเฅ€เค–เฅ€ เค›เฅเคฐเฅ€เฅค +completion.workflow.event.push=เคชเฅเคฐเคคเคฟเคฌเคฆเฅเคง เคฏเคพ เคŸเฅˆเค— เคชเฅเคถ เค•เคฟเคฏเคพ เค—เคฏเคพ +completion.workflow.event.registry_package=เคชเฅˆเค•เฅ‡เคœ เคชเฅเคฐเค•เคพเคถเคฟเคค เคฏเคพ เค…เคฆเฅเคฏเคคเคจ +completion.workflow.event.release=เคฐเคฟเคฒเฅ€เคœ เคฌเคฆเคฒ เค—เคˆ +completion.workflow.event.repository_dispatch=เค•เคธเฅเคŸเคฎ เคเคชเฅ€เค†เคˆ เค‡เคตเฅ‡เค‚เคŸ +completion.workflow.event.schedule=เค•เฅเคฐเฅ‰เคจ เคŸเคฟเค•. เค˜เคกเคผเฅ€ เค•เคพ เค•เคพเคฎเฅค +completion.workflow.event.status=เคชเฅเคฐเคคเคฟเคฌเคฆเฅเคง เคธเฅเคฅเคฟเคคเคฟ เคฌเคฆเคฒ เค—เคˆ +completion.workflow.event.watch=เคญเค‚เคกเคพเคฐ เคคเคพเคฐเคพเค‚เค•เคฟเคค +completion.workflow.event.workflow_call=เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅ‰เคฒ +completion.workflow.event.workflow_dispatch=เคฎเฅˆเคจเฅเค…เคฒ เคฐเคจ เคฌเคŸเคจ +completion.workflow.event.workflow_run=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคฐเคจ เคฌเคฆเคฒ เค—เคฏเคพ +completion.workflow.eventFilter.types=เค—เคคเคฟเคตเคฟเคงเคฟ เคชเฅเคฐเค•เคพเคฐ เคธเฅ€เคฎเคฟเคค เค•เคฐเฅ‡เค‚ +completion.workflow.eventFilter.branches=เค•เฅ‡เคตเคฒ เคฏเฅ‡ เคถเคพเค–เคพเคเค +completion.workflow.eventFilter.branches-ignore=เค‡เคจ เคถเคพเค–เคพเค“เค‚ เค•เฅ‹ เค›เฅ‹เคกเคผเฅ‡เค‚ +completion.workflow.eventFilter.tags=เค•เฅ‡เคตเคฒ เคฏเฅ‡ เคŸเฅˆเค— +completion.workflow.eventFilter.tags-ignore=เค‡เคจ เคŸเฅˆเค— เค•เฅ‹ เค›เฅ‹เคกเคผเฅ‡เค‚ +completion.workflow.eventFilter.paths=เคฌเคธ เคฏเฅ‡ เคฐเคพเคธเฅเคคเฅ‡ +completion.workflow.eventFilter.paths-ignore=เค‡เคจ เคฐเคพเคธเฅเคคเฅ‹เค‚ เค•เฅ‹ เค›เฅ‹เคกเคผเฅ‡เค‚ +completion.workflow.eventFilter.workflows=เคฆเฅ‡เค–เคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคจเคพเคฎ +completion.workflow.eventFilter.cron=เค•เฅเคฐเฅ‰เคจ เคถเฅ‡เคกเฅเคฏเฅ‚เคฒ. เค›เฅ‹เคŸเฅ€ เค˜เคกเคผเฅ€. +completion.workflow.permission.actions=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคšเคฒเคคเคพ เคนเฅˆ เค”เคฐ เค•เฅเคฐเคฟเคฏเคพ เค•เคฒเคพเค•เฅƒเคคเคฟเคฏเคพเค +completion.workflow.permission.artifact-metadata=เคตเคฟเคฐเฅ‚เคชเคฃ เคธเคพเค•เฅเคทเฅเคฏ เคฎเฅ‡เคŸเคพเคกเฅ‡เคŸเคพ เคฐเคฟเค•เฅ‰เคฐเฅเคก +completion.workflow.permission.attestations=เคตเคฟเคฐเฅ‚เคชเคฃ เคธเคพเค•เฅเคทเฅเคฏ เคธเคคเฅเคฏเคพเคชเคจ +completion.workflow.permission.checks=เคฐเคจ เค”เคฐ เคธเฅเค‡เคŸเฅเคธ เค•เฅ€ เคœเคพเคเคš เค•เคฐเฅ‡เค‚ +completion.workflow.permission.code-quality=เค•เฅ‹เคก เค—เฅเคฃเคตเคคเฅเคคเคพ เคฐเคฟเคชเฅ‹เคฐเฅเคŸ +completion.workflow.permission.contents=เคญเค‚เคกเคพเคฐ เคธเคพเคฎเค—เฅเคฐเฅ€ +completion.workflow.permission.deployments=เคคเฅˆเคจเคพเคคเฅ€ +completion.workflow.permission.discussions=เคตเคฟเคšเคพเคฐ เคตเคฟเคฎเคฐเฅเคถ +completion.workflow.permission.id-token=OpenID Connect เคŸเฅ‹เค•เคจ +completion.workflow.permission.issues=เคฎเฅเคฆเฅเคฆเฅ‡ +completion.workflow.permission.models=GitHub เคฎเฅ‰เคกเคฒ +completion.workflow.permission.packages=GitHub เคชเฅˆเค•เฅ‡เคœ +completion.workflow.permission.pages=GitHub เคชเฅ‡เคœ +completion.workflow.permission.pull-requests=เค…เคจเฅเคฐเฅ‹เคง เค–เฅ€เค‚เคšเฅ‡เค‚ +completion.workflow.permission.security-events=เค•เฅ‹เคก เคธเฅเค•เฅˆเคจเคฟเค‚เค— เค”เคฐ เคธเฅเคฐเค•เฅเคทเคพ เค˜เคŸเคจเคพเคเค +completion.workflow.permission.statuses=เคชเฅเคฐเคคเคฟเคฌเคฆเฅเคง เคธเฅเคฅเคฟเคคเคฟเคฏเคพเค +completion.workflow.permission.vulnerability-alerts=Dependabot เค…เคฒเคฐเฅเคŸ +completion.workflow.permission.value.read=เคชเคนเฅเค‚เคš เคชเคขเคผเฅ‡เค‚ +completion.workflow.permission.value.write=เคฒเคฟเค–เคจเฅ‡ เค•เฅ€ เคชเคนเฅเค‚เคš, เคชเคขเคผเคจเคพ เคถเคพเคฎเคฟเคฒ เคนเฅˆ +completion.workflow.permission.value.none=เค•เฅ‹เคˆ เคชเคนเฅเค‚เคš เคจเคนเฅ€เค‚ +completion.workflow.permission.shorthand.read-all=เคธเคญเฅ€ เค…เคจเฅเคฎเคคเคฟเคฏเคพเค เคชเคขเคผเฅ‡เค‚. เคฌเคกเคผเคพ เค•เคฎเฅเคฌเคฒ. +completion.workflow.permission.shorthand.write-all=เคธเคญเฅ€ เค…เคจเฅเคฎเคคเคฟเคฏเคพเค เคฒเคฟเค–เฅ‡เค‚. เคฌเคกเคผเคพ เคนเคฅเฅŒเคกเคผเคพ. +completion.workflow.permission.shorthand.empty=เคŸเฅ‹เค•เคจ เค…เคจเฅเคฎเคคเคฟเคฏเคพเค เค…เค•เฅเคทเคฎ เค•เคฐเฅ‡เค‚ +completion.workflow.job.name=เค•เคพเคฐเฅเคฏ เคชเฅเคฐเคฆเคฐเฅเคถเคจ เคจเคพเคฎ +completion.workflow.job.permissions=เค•เคพเคฐเฅเคฏ เคŸเฅ‹เค•เคจ เค…เคจเฅเคฎเคคเคฟเคฏเคพเค +completion.workflow.job.needs=เคจเฅŒเค•เคฐเคฟเคฏเฅ‹เค‚ เค•เฅ‡ เคฒเคฟเค เค‡เค‚เคคเคœเคพเคฐ เค•เคฐเคจเคพ เคนเฅ‹เค—เคพ +completion.workflow.job.if=เคจเฅŒเค•เคฐเฅ€ เค•เฅ€ เคธเฅเคฅเคฟเคคเคฟ +completion.workflow.job.runs-on=เคงเคพเคตเค• เคฒเฅ‡เคฌเคฒ เคฏเคพ เคธเคฎเฅ‚เคน +completion.workflow.job.snapshot=เคงเคพเคตเค• เคธเฅเคจเฅˆเคชเคถเฅ‰เคŸ +completion.workflow.job.environment=เคชเคฐเคฟเคจเคฟเคฏเฅ‹เคœเคจ เคตเคพเคคเคพเคตเคฐเคฃ +completion.workflow.job.concurrency=เค•เคพเคฐเฅเคฏ เคธเคฎเคตเคฐเฅเคคเฅ€ เคฒเฅ‰เค• +completion.workflow.job.outputs=เค†เค‰เคŸเคชเฅเคŸ เค…เคจเฅเคฏ เคจเฅŒเค•เคฐเคฟเคฏเคพเค‚ เคชเคขเคผ เคธเค•เคคเฅ€ เคนเฅˆเค‚ +completion.workflow.job.env=เคจเฅŒเค•เคฐเฅ€ เค•เคพ เคฎเคพเคนเฅŒเคฒ เคšเคฐ +completion.workflow.job.defaults=เค•เคพเคฐเฅเคฏ เคกเคฟเคซเคผเฅ‰เคฒเฅเคŸ เคธเฅ‡เคŸเคฟเค‚เค—เฅเคธ +completion.workflow.job.steps=เคšเคฐเคฃ เคธเฅ‚เคšเฅ€. เคตเคพเคธเฅเคคเคตเคฟเค• เค•เคพเคฐเฅเคฏ. +completion.workflow.job.timeout-minutes=เค•เคพเคฐเฅเคฏ เค•เคพ เคธเคฎเคฏ เคฎเคฟเคจเคŸเฅ‹เค‚ เคฎเฅ‡เค‚ เคธเคฎเคพเคชเฅเคค +completion.workflow.job.strategy=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เค”เคฐ เคถเฅ‡เคกเฅเคฏเฅ‚เคฒเคฟเค‚เค— เคฐเคฃเคจเฅ€เคคเคฟ +completion.workflow.job.continue-on-error=เค‡เคธ เค•เคพเคฎ เค•เฅ‹ เคงเฅ€เคฐเฅ‡ เคธเฅ‡ เค…เคธเคซเคฒ เคนเฅ‹เคจเฅ‡ เคฆเฅ‹ +completion.workflow.job.container=เค‡เคธ เค•เคพเคฐเฅเคฏ เค•เฅ‡ เคฒเคฟเค เค•เค‚เคŸเฅ‡เคจเคฐ +completion.workflow.job.services=เคธเคพเค‡เคกเค•เคพเคฐ เคธเฅ‡เคตเคพ เค•เค‚เคŸเฅ‡เคจเคฐ +completion.workflow.job.uses=เค•เฅ‰เคฒ เค•เคฐเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เคชเฅเคจ: เคชเฅเคฐเคฏเฅ‹เคœเฅเคฏ เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ +completion.workflow.job.with=เคฌเฅเคฒเคพเค เค—เค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅ‡ เคฒเคฟเค เค‡เคจเคชเฅเคŸ +completion.workflow.job.secrets=เคฌเฅเคฒเคพเค เค—เค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅ‡ เคฒเคฟเค เคฐเคนเคธเฅเคฏ +completion.workflow.defaultsRun.shell=เคฐเคจ เคšเคฐเคฃเฅ‹เค‚ เค•เฅ‡ เคฒเคฟเค เคกเคฟเคซเคผเฅ‰เคฒเฅเคŸ เคถเฅ‡เคฒ +completion.workflow.defaultsRun.working-directory=เคกเคฟเคซเคผเฅ‰เคฒเฅเคŸ เค•เคพเคฐเฅเคฏเคถเฅ€เคฒ เคจเคฟเคฐเฅเคฆเฅ‡เคถเคฟเค•เคพ +completion.workflow.concurrency.group=เค•เคคเคพเคฐเคฌเคฆเฅเคง เคฐเคจ เค•เฅ‡ เคฒเคฟเค เคฒเฅ‰เค• เคจเคพเคฎ +completion.workflow.concurrency.cancel-in-progress=เคชเฅเคฐเคพเคจเฅ‡ เคฎเคฟเคฒเคพเคจ เคฐเคจ เคฐเคฆเฅเคฆ เค•เคฐเฅ‡เค‚ +completion.workflow.environment.name=เคชเคฐเฅเคฏเคพเคตเคฐเคฃ เค•เคพ เคจเคพเคฎ +completion.workflow.environment.url=เคชเคฐเฅเคฏเคพเคตเคฐเคฃ URL +completion.workflow.strategy.matrix=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เค…เค•เฅเคท เค”เคฐ เคตเฅ‡เคฐเคฟเคเค‚เคŸ +completion.workflow.strategy.fail-fast=เคตเคฟเคซเคฒเคคเคพ เคชเคฐ เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคญเคพเคˆ-เคฌเคนเคจเฅ‹เค‚ เค•เฅ‹ เคฐเคฆเฅเคฆ เค•เคฐเฅ‡เค‚ +completion.workflow.strategy.max-parallel=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคธเคฎเคพเค‚เคคเคฐเคคเคพ เค•เฅˆเคช +completion.workflow.matrix.include=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคธเค‚เคฏเฅ‹เคœเคจ เคœเฅ‹เคกเคผเฅ‡เค‚ +completion.workflow.matrix.exclude=เคฎเฅˆเคŸเฅเคฐเคฟเค•เฅเคธ เคธเค‚เคฏเฅ‹เคœเคจ เคนเคŸเคพเคเค +completion.workflow.step.id=เคธเค‚เคฆเคฐเฅเคญ เค•เฅ‡ เคฒเคฟเค เคšเคฐเคฃ เค†เคˆเคกเฅ€ +completion.workflow.step.if=เคšเคฐเคฃ เคถเคฐเฅเคค +completion.workflow.step.name=เคšเคฐเคฃ เคชเฅเคฐเคฆเคฐเฅเคถเคจ เคจเคพเคฎ +completion.workflow.step.uses=เคšเคฒเคพเคจเฅ‡ เค•เฅ€ เค•เฅเคฐเคฟเคฏเคพ +completion.workflow.step.run=เคšเคฒเคพเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เคถเฅ‡เคฒ เคธเฅเค•เฅเคฐเคฟเคชเฅเคŸ +completion.workflow.step.shell=เค‡เคธ เคฐเคจ เคšเคฐเคฃ เค•เฅ‡ เคฒเคฟเค เคถเฅ‡เคฒ +completion.workflow.step.with=เค•เคพเคฐเฅเคฐเคตเคพเคˆ เค‡เคจเคชเฅเคŸ +completion.workflow.step.env=เคšเคฐเคฃ เคชเคฐเฅเคฏเคพเคตเคฐเคฃ เคšเคฐ +completion.workflow.step.continue-on-error=เค‡เคธ เค•เคฆเคฎ เค•เฅ‹ เคงเฅ€เคฐเฅ‡ เคธเฅ‡ เคตเคฟเคซเคฒ เคนเฅ‹เคจเฅ‡ เคฆเฅ‡เค‚ +completion.workflow.step.timeout-minutes=เคธเฅเคŸเฅ‡เคช เคŸเคพเค‡เคฎเค†เค‰เคŸ เคฎเคฟเคจเคŸเฅ‹เค‚ เคฎเฅ‡เค‚ +completion.workflow.step.working-directory=เคšเคฐเคฃ เค•เคพเคฐเฅเคฏเคถเฅ€เคฒ เคจเคฟเคฐเฅเคฆเฅ‡เคถเคฟเค•เคพ +completion.workflow.container.image=เค•เค‚เคŸเฅ‡เคจเคฐ เค›เคตเคฟ +completion.workflow.container.credentials=เคฐเคœเคฟเคธเฅเคŸเฅเคฐเฅ€ เค•เฅเคฐเฅ‡เคกเฅ‡เค‚เคถเคฟเคฏเคฒ +completion.workflow.container.env=เค•เค‚เคŸเฅ‡เคจเคฐ เคชเคฐเฅเคฏเคพเคตเคฐเคฃ เคšเคฐ +completion.workflow.container.ports=เค‰เคœเคพเค—เคฐ เค•เคฐเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เคฌเค‚เคฆเคฐเค—เคพเคน +completion.workflow.container.volumes=เคฎเคพเค‰เค‚เคŸ เค•เคฐเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เคตเฅ‰เคฒเฅเคฏเฅ‚เคฎ +completion.workflow.container.options=Docker เคตเคฟเค•เคฒเฅเคช เคฌเคจเคพเคเค‚ +completion.workflow.service.image=เคธเฅ‡เคตเคพ เค•เค‚เคŸเฅ‡เคจเคฐ เค›เคตเคฟ +completion.workflow.service.credentials=เคฐเคœเคฟเคธเฅเคŸเฅเคฐเฅ€ เค•เฅเคฐเฅ‡เคกเฅ‡เค‚เคถเคฟเคฏเคฒ +completion.workflow.service.env=เคธเฅ‡เคตเคพ เคชเคฐเคฟเคตเฅ‡เคถ เคšเคฐ +completion.workflow.service.ports=เคธเฅ‡เคตเคพ เคฌเค‚เคฆเคฐเค—เคพเคน +completion.workflow.service.volumes=เคธเฅ‡เคตเคพ เค•เฅ€ เคฎเคพเคคเฅเคฐเคพ +completion.workflow.service.options=Docker เคตเคฟเค•เคฒเฅเคช เคฌเคจเคพเคเค‚ +completion.workflow.credentials.username=เคฐเคœเคฟเคธเฅเคŸเฅเคฐเฅ€ เค‰เคชเคฏเฅ‹เค•เฅเคคเคพเคจเคพเคฎ +completion.workflow.credentials.password=เคฐเคœเคฟเคธเฅเคŸเฅเคฐเฅ€ เคชเคพเคธเคตเคฐเฅเคก เคฏเคพ เคŸเฅ‹เค•เคจ +completion.workflow.inputType.string=เคชเคพเค  เค‡เคจเคชเฅเคŸ +completion.workflow.inputType.boolean=เคธเคนเฅ€ เคฏเคพ เค—เคผเคฒเคค เค‡เคจเคชเฅเคŸ +completion.workflow.inputType.choice=เคกเฅเคฐเฅ‰เคชเคกเคพเค‰เคจ เคตเคฟเค•เคฒเฅเคช เค‡เคจเคชเฅเคŸ +completion.workflow.inputType.number=เคธเค‚เค–เฅเคฏเคพ เค‡เคจเคชเฅเคŸ +completion.workflow.inputType.environment=เคชเคฐเฅเคฏเคพเคตเคฐเคฃ เคชเคฟเค•เคฐ เค‡เคจเคชเฅเคŸ +completion.workflow.boolean.true=เคนเคพเค. เค‡เคธเฅ‡ เคชเคฒเคŸเฅ‡เค‚. +completion.workflow.boolean.false=เคจเคนเฅ€เค‚, เค‡เคธเฅ‡ เค…เค‚เคงเฅ‡เคฐเคพ เคฐเค–เฅ‹. +completion.workflow.runner.ubuntu-latest=เคจเคตเฅ€เคจเคคเคฎ Ubuntu เคงเคพเคตเค• +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 เคงเคพเคตเค• +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 เคงเคพเคตเค• +completion.workflow.runner.windows-latest=เคจเคตเฅ€เคจเคคเคฎ Windows เคงเคพเคตเค• +completion.workflow.runner.windows-2025=Windows เคธเคฐเฅเคตเคฐ 2025 เคงเคพเคตเค• +completion.workflow.runner.windows-2022=Windows เคธเคฐเฅเคตเคฐ 2022 เคงเคพเคตเค• +completion.workflow.runner.macos-latest=เคจเคตเฅ€เคจเคคเคฎ macOS เคงเคพเคตเค• +completion.workflow.runner.macos-15=macOS 15 เคงเคพเคตเค• +completion.workflow.runner.macos-14=macOS 14 เคงเคพเคตเค• +completion.workflow.runner.self-hosted=เค†เคชเค•เคพ เค…เคชเคจเคพ เคงเคพเคตเค•. เค†เคชเค•เคพ เคธเคฐเฅเค•เคธ. +completion.steps.outputs=เคšเคฐเคฃ เค•เฅ‡ เคฒเคฟเค เคชเคฐเคฟเคญเคพเคทเคฟเคค เค†เค‰เคŸเคชเฅเคŸ เค•เคพ เคธเฅ‡เคŸเฅค +completion.steps.conclusion=เคœเคพเคฐเฅ€ เคฐเค–เฅ‡เค‚-เคคเฅเคฐเฅเคŸเคฟ เคฒเคพเค—เฅ‚ เคนเฅ‹เคจเฅ‡ เค•เฅ‡ เคฌเคพเคฆ เคชเฅ‚เคฐเฅเคฃ เคšเคฐเคฃ เค•เคพ เคชเคฐเคฟเคฃเคพเคฎ เคฒเคพเค—เฅ‚ เคนเฅ‹เคคเคพ เคนเฅˆเฅค +completion.steps.outcome=เคœเคพเคฐเฅ€ เคฐเค–เฅ‡เค‚-เคคเฅเคฐเฅเคŸเคฟ เคฒเคพเค—เฅ‚ เคนเฅ‹เคจเฅ‡ เคธเฅ‡ เคชเคนเคฒเฅ‡ เคชเฅ‚เคฐเฅเคฃ เค•เคฟเค เค—เค เคšเคฐเคฃ เค•เคพ เคชเคฐเคฟเคฃเคพเคฎเฅค +completion.jobs.outputs=เค•เคพเคฐเฅเคฏ เค•เฅ‡ เคฒเคฟเค เคชเคฐเคฟเคญเคพเคทเคฟเคค เค†เค‰เคŸเคชเฅเคŸ เค•เคพ เคธเฅ‡เคŸเฅค +completion.jobs.result=เค•เคพเคฐเฅเคฏ เค•เคพ เคชเคฐเคฟเคฃเคพเคฎ. +settings.displayName=GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ +settings.language.label=เคญเคพเคทเคพ: +settings.language.system=IDE/เคธเคฟเคธเฅเคŸเคฎ เคกเคฟเคซเคผเฅ‰เคฒเฅเคŸ +settings.cache.title=เคเค•เฅเคถเคจ เค•เฅˆเคถ +settings.cache.column.key=เค•เฅˆเคถ เค•เฅเค‚เคœเฅ€ +settings.cache.column.name=เคจเคพเคฎ +settings.cache.column.kind=เคฆเคฏเคพเคฒเฅ +settings.cache.column.state=เคฐเคพเคœเฅเคฏ +settings.cache.column.expires=เคธเคฎเคพเคชเฅเคค เคนเฅ‹ เคฐเคนเคพ เคนเฅˆ +settings.cache.kind.local=เคธเฅเคฅเคพเคจเฅ€เคฏ +settings.cache.kind.remote=เคฆเฅ‚เคฐเคธเฅเคฅ +settings.cache.state.resolved=เคธเคฎเคพเคงเคพเคจ เคนเฅ‹ เค—เคฏเคพ +settings.cache.state.pending=เคฒเค‚เคฌเคฟเคค +settings.cache.state.expired=เคฌเคพเคธเฅ€ +settings.cache.state.suppressed=เคฆเคฌเคพ เคฆเคฟเคฏเคพ เค—เคฏเคพ +settings.cache.refresh=Refresh เคคเคพเคฒเคฟเค•เคพ +settings.cache.deleteSelected=เคšเคฏเคจเคฟเคค เคนเคŸเคพเคเค +settings.cache.deleteAll=เคธเคญเฅ€ เคนเคŸเคพเคเค‚ +settings.cache.export=เคจเคฟเคฐเฅเคฏเคพเคค เค•เคฐเฅ‡เค‚ +settings.cache.import=เค†เคฏเคพเคค เค•เคฐเฅ‡เค‚ +settings.cache.summary=เค•เฅˆเคถ: {0} เคชเฅเคฐเคตเคฟเคทเฅเคŸเคฟเคฏเคพเค, {1} เคนเคฒ, {2} เคฐเคฟเคฎเฅ‹เคŸ, {3} เคฌเคพเคธเฅ€, {4} เคฎเฅเคฏเฅ‚เคŸเฅค เค•เฅˆเคถ: {5} KB. +settings.cache.noneSelected=เคชเคนเคฒเฅ‡ เค•เฅˆเคถ เคชเค‚เค•เฅเคคเคฟเคฏเคพเค เคšเฅเคจเฅ‡เค‚. เคเคพเคกเคผเฅ‚ เค…เคจเฅเคฎเคพเคจ เคฒเค—เคพเคจเฅ‡ เคธเฅ‡ เค‡เคจเค•เคพเคฐ เค•เคฐเคคเฅ€ เคนเฅˆเฅค +settings.cache.deleteSelected.done={0} เค•เฅˆเคถ เคชเฅเคฐเคตเคฟเคทเฅเคŸเคฟเคฏเคพเค เคนเคŸเคพเคˆ เค—เคˆเค‚เฅค เค›เฅ‹เคŸเฅ‡-เค›เฅ‹เคŸเฅ‡ เคงเฅ‚เคฒ เค•เฅ‡ เคฌเคพเคฆเคฒ เคธเคฎเคพเคนเคฟเคค เคนเฅ‹ เค—เคเฅค +settings.cache.deleteAll.confirm=เคธเคญเฅ€ GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅˆเคถ เคชเฅเคฐเคตเคฟเคทเฅเคŸเคฟเคฏเคพเค เคนเคŸเคพเคเค? +settings.cache.deleteAll.done=เคธเคญเฅ€ เค•เฅˆเคถ เคชเฅเคฐเคตเคฟเคทเฅเคŸเคฟเคฏเคพเค เคนเคŸเคพ เคฆเฅ€ เค—เคˆเค‚. เค•เฅˆเคถ เค…เคฌ เคธเค‚เคฆเคฟเค—เฅเคง เคฐเฅ‚เคช เคธเฅ‡ เคถเคพเค‚เคค เคนเฅˆเฅค +settings.cache.export.done=เคจเคฟเคฐเฅเคฏเคพเคคเคฟเคค {0} เค•เฅˆเคถ เคชเฅเคฐเคตเคฟเคทเฅเคŸเคฟเคฏเคพเคเฅค เคชเคฟเค‚เคœเคฐเฅ‡ เคฎเฅ‡เค‚ เคฌเค‚เคฆ เค›เฅ‹เคŸเคพ เคธเค‚เค—เฅเคฐเคนเคฟเคค เคœเคพเคจเคตเคฐเฅค +settings.cache.import.done=เค†เคฏเคพเคคเคฟเคค เค•เฅˆเคถ เคชเฅเคฐเคตเคฟเคทเฅเคŸเคฟเคฏเคพเคเฅค เคชเฅเคฐเคพเคฒเฅ‡เค– เคœเคพเคจเคตเคฐ เคจเฅ‡ เคตเฅเคฏเคตเคนเคพเคฐ เค•เคฟเคฏเคพเฅค +settings.cache.import.unsupported=เค…เคธเคฎเคฐเฅเคฅเคฟเคค GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅˆเคถ เคซเคผเคพเค‡เคฒเฅค +settings.cache.import.brokenLine=เคŸเฅ‚เคŸเฅ€ เคนเฅเคˆ GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅˆเคถ เคฒเคพเค‡เคจเฅค +settings.cache.import.brokenKey=เคŸเฅ‚เคŸเฅ€ เคนเฅเคˆ GitHub เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅˆเคถ เค•เฅเค‚เคœเฅ€เฅค +settings.support.button=เค‡เคธ เคชเฅเคฒเค—เค‡เคจ เค•เคพ เคธเคฎเคฐเฅเคฅเคจ เค•เคฐเฅ‡เค‚ +settings.support.tooltip=เคธเคนเคพเคฏเคคเคพ เคชเฅƒเคทเฅเค  เค–เฅ‹เคฒเฅ‡เค‚ +settings.support.line.0=เคฌเคฟเคฒเฅเคก เคซเคฐเฅเคจเฅ‡เคธ เค•เฅ‹ เค–เคฟเคฒเคพเคเค‚ +settings.support.line.1=เคชเคพเคฐเฅเคธเคฐ เค•เฅ‰เคซเคผเฅ€ เค–เคฐเฅ€เคฆเฅ‡เค‚ +settings.support.line.2=เค•เคฎ เคชเฅเคฐเฅ‡เคคเคตเคพเคงเคฟเคค เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เค•เฅ‹ เคชเฅเคฐเคพเคฏเฅ‹เคœเคฟเคค เค•เคฐเฅ‡เค‚ +workflow.run.jobs.title=เค•เคพเคฐเฅเคฏเคชเฅเคฐเคตเคพเคน เคจเฅŒเค•เคฐเคฟเคฏเคพเค +workflow.run.jobs.root=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคšเคฒเคพเคเค +workflow.run.jobs.description=GitHub เค•เฅเคฐเคฟเคฏเคพเคเค เคœเฅ‰เคฌ เคŸเฅเคฐเฅ€ เค”เคฐ เคšเคฏเคจเคฟเคค เคœเฅ‰เคฌ เคฒเฅ‰เค— +workflow.run.tree.done=เค•เคฟเคฏเคพ +workflow.run.tree.failed=เคตเคฟเคซเคฒ +workflow.run.tree.skipped=เค›เฅ‹เคกเคผ เคฆเคฟเคฏเคพ เค—เคฏเคพ +workflow.run.tree.warn=เคšเฅ‡เคคเคพเคตเคจเฅ€ +workflow.run.tree.err=เค—เคผเคฒเคคเฅ€ +workflow.run.delete.tooltip=เคšเคฒเคพเคเค เคนเคŸเคพเคเค +workflow.run.delete.noRun=เค…เคญเฅ€ เคคเค• เค•เฅ‹เคˆ เคฐเคจ เค†เคˆเคกเฅ€ เคจเคนเฅ€เค‚ เคนเฅˆ. +workflow.run.delete.requested=เคฐเคจ {0} เค•เฅ‹ เคนเคŸเคพเคฏเคพ เคœเคพ เคฐเคนเคพ เคนเฅˆเฅค +workflow.run.delete.done={0} เคšเคฒเคพเคเค เคนเคŸเคพ เคฆเคฟเคฏเคพ เค—เคฏเคพเฅค +workflow.run.delete.http=HTTP {0} เคนเคŸเคพเคเค‚เฅค เค‰เคซเคผ. +workflow.run.delete.failed=เคซเคผเคฟเคœเคผเคฒเฅเคก เคนเคŸเคพเคเค‚: {0} +workflow.run.rerun.all.tooltip=เคตเคฐเฅเค•เคซเคผเฅเคฒเฅ‹ เคชเฅเคจเคƒ เคšเคฒเคพเคเค +workflow.run.rerun.failed.tooltip=เคตเคฟเคซเคฒ เคจเฅŒเค•เคฐเคฟเคฏเฅ‹เค‚ เค•เฅ‹ เคชเฅเคจเคƒ เคšเคฒเคพเคเค +workflow.run.rerun.noRun=เค…เคญเฅ€ เคคเค• เค•เฅ‹เคˆ เคฐเคจ เค†เคˆเคกเฅ€ เคจเคนเฅ€เค‚ เคนเฅˆ. +workflow.run.rerun.all.requested=เคชเฅเคจ: เคšเคฒเคพเคจเฅ‡ เค•เคพ เค…เคจเฅเคฐเฅ‹เคง เค•เคฟเคฏเคพ เค—เคฏเคพ: {0}. +workflow.run.rerun.failed.requested=เคตเคฟเคซเคฒ เคจเฅŒเค•เคฐเคฟเคฏเฅ‹เค‚ เค•เคพ เค…เคจเฅเคฐเฅ‹เคง: {0}เฅค +workflow.run.rerun.all.done=เคชเฅเคจ: เคšเคฒเคพเคเค เคชเค‚เค•เฅเคคเคฟเคฌเคฆเฅเคง: {0}. +workflow.run.rerun.failed.done=เคตเคฟเคซเคฒ เคจเฅŒเค•เคฐเคฟเคฏเคพเค‚ เค•เคคเคพเคฐเคฌเคฆเฅเคง: {0}. +workflow.run.rerun.http=HTTP {0} เค•เฅ‹ เคชเฅเคจเคƒ เคšเคฒเคพเคเคเฅค เค‰เคซเคผ. +workflow.run.rerun.failed=เคชเฅเคจเคƒ เคšเคฒเคพเคเค เคตเคฟเคซเคฒ: {0} +workflow.run.download.log.tooltip=เค•เคพเคฐเฅเคฏ เคฒเฅ‰เค— เคธเคนเฅ‡เคœเฅ‡เค‚ +workflow.run.download.artifacts.tooltip=เค•เคฒเคพเค•เฅƒเคคเคฟเคฏเคพเค เคกเคพเค‰เคจเคฒเฅ‹เคก เค•เคฐเฅ‡เค‚ +workflow.run.download.noRun=เค…เคญเฅ€ เคคเค• เค•เฅ‹เคˆ เคฐเคจ เค†เคˆเคกเฅ€ เคจเคนเฅ€เค‚ เคนเฅˆ. +workflow.run.download.log.requested={0} เค•เฅ‡ เคฒเคฟเค เคฒเฅ‰เค— เคฒเคพเคฏเคพ เคœเคพ เคฐเคนเคพ เคนเฅˆเฅค +workflow.run.download.log.done=เคฒเฅ‰เค— เคธเคนเฅ‡เคœเคพ เค—เคฏเคพ: {0}. +workflow.run.download.artifacts.requested=เค•เคฒเคพเค•เฅƒเคคเคฟเคฏเคพเค เคฒเคพเคฏเฅ€ เคœเคพ เคฐเคนเฅ€ เคนเฅˆเค‚เฅค +workflow.run.download.artifacts.empty=เค•เฅ‹เคˆ เค•เคฒเคพเค•เฅƒเคคเคฟเคฏเคพเค เคจเคนเฅ€เค‚. เค›เฅ‹เคŸเคพ เคถเฅ‚เคจเฅเคฏ. +workflow.run.download.artifact.expired=เค•เคฒเคพเค•เฅƒเคคเคฟ เค•เฅ€ เคธเคฎเคฏ เคธเฅ€เคฎเคพ เคธเคฎเคพเคชเฅเคค เคนเฅ‹ เค—เคˆ: {0}เฅค +workflow.run.download.artifact.done=เค•เคฒเคพเค•เฅƒเคคเคฟเคฏเคพเค เคธเคนเฅ‡เคœเฅ€ เค—เคˆเค‚: {0} -> {1}เฅค +workflow.run.download.failed=เคกเคพเค‰เคจเคฒเฅ‹เคก เคตเคฟเคซเคฒ: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_id.properties b/src/main/resources/messages/GitHubWorkflowBundle_id.properties new file mode 100644 index 0000000..e333d8d --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_id.properties @@ -0,0 +1,442 @@ +plugin.name=Alur Kerja GitHub +plugin.description=Dukungan untuk file alur kerja Tindakan GitHub +group.GitHubWorkflow.Tools.text=Alur Kerja GitHub +group.GitHubWorkflow.Tools.description=Alat plugin Alur Kerja GitHub +action.GitHubWorkflow.RefreshActionCache.text=Cache Tindakan Refresh +action.GitHubWorkflow.RefreshActionCache.description=Refresh menyelesaikan Tindakan GitHub jarak jauh dan metadata alur kerja yang dapat digunakan kembali +action.GitHubWorkflow.RestoreActionWarnings.text=Kembalikan Peringatan Tindakan +action.GitHubWorkflow.RestoreActionWarnings.description=Pulihkan peringatan validasi tindakan, input, dan output yang disembunyikan +action.GitHubWorkflow.ClearActionCache.text=Hapus Cache Tindakan +action.GitHubWorkflow.ClearActionCache.description=Hapus Tindakan GitHub yang di-cache dan metadata alur kerja yang dapat digunakan kembali +notification.cache.cleared=Menghapus entri Alur Kerja GitHub yang di-cache {0}. +notification.cache.refresh.started=Refreshing {0} menyimpan cache entri Alur Kerja GitHub jarak jauh. +notification.warnings.restored=Peringatan yang dipulihkan untuk entri Alur Kerja {0} GitHub. +workflow.run.configuration.display=Alur Kerja GitHub +workflow.run.configuration.description=Kirim dan ikuti alur kerja Tindakan GitHub yang berjalan +workflow.run.configuration.name=Alur Kerja GitHub: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=Pemilik +workflow.run.field.repo=Gudang +workflow.run.field.workflow=File alur kerja +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Penggantian token env var +workflow.run.inputs.title=Input workflow_dispatch (kunci=nilai) +workflow.run.error.apiUrl=GitHub API URL diperlukan. +workflow.run.error.repository=Pemilik dan nama repositori GitHub wajib diisi. +workflow.run.error.workflow=File alur kerja diperlukan. +workflow.run.error.ref=Diperlukan referensi cabang atau tag. +workflow.run.error.inputs=GitHub workflow_dispatch mendukung maksimal 25 input. +workflow.run.gutter.stop=Hentikan alur kerja yang dijalankan +workflow.run.gutter.stop.text=Hentikan alur kerja yang dijalankan +workflow.run.gutter.stop.description=Batalkan proses ini +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=menjalankan: +workflow.log.warning=peringatan: +workflow.log.error=kesalahan: +workflow.run.cancel.requested=Pembatalan yang diminta: {0}. +workflow.run.stop.before.id=Berhenti diminta. Belum ada id yang dijalankan. +workflow.run.cancel.http=Batalkan HTTP {0}. Aduh. +workflow.run.cancel.failed=Pembatalan gagal: {0} +workflow.run.interrupted=Terganggu. +workflow.run.link=Jalankan: {0} +workflow.run.discovery=Jalankan diterima. ID lari berburu. +workflow.run.discovery.none=Belum ada id yang dijalankan. Tab tindakan tahu lebih banyak. +workflow.run.status=Status: {0}{1} +workflow.run.job.main=Pekerjaan: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Status: {0} {1}{2}{3} +workflow.run.logs.later=Log akan muncul ketika GitHub menerbitkannya. +workflow.run.job.logs.later=Pekerjaan {0}: {1} +workflow.run.log.failed=Gagal mengunduh log: {0} +workflow.run.log.failed.job=Pengunduhan log gagal untuk {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Pekerjaan: {0} +workflow.run.job.fallbackName=Pekerjaan {0} +workflow.run.overview=Alur kerja dijalankan {0} {1}/{2} selesai, {3} berjalan +workflow.run.state.ok=[Oke] +workflow.run.state.fail=[GAGAL] +workflow.run.state.running=[JALANKAN] +workflow.run.state.waiting=[TUNGGU] +workflow.run.dispatch.verbs=Priming|Antrian|Pemanggilan|Peluncuran|Booting +workflow.run.dispatch.objects=alur kerja|otomatisasi|pipa|jalankan +workflow.run.dispatch={0} {1} {2}{3} untuk {4} di {5}. +workflow.run.notification.auth=Pengiriman alur kerja GitHub memerlukan akun GitHub yang diautentikasi. Tambahkan atau segarkan akun di {0}. +workflow.run.notification.openSettings=Buka Pengaturan GitHub +workflow.cache.progress.title=Menyelesaikan tindakan GitHub +workflow.cache.progress.text=Menyelesaikan {0} {1} +workflow.cache.kind.action=tindakan +workflow.cache.kind.workflow=alur kerja +inspection.parameter.input=masukan +inspection.parameter.secret=rahasia +inspection.action.delete.invalid=Hapus {0} [{1}] yang tidak valid +inspection.action.update.major=Perbarui tindakan [{0}] menjadi [{1}] +inspection.warning.toggle=Alihkan peringatan [{0}] untuk [{1}] +inspection.warning.on=pada +inspection.warning.off=mati +inspection.statement.incomplete=Pernyataan tidak lengkap [{0}] +inspection.invalid.suffix.remove=Hapus akhiran yang tidak valid [{0}] +inspection.replace.with=Ganti dengan [{0}] +inspection.invalid.remove=Hapus [{0}] yang tidak valid +inspection.workflow.syntax.unknownTopLevelKey=Kunci alur kerja tidak dikenal [{0}] +inspection.workflow.syntax.unknownEventKey=Peristiwa alur kerja tidak diketahui [{0}] +inspection.workflow.syntax.unknownTriggerKey=Kunci pemicu tidak dikenal [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Filter pemicu tidak diketahui [{0}] +inspection.workflow.syntax.unknownTriggerValue=Nilai pemicu tidak diketahui [{0}] +inspection.workflow.syntax.unknownPermission=Izin tidak diketahui [{0}] +inspection.workflow.syntax.unknownPermissionValue=Nilai izin tidak diketahui [{0}] +inspection.workflow.syntax.unknownJobKey=Kunci pekerjaan tidak diketahui [{0}] +inspection.workflow.syntax.unknownStepKey=Kunci langkah tidak diketahui [{0}] +inspection.action.reload=Muat ulang [{0}] +inspection.action.unresolved=[{0}] yang belum terselesaikan - periksa akses akun GitHub, izin repositori pribadi, batas kapasitas, referensi yang hilang, atau metadata tindakan/alur kerja yang hilang +inspection.action.jump=Lompat ke file [{0}] +inspection.output.unused=Tidak digunakan [{0}] +inspection.secret.invalid.if=Hapus [{0}] - Rahasia tidak valid dalam pernyataan `if` +inspection.secret.replace.runtime=Ganti [{0}] dengan [{1}] - jika tidak disediakan saat runtime +inspection.needs.invalid.job=Hapus jobId [{0}] yang tidak valid - jobId ini tidak cocok dengan pekerjaan sebelumnya +documentation.description=Deskripsi: {0} +documentation.type=Jenis: {0} +documentation.required=Diperlukan: {0} +documentation.default=Bawaan: {0} +documentation.deprecated=Tidak digunakan lagi: {0} +documentation.open.declaration=Deklarasi terbuka ({0}) +documentation.input.label=Masukan +documentation.secret.label=Rahasia +documentation.env.label=Variabel lingkungan +documentation.matrix.label=Properti matriks +documentation.need.label=Pekerjaan yang dibutuhkan +documentation.need.description=Ketergantungan pekerjaan langsung. +documentation.needOutput.label=Output pekerjaan yang dibutuhkan +documentation.reusableJob.label=Pekerjaan alur kerja yang dapat digunakan kembali +documentation.reusableJob.description=Pekerjaan dideklarasikan dalam alur kerja yang dapat digunakan kembali ini. +documentation.reusableJobOutput.label=Output pekerjaan alur kerja yang dapat digunakan kembali +documentation.service.label=Wadah layanan +documentation.servicePort.label=Pelabuhan layanan +documentation.container.label=Wadah pekerjaan +documentation.symbol.label=Simbol alur kerja +documentation.symbol.description=Ekspresi alur kerja terselesaikan. +documentation.workflowOutput.label=Keluaran alur kerja +documentation.jobOutput.label=Keluaran pekerjaan +documentation.action.label=Tindakan +documentation.externalAction.label=Tindakan eksternal +documentation.reusableWorkflow.label=Alur kerja yang dapat digunakan kembali +documentation.resolvedFrom=diselesaikan dari {0} +documentation.notResolved=belum terselesaikan +documentation.inputs.title=masukan +documentation.outputs.title=Keluaran +documentation.secrets.title=Rahasia +documentation.value.label=Nilai +documentation.step.title=Langkah {0} +documentation.name.label=Nama +documentation.uses.label=Kegunaan +documentation.run.label=Jalankan +documentation.description.label=Deskripsi +documentation.step.label=Langkah +documentation.source.label=Sumber +documentation.stepOutput.label=Keluaran langkah +documentation.context.github=konteks github +documentation.context.github.description=Informasi tentang alur kerja yang dijalankan dan kejadian saat ini. +documentation.context.gitea=konteks gitea +documentation.context.gitea.description=Alias โ€‹โ€‹yang kompatibel dengan Gitea untuk konteks Tindakan GitHub. +documentation.context.inputs=konteks masukan +documentation.context.inputs.description=Input alur kerja, pengiriman, atau tindakan tersedia di sini. +documentation.context.secrets=konteks rahasia +documentation.context.secrets.description=Nilai rahasia tersedia untuk alur kerja ini atau panggilan alur kerja yang dapat digunakan kembali. +documentation.context.env=konteks env +documentation.context.env.description=Variabel lingkungan terlihat di lokasi ini. +documentation.context.matrix=konteks matriks +documentation.context.matrix.description=Nilai matriks untuk pekerjaan saat ini. +documentation.context.steps=konteks langkah +documentation.context.steps.description=Langkah-langkah sebelumnya dalam pekerjaan saat ini, termasuk keluaran dan status. +documentation.context.needs=membutuhkan konteks +documentation.context.needs.description=Ketergantungan pekerjaan langsung dan keluaran/hasilnya. +documentation.context.jobs=konteks pekerjaan +documentation.context.jobs.description=Pekerjaan dan keluaran alur kerja yang dapat digunakan kembali. +documentation.context.outputs=keluaran +documentation.context.outputs.description=Nilai keluaran yang diekspos oleh langkah atau pekerjaan ini. +documentation.context.result=hasil +documentation.context.result.description=Hasil pekerjaan: berhasil, gagal, dibatalkan, atau dilewati. +documentation.context.outcome=hasil +documentation.context.outcome.description=Hasil langkah sebelum kesalahan lanjutan diterapkan. +documentation.context.conclusion=kesimpulan +documentation.context.conclusion.description=Hasil langkah setelah kesalahan lanjutan diterapkan. +error.report.action=Laporkan Pengecualian +error.report.description=Deskripsi +error.report.steps=Langkah-Langkah Reproduksi +error.report.sample=Harap berikan contoh kode jika ada +error.report.message=Pesan +error.report.runtime=Informasi Waktu Proses +error.report.pluginVersion=Versi plugin: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=pelacakan tumpukan +completion.shell.bash=cangkang Bash. Menggunakan bash pada runner Linux dan macOS, dan Git untuk bash Windows pada runner Windows. +completion.shell.sh=Penggantian cangkang POSIX. +completion.shell.pwsh=Inti PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Prompt perintah Windows. +completion.shell.python=Pelari perintah Python. +completion.runner.name=Nama pelari yang melaksanakan pekerjaan. +completion.runner.os=Sistem operasi pelari yang menjalankan pekerjaan. Nilai yang mungkin adalah Linux, Windows, atau macOS. +completion.runner.arch=Arsitektur pelari yang melaksanakan pekerjaan. Nilai yang mungkin adalah X86, X64, ARM, atau ARM64. +completion.runner.temp=Jalur ke direktori sementara di runner. Direktori ini dikosongkan pada awal dan akhir setiap pekerjaan. Perhatikan bahwa file tidak akan dihapus jika akun pengguna pelari tidak memiliki izin untuk menghapusnya. +completion.runner.toolCache=Jalur ke direktori yang berisi alat prainstal untuk runner yang dihosting GitHub. +completion.runner.debug=Ini disetel hanya jika pencatatan debug diaktifkan, dan selalu bernilai 1. +completion.runner.environment=Lingkungan pelari yang melaksanakan pekerjaan. Nilai yang mungkin adalah yang dihosting di github atau dihosting sendiri. +completion.job.status=Status pekerjaan saat ini. +completion.job.checkRunId=ID proses pemeriksaan dari pekerjaan saat ini. +completion.job.container=Informasi tentang wadah pekerjaan. +completion.job.services=Kontainer layanan yang dibuat untuk suatu pekerjaan. +completion.job.workflowRef=Referensi lengkap file alur kerja yang mendefinisikan pekerjaan saat ini. +completion.job.workflowSha=Penerapan SHA dari file alur kerja yang menentukan pekerjaan saat ini. +completion.job.workflowRepository=Pemilik/repo repositori yang berisi file alur kerja yang mendefinisikan pekerjaan saat ini. +completion.job.workflowFilePath=Jalur file alur kerja, relatif terhadap akar repositori. +completion.job.containerField=Bidang kontainer pekerjaan +completion.job.service=Layanan pekerjaan +completion.job.serviceField=Bidang layanan pekerjaan +completion.job.mappedServicePort=Port layanan yang dipetakan +completion.strategy.failFast=Apakah semua pekerjaan yang sedang berlangsung dibatalkan jika ada pekerjaan matriks yang gagal. +completion.strategy.jobIndex=Indeks berbasis nol dari pekerjaan saat ini dalam matriks. +completion.strategy.jobTotal=Jumlah total pekerjaan dalam matriks. +completion.strategy.maxParallel=Jumlah maksimum tugas matriks yang dapat dijalankan secara bersamaan. +completion.context.inputs=Input alur kerja, seperti workflow_dispatch atau workflow_call. +completion.context.secrets=Rahasia alur kerja. +completion.context.job=Informasi tentang pekerjaan yang sedang berjalan. +completion.context.jobs=Pekerjaan alur kerja. +completion.context.matrix=Properti matriks ditentukan untuk pekerjaan matriks saat ini. +completion.context.strategy=Informasi strategi eksekusi matriks untuk pekerjaan saat ini. +completion.context.steps=Langkah-langkah dengan id dalam pekerjaan saat ini. +completion.context.env=Variabel lingkungan dari pekerjaan dan langkah. +completion.context.vars=Variabel konfigurasi khusus dari cakupan organisasi, repositori, dan lingkungan. +completion.context.needs=Pekerjaan yang harus diselesaikan sebelum pekerjaan ini dapat dijalankan, ditambah keluaran dan hasilnya. +completion.context.github=Alur kerja dijalankan dan informasi peristiwa dari konteks GitHub. +completion.context.gitea=Alias โ€‹โ€‹yang kompatibel dengan Gitea untuk konteks Tindakan GitHub. +completion.context.runner=Informasi tentang pelari yang menjalankan pekerjaan saat ini. +completion.secret.githubToken=Token yang dibuat secara otomatis untuk setiap alur kerja yang dijalankan. +completion.remote.repository=Repositori jarak jauh +completion.uses.local.workflow=Alur kerja lokal yang dapat digunakan kembali +completion.uses.local.action=Tindakan lokal +completion.uses.ref.known=Referensi alur kerja yang diketahui +completion.uses.ref.remote=Referensi alur kerja jarak jauh +completion.uses.remote.known=Tindakan jarak jauh yang diketahui atau alur kerja yang dapat digunakan kembali +completion.workflow.syntax=Sintaks alur kerja Tindakan GitHub +completion.workflow.top.name=Nama tampilan alur kerja +completion.workflow.top.run-name=Nama proses dinamis +completion.workflow.top.on=Peristiwa yang memulai alur kerja +completion.workflow.top.permissions=Izin bawaan GITHUB_TOKEN +completion.workflow.top.env=Variabel lingkungan seluruh alur kerja +completion.workflow.top.defaults=Pengaturan pekerjaan dan langkah default +completion.workflow.top.concurrency=Grup konkurensi dan pembatalan +completion.workflow.top.jobs=Pekerjaan yang dijalankan dalam alur kerja ini +completion.workflow.event.branch_protection_rule=Perlindungan cabang berubah +completion.workflow.event.check_run=Proses pemeriksaan tunggal diubah +completion.workflow.event.check_suite=Periksa suite berubah +completion.workflow.event.create=Cabang atau tag dibuat +completion.workflow.event.delete=Cabang atau tag dihapus +completion.workflow.event.deployment=Penerapan dibuat +completion.workflow.event.deployment_status=Status penerapan berubah +completion.workflow.event.discussion=Diskusi berubah +completion.workflow.event.discussion_comment=Komentar diskusi diubah +completion.workflow.event.fork=Repositori bercabang +completion.workflow.event.gollum=Halaman Wiki berubah +completion.workflow.event.image_version=Versi gambar paket diubah +completion.workflow.event.issue_comment=Masalah atau komentar PR diubah +completion.workflow.event.issues=Masalah berubah +completion.workflow.event.label=Labelnya berubah +completion.workflow.event.merge_group=Gabungkan pemeriksaan antrian yang diminta +completion.workflow.event.milestone=Tonggak sejarah berubah +completion.workflow.event.page_build=Pembuatan halaman berjalan +completion.workflow.event.project=Proyek klasik berubah +completion.workflow.event.project_card=Kartu proyek klasik telah diubah +completion.workflow.event.project_column=Kolom proyek klasik diubah +completion.workflow.event.public=Repositori menjadi publik +completion.workflow.event.pull_request=Permintaan tarik diubah +completion.workflow.event.pull_request_review=Ulasan PR berubah +completion.workflow.event.pull_request_review_comment=Komentar ulasan PR diubah +completion.workflow.event.pull_request_target=Konteks target PR. Pisau tajam. +completion.workflow.event.push=Komit atau tag didorong +completion.workflow.event.registry_package=Paket diterbitkan atau diperbarui +completion.workflow.event.release=Rilis diubah +completion.workflow.event.repository_dispatch=Acara API khusus +completion.workflow.event.schedule=Centang cron. Mesin jam. +completion.workflow.event.status=Status komit berubah +completion.workflow.event.watch=Repositori dibintangi +completion.workflow.event.workflow_call=Panggilan alur kerja yang dapat digunakan kembali +completion.workflow.event.workflow_dispatch=Tombol jalankan manual +completion.workflow.event.workflow_run=Alur kerja yang dijalankan berubah +completion.workflow.eventFilter.types=Batasi jenis aktivitas +completion.workflow.eventFilter.branches=Hanya cabang-cabang ini +completion.workflow.eventFilter.branches-ignore=Lewati cabang-cabang ini +completion.workflow.eventFilter.tags=Hanya tag ini +completion.workflow.eventFilter.tags-ignore=Lewati tag ini +completion.workflow.eventFilter.paths=Hanya jalan ini +completion.workflow.eventFilter.paths-ignore=Lewati jalur ini +completion.workflow.eventFilter.workflows=Nama alur kerja yang harus diperhatikan +completion.workflow.eventFilter.cron=Jadwal cron. Jarum jam kecil. +completion.workflow.permission.actions=Alur kerja berjalan dan artefak tindakan +completion.workflow.permission.artifact-metadata=Catatan metadata artefak +completion.workflow.permission.attestations=Pengesahan artefak +completion.workflow.permission.checks=Periksa run dan suite +completion.workflow.permission.code-quality=Laporan kualitas kode +completion.workflow.permission.contents=Isi repositori +completion.workflow.permission.deployments=Penerapan +completion.workflow.permission.discussions=Diskusi +completion.workflow.permission.id-token=Token OpenID Connect +completion.workflow.permission.issues=Masalah +completion.workflow.permission.models=Model GitHub +completion.workflow.permission.packages=Paket GitHub +completion.workflow.permission.pages=Halaman GitHub +completion.workflow.permission.pull-requests=Tarik permintaan +completion.workflow.permission.security-events=Pemindaian kode dan peristiwa keamanan +completion.workflow.permission.statuses=Komit status +completion.workflow.permission.vulnerability-alerts=Peringatan Dependabot +completion.workflow.permission.value.read=Akses baca +completion.workflow.permission.value.write=Akses tulis, termasuk baca +completion.workflow.permission.value.none=Tidak ada akses +completion.workflow.permission.shorthand.read-all=Semua izin dibaca. Selimut besar. +completion.workflow.permission.shorthand.write-all=Semua izin menulis. Palu besar. +completion.workflow.permission.shorthand.empty=Nonaktifkan izin token +completion.workflow.job.name=Nama tampilan pekerjaan +completion.workflow.job.permissions=Izin token pekerjaan +completion.workflow.job.needs=Pekerjaan yang harus ditunggu +completion.workflow.job.if=Kondisi pekerjaan +completion.workflow.job.runs-on=Label atau grup pelari +completion.workflow.job.snapshot=Cuplikan pelari +completion.workflow.job.environment=Lingkungan penerapan +completion.workflow.job.concurrency=Kunci konkurensi pekerjaan +completion.workflow.job.outputs=Output yang dapat dibaca oleh pekerjaan lain +completion.workflow.job.env=Variabel lingkungan kerja +completion.workflow.job.defaults=Pengaturan default pekerjaan +completion.workflow.job.steps=Daftar langkah. Pekerjaan sebenarnya. +completion.workflow.job.timeout-minutes=Batas waktu pekerjaan dalam hitungan menit +completion.workflow.job.strategy=Strategi matriks dan penjadwalan +completion.workflow.job.continue-on-error=Biarkan pekerjaan ini gagal dengan lembut +completion.workflow.job.container=Wadah untuk pekerjaan ini +completion.workflow.job.services=Kontainer servis sespan +completion.workflow.job.uses=Alur kerja yang dapat digunakan kembali untuk menelepon +completion.workflow.job.with=Input untuk alur kerja yang disebut +completion.workflow.job.secrets=Rahasia untuk alur kerja yang disebut +completion.workflow.defaultsRun.shell=Shell default untuk langkah-langkah yang dijalankan +completion.workflow.defaultsRun.working-directory=Direktori kerja default +completion.workflow.concurrency.group=Kunci nama untuk proses yang antri +completion.workflow.concurrency.cancel-in-progress=Batalkan proses pencocokan lama +completion.workflow.environment.name=Nama lingkungan +completion.workflow.environment.url=Lingkungan URL +completion.workflow.strategy.matrix=Sumbu matriks dan variannya +completion.workflow.strategy.fail-fast=Batalkan saudara matriks jika gagal +completion.workflow.strategy.max-parallel=Batas paralelisme matriks +completion.workflow.matrix.include=Tambahkan kombinasi matriks +completion.workflow.matrix.exclude=Hapus kombinasi matriks +completion.workflow.step.id=ID langkah untuk referensi +completion.workflow.step.if=Kondisi langkah +completion.workflow.step.name=Nama tampilan langkah +completion.workflow.step.uses=Tindakan untuk dijalankan +completion.workflow.step.run=Skrip shell untuk dijalankan +completion.workflow.step.shell=Shell untuk langkah lari ini +completion.workflow.step.with=Masukan tindakan +completion.workflow.step.env=Variabel lingkungan langkah +completion.workflow.step.continue-on-error=Biarkan langkah ini gagal dengan lembut +completion.workflow.step.timeout-minutes=Batas waktu langkah dalam hitungan menit +completion.workflow.step.working-directory=Langkah direktori kerja +completion.workflow.container.image=Gambar kontainer +completion.workflow.container.credentials=Kredensial registri +completion.workflow.container.env=Variabel lingkungan kontainer +completion.workflow.container.ports=Port untuk diekspos +completion.workflow.container.volumes=Volume yang akan dipasang +completion.workflow.container.options=Docker membuat opsi +completion.workflow.service.image=Gambar kontainer layanan +completion.workflow.service.credentials=Kredensial registri +completion.workflow.service.env=Variabel lingkungan layanan +completion.workflow.service.ports=Port layanan +completion.workflow.service.volumes=Volume layanan +completion.workflow.service.options=Docker membuat opsi +completion.workflow.credentials.username=Nama pengguna registri +completion.workflow.credentials.password=Kata sandi atau token registri +completion.workflow.inputType.string=Masukan teks +completion.workflow.inputType.boolean=Masukan benar atau salah +completion.workflow.inputType.choice=Masukan pilihan tarik-turun +completion.workflow.inputType.number=Masukan angka +completion.workflow.inputType.environment=Masukan pemilih lingkungan +completion.workflow.boolean.true=Ya. Balikkan. +completion.workflow.boolean.false=Tidak. Jaga agar tetap gelap. +completion.workflow.runner.ubuntu-latest=Pelari Ubuntu terbaru +completion.workflow.runner.ubuntu-24.04=Pelari Ubuntu 24.04 +completion.workflow.runner.ubuntu-22.04=Pelari Ubuntu 22.04 +completion.workflow.runner.windows-latest=Pelari Windows terbaru +completion.workflow.runner.windows-2025=Pelari Windows Server 2025 +completion.workflow.runner.windows-2022=Pelari Windows Server 2022 +completion.workflow.runner.macos-latest=Pelari macOS terbaru +completion.workflow.runner.macos-15=macOS 15 pelari +completion.workflow.runner.macos-14=macOS 14 pelari +completion.workflow.runner.self-hosted=Pelari Anda sendiri. Sirkus Anda. +completion.steps.outputs=Kumpulan output yang ditentukan untuk langkah tersebut. +completion.steps.conclusion=Hasil dari langkah yang diselesaikan setelah kesalahan lanjutan diterapkan. +completion.steps.outcome=Hasil dari langkah yang diselesaikan sebelum kesalahan lanjutan diterapkan. +completion.jobs.outputs=Kumpulan output yang ditentukan untuk pekerjaan itu. +completion.jobs.result=Hasil pekerjaan. +settings.displayName=Alur Kerja GitHub +settings.language.label=Bahasa: +settings.language.system=IDE/default sistem +settings.cache.title=Tembolok tindakan +settings.cache.column.key=Kunci cache +settings.cache.column.name=Nama +settings.cache.column.kind=Baik hati +settings.cache.column.state=Negara +settings.cache.column.expires=Kedaluwarsa +settings.cache.kind.local=lokal +settings.cache.kind.remote=terpencil +settings.cache.state.resolved=terselesaikan +settings.cache.state.pending=tertunda +settings.cache.state.expired=basi +settings.cache.state.suppressed=ditekan +settings.cache.refresh=Tabel Refresh +settings.cache.deleteSelected=Hapus yang dipilih +settings.cache.deleteAll=Hapus semua +settings.cache.export=Ekspor +settings.cache.import=Impor +settings.cache.summary=Cache: Entri {0}, {1} terselesaikan, {2} jarak jauh, {3} basi, {4} dibisukan. Tembolok: {5} KB. +settings.cache.noneSelected=Pilih baris cache terlebih dahulu. Sapu menolak menebak-nebak. +settings.cache.deleteSelected.done=Entri cache {0} dihapus. Awan debu kecil terkandung. +settings.cache.deleteAll.confirm=Hapus semua entri cache Alur Kerja GitHub? +settings.cache.deleteAll.done=Menghapus semua entri cache. Cache sekarang sepi secara mencurigakan. +settings.cache.export.done=Entri cache {0} yang diekspor. Binatang arsip kecil dikurung. +settings.cache.import.done=Entri cache yang diimpor. Binatang arsip itu berperilaku baik. +settings.cache.import.unsupported=File cache Alur Kerja GitHub tidak didukung. +settings.cache.import.brokenLine=Baris cache alur kerja GitHub rusak. +settings.cache.import.brokenKey=Kunci cache alur kerja GitHub rusak. +settings.support.button=Dukung plugin ini +settings.support.tooltip=Buka halaman dukungan +settings.support.line.0=Beri makan tungku pembangunan +settings.support.line.1=Beli kopi parser +settings.support.line.2=Mensponsori lebih sedikit alur kerja yang berhantu +workflow.run.jobs.title=Pekerjaan alur kerja +workflow.run.jobs.root=Alur kerja dijalankan +workflow.run.jobs.description=Pohon pekerjaan Tindakan GitHub dan log pekerjaan yang dipilih +workflow.run.tree.done=selesai +workflow.run.tree.failed=gagal +workflow.run.tree.skipped=dilewati +workflow.run.tree.warn=memperingatkan +workflow.run.tree.err=salah +workflow.run.delete.tooltip=Hapus jalankan +workflow.run.delete.noRun=Belum ada id yang dijalankan. +workflow.run.delete.requested=Menghapus jalankan {0}. +workflow.run.delete.done=Jalankan {0} dihapus. +workflow.run.delete.http=Hapus HTTP {0}. Aduh. +workflow.run.delete.failed=Hapus gagal: {0} +workflow.run.rerun.all.tooltip=Jalankan kembali alur kerja +workflow.run.rerun.failed.tooltip=Jalankan kembali pekerjaan yang gagal +workflow.run.rerun.noRun=Belum ada id yang dijalankan. +workflow.run.rerun.all.requested=Jalankan ulang yang diminta: {0}. +workflow.run.rerun.failed.requested=Pekerjaan yang gagal diminta: {0}. +workflow.run.rerun.all.done=Jalankan kembali antrian: {0}. +workflow.run.rerun.failed.done=Pekerjaan yang gagal dalam antrean: {0}. +workflow.run.rerun.http=Jalankan kembali HTTP {0}. Aduh. +workflow.run.rerun.failed=Jalankan ulang gagal: {0} +workflow.run.download.log.tooltip=Simpan log pekerjaan +workflow.run.download.artifacts.tooltip=Unduh artefak +workflow.run.download.noRun=Belum ada id yang dijalankan. +workflow.run.download.log.requested=Mengambil log untuk {0}. +workflow.run.download.log.done=Log disimpan: {0}. +workflow.run.download.artifacts.requested=Mengambil artefak. +workflow.run.download.artifacts.empty=Tidak ada artefak. Kekosongan kecil. +workflow.run.download.artifact.expired=Artefak kedaluwarsa: {0}. +workflow.run.download.artifact.done=Artefak disimpan: {0} -> {1}. +workflow.run.download.failed=Unduhan gagal: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_it.properties b/src/main/resources/messages/GitHubWorkflowBundle_it.properties new file mode 100644 index 0000000..7822206 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_it.properties @@ -0,0 +1,442 @@ +plugin.name=Flusso di lavoro GitHub +plugin.description=Supporto per i file del flusso di lavoro delle azioni GitHub +group.GitHubWorkflow.Tools.text=Flusso di lavoro GitHub +group.GitHubWorkflow.Tools.description=GitHub Strumenti plug-in del flusso di lavoro +action.GitHubWorkflow.RefreshActionCache.text=Refresh Cache delle azioni +action.GitHubWorkflow.RefreshActionCache.description=Refresh ha risolto le azioni GitHub remote e i metadati del flusso di lavoro riutilizzabili +action.GitHubWorkflow.RestoreActionWarnings.text=Ripristina avvisi di azioni +action.GitHubWorkflow.RestoreActionWarnings.description=Ripristina gli avvisi di convalida di azioni, input e output soppressi +action.GitHubWorkflow.ClearActionCache.text=Cancella cache delle azioni +action.GitHubWorkflow.ClearActionCache.description=Cancella le azioni GitHub memorizzate nella cache e i metadati del flusso di lavoro riutilizzabili +notification.cache.cleared=Cancellate le voci del flusso di lavoro GitHub memorizzate nella cache di {0}. +notification.cache.refresh.started=Refreshing {0} ha memorizzato nella cache le voci del flusso di lavoro GitHub remote. +notification.warnings.restored=Avvisi ripristinati per {0} GitHub Voci del flusso di lavoro. +workflow.run.configuration.display=Flusso di lavoro GitHub +workflow.run.configuration.description=Invia e segui le esecuzioni del flusso di lavoro Azioni GitHub +workflow.run.configuration.name=Flusso di lavoro GitHub: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=Proprietario +workflow.run.field.repo=Deposito +workflow.run.field.workflow=File del flusso di lavoro +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Fallback variabile token +workflow.run.inputs.title=Ingressi workflow_dispatch (chiave=valore) +workflow.run.error.apiUrl=GitHub API URL รจ obbligatorio. +workflow.run.error.repository=Il proprietario e il nome del repository GitHub sono obbligatori. +workflow.run.error.workflow=Il file del flusso di lavoro รจ obbligatorio. +workflow.run.error.ref=Il riferimento al ramo o al tag รจ obbligatorio. +workflow.run.error.inputs=GitHub workflow_dispatch supporta al massimo 25 ingressi. +workflow.run.gutter.stop=Arresta l''esecuzione del flusso di lavoro +workflow.run.gutter.stop.text=Arresta l''esecuzione del flusso di lavoro +workflow.run.gutter.stop.description=Annulla questa corsa +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=correre: +workflow.log.warning=avvertimento: +workflow.log.error=errore: +workflow.run.cancel.requested=Cancellazione richiesta: {0}. +workflow.run.stop.before.id=Sosta richiesta. Nessun ID corsa ancora. +workflow.run.cancel.http=Annulla HTTP {0}. Uffa. +workflow.run.cancel.failed=Annullamento fallito: {0} +workflow.run.interrupted=Interrotto. +workflow.run.link=Esegui: {0} +workflow.run.discovery=Corsa accettata. ID corsa di caccia. +workflow.run.discovery.none=Nessun ID corsa ancora. La scheda Azioni ne sa di piรน. +workflow.run.status=Stato: {0}{1} +workflow.run.job.main=Lavoro: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Stato: {0} {1}{2}{3} +workflow.run.logs.later=I log verranno visualizzati quando GitHub li pubblicherร . +workflow.run.job.logs.later=Lavoro {0}: {1} +workflow.run.log.failed=Download del registro non riuscito: {0} +workflow.run.log.failed.job=Download del registro non riuscito per {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Lavoro: {0} +workflow.run.job.fallbackName=Lavoro {0} +workflow.run.overview=Flusso di lavoro eseguito {0} {1}/{2} completato, {3} in esecuzione +workflow.run.state.ok=[OK] +workflow.run.state.fail=[FALLITO] +workflow.run.state.running=[CORRI] +workflow.run.state.waiting=[ATTENDERE] +workflow.run.dispatch.verbs=Priming|Coda|Evocazione|Lancio|Avvio +workflow.run.dispatch.objects=flusso di lavoro|automazione|pipeline|esecuzione +workflow.run.dispatch={0} {1} {2}{3} per {4} su {5}. +workflow.run.notification.auth=L''invio del flusso di lavoro GitHub richiede un account GitHub autenticato. Aggiungi o aggiorna gli account in {0}. +workflow.run.notification.openSettings=Apri le Impostazioni GitHub +workflow.cache.progress.title=Risoluzione delle azioni GitHub +workflow.cache.progress.text=Risoluzione di {0} {1} +workflow.cache.kind.action=azione +workflow.cache.kind.workflow=flusso di lavoro +inspection.parameter.input=ingresso +inspection.parameter.secret=segreto +inspection.action.delete.invalid=Elimina {0} non valido [{1}] +inspection.action.update.major=Aggiorna l''azione [{0}] in [{1}] +inspection.warning.toggle=Attiva/disattiva gli avvisi [{0}] per [{1}] +inspection.warning.on=su +inspection.warning.off=spento +inspection.statement.incomplete=Dichiarazione incompleta [{0}] +inspection.invalid.suffix.remove=Rimuovi il suffisso non valido [{0}] +inspection.replace.with=Sostituisci con [{0}] +inspection.invalid.remove=Rimuovi [{0}] non valido +inspection.workflow.syntax.unknownTopLevelKey=Chiave del flusso di lavoro sconosciuta [{0}] +inspection.workflow.syntax.unknownEventKey=Evento del flusso di lavoro sconosciuto [{0}] +inspection.workflow.syntax.unknownTriggerKey=Chiave di attivazione sconosciuta [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Filtro di attivazione sconosciuto [{0}] +inspection.workflow.syntax.unknownTriggerValue=Valore di attivazione sconosciuto [{0}] +inspection.workflow.syntax.unknownPermission=Autorizzazione sconosciuta [{0}] +inspection.workflow.syntax.unknownPermissionValue=Valore dell''autorizzazione sconosciuto [{0}] +inspection.workflow.syntax.unknownJobKey=Chiave lavoro sconosciuta [{0}] +inspection.workflow.syntax.unknownStepKey=Tasto passaggio sconosciuto [{0}] +inspection.action.reload=Ricarica [{0}] +inspection.action.unresolved=Non risolto [{0}]: controlla l''accesso all''account GitHub, le autorizzazioni del repository privato, i limiti di velocitร , i riferimenti mancanti o i metadati di azioni/flussi di lavoro mancanti +inspection.action.jump=Vai al file [{0}] +inspection.output.unused=Non utilizzato [{0}] +inspection.secret.invalid.if=Rimuovi [{0}] - I segreti non sono validi nelle istruzioni "if". +inspection.secret.replace.runtime=Sostituisci [{0}] con [{1}] - se non viene fornito in fase di esecuzione +inspection.needs.invalid.job=Rimuovi jobId non valido [{0}]: questo jobId non corrisponde a nessun lavoro precedente +documentation.description=Descrizione: {0} +documentation.type=Tipo: {0} +documentation.required=Richiesto: {0} +documentation.default=Impostazione predefinita: {0} +documentation.deprecated=Obsoleto: {0} +documentation.open.declaration=Dichiarazione aperta ({0}) +documentation.input.label=Ingresso +documentation.secret.label=Segreto +documentation.env.label=Variabile d''ambiente +documentation.matrix.label=Proprietร  della matrice +documentation.need.label=Lavoro necessario +documentation.need.description=Dipendenza diretta dal lavoro. +documentation.needOutput.label=Risultati lavorativi necessari +documentation.reusableJob.label=Lavoro del flusso di lavoro riutilizzabile +documentation.reusableJob.description=Lavoro dichiarato in questo flusso di lavoro riutilizzabile. +documentation.reusableJobOutput.label=Output del lavoro del flusso di lavoro riutilizzabile +documentation.service.label=Contenitore di servizio +documentation.servicePort.label=Porto di servizio +documentation.container.label=Contenitore di lavoro +documentation.symbol.label=Simbolo del flusso di lavoro +documentation.symbol.description=Espressione del flusso di lavoro risolta. +documentation.workflowOutput.label=Output del flusso di lavoro +documentation.jobOutput.label=Produzione di lavoro +documentation.action.label=Azione +documentation.externalAction.label=Azione esterna +documentation.reusableWorkflow.label=Flusso di lavoro riutilizzabile +documentation.resolvedFrom=risolto da {0} +documentation.notResolved=non ancora risolto +documentation.inputs.title=Ingressi +documentation.outputs.title=Uscite +documentation.secrets.title=Segreti +documentation.value.label=Valore +documentation.step.title=Passaggio {0} +documentation.name.label=Nome +documentation.uses.label=Usi +documentation.run.label=Corri +documentation.description.label=Descrizione +documentation.step.label=Passo +documentation.source.label=Fonte +documentation.stepOutput.label=Uscita del passo +documentation.context.github=contesto github +documentation.context.github.description=Informazioni sull''esecuzione e sull''evento del flusso di lavoro corrente. +documentation.context.gitea=contesto gitea +documentation.context.gitea.description=Alias compatibile con Gitea per il contesto Azioni GitHub. +documentation.context.inputs=contesto degli input +documentation.context.inputs.description=Flusso di lavoro, invio o input di azione disponibili qui. +documentation.context.secrets=contesto segreto +documentation.context.secrets.description=Valori segreti disponibili per questo flusso di lavoro o chiamata al flusso di lavoro riutilizzabile. +documentation.context.env=contesto ambientale +documentation.context.env.description=Variabili d''ambiente visibili in questa posizione. +documentation.context.matrix=contesto della matrice +documentation.context.matrix.description=Valori della matrice per il lavoro corrente. +documentation.context.steps=contesto dei passaggi +documentation.context.steps.description=Passaggi precedenti nel lavoro corrente, inclusi output e stato. +documentation.context.needs=ha bisogno di contesto +documentation.context.needs.description=Dipendenze dirette dal lavoro e loro output/risultati. +documentation.context.jobs=contesto lavorativo +documentation.context.jobs.description=Lavori e output del flusso di lavoro riutilizzabili. +documentation.context.outputs=uscite +documentation.context.outputs.description=Valori di output esposti da questo passaggio o processo. +documentation.context.result=risultato +documentation.context.result.description=Risultato del lavoro: successo, fallimento, annullato o saltato. +documentation.context.outcome=risultato +documentation.context.outcome.description=Risultato del passaggio prima dell''applicazione della continuazione in caso di errore. +documentation.context.conclusion=conclusione +documentation.context.conclusion.description=Risultato del passaggio dopo l''applicazione della continuazione in caso di errore. +error.report.action=Segnala eccezione +error.report.description=Descrizione +error.report.steps=Passaggi per riprodurre +error.report.sample=Fornire un esempio di codice, se applicabile +error.report.message=Messaggio +error.report.runtime=Informazioni sull''esecuzione +error.report.pluginVersion=Versione del plugin: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=Stacktrace +completion.shell.bash=Guscio Bash. Utilizza bash sui runner Linux e macOS e Git per bash Windows sui runner Windows. +completion.shell.sh=Fallback della shell POSIX. +completion.shell.pwsh=Nucleo PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Prompt dei comandi Windows. +completion.shell.python=Runner di comandi Python. +completion.runner.name=Il nome del corridore che esegue il lavoro. +completion.runner.os=Il sistema operativo del corridore che esegue il lavoro. I valori possibili sono Linux, Windows o macOS. +completion.runner.arch=L''architettura del corridore che esegue il lavoro. I valori possibili sono X86, X64, ARM o ARM64. +completion.runner.temp=Il percorso di una directory temporanea sul runner. Questa directory viene svuotata all''inizio e alla fine di ogni lavoro. Tieni presente che i file non verranno rimossi se l''account utente del corridore non dispone dell''autorizzazione per eliminarli. +completion.runner.toolCache=Il percorso della directory contenente gli strumenti preinstallati per i runner ospitati su GitHub. +completion.runner.debug=Viene impostato solo se la registrazione del debug รจ abilitata e ha sempre il valore 1. +completion.runner.environment=L''ambiente del corridore che esegue il lavoro. I valori possibili sono ospitati su github o ospitati autonomamente. +completion.job.status=Lo stato attuale del lavoro. +completion.job.checkRunId=L''ID dell''esecuzione del controllo del lavoro corrente. +completion.job.container=Informazioni sul contenitore del lavoro. +completion.job.services=I contenitori di servizi creati per un lavoro. +completion.job.workflowRef=Il riferimento completo del file del flusso di lavoro che definisce il lavoro corrente. +completion.job.workflowSha=Il commit SHA del file del flusso di lavoro che definisce il lavoro corrente. +completion.job.workflowRepository=Il proprietario/repo del repository contenente il file del flusso di lavoro che definisce il lavoro corrente. +completion.job.workflowFilePath=Il percorso del file del flusso di lavoro, relativo alla radice del repository. +completion.job.containerField=Campo Contenitore lavori +completion.job.service=Servizio per il lavoro +completion.job.serviceField=Campo dei servizi per il lavoro +completion.job.mappedServicePort=Porta di servizio mappata +completion.strategy.failFast=Se tutti i lavori in corso vengono annullati se un lavoro della matrice fallisce. +completion.strategy.jobIndex=L''indice in base zero del lavoro corrente nella matrice. +completion.strategy.jobTotal=Il numero totale di posti di lavoro nella matrice. +completion.strategy.maxParallel=Il numero massimo di lavori matrice che possono essere eseguiti simultaneamente. +completion.context.inputs=Ingressi del flusso di lavoro, come workflow_dispatch o workflow_call. +completion.context.secrets=Segreti del flusso di lavoro. +completion.context.job=Informazioni sul lavoro attualmente in esecuzione. +completion.context.jobs=Lavori del flusso di lavoro. +completion.context.matrix=Proprietร  della matrice definite per il lavoro della matrice corrente. +completion.context.strategy=Informazioni sulla strategia di esecuzione della matrice per il lavoro corrente. +completion.context.steps=Passaggi con un ID nel lavoro corrente. +completion.context.env=Variabili di ambiente da lavori e passaggi. +completion.context.vars=Variabili di configurazione personalizzate dagli ambiti dell''organizzazione, del repository e dell''ambiente. +completion.context.needs=Lavori che devono essere completati prima che questo lavoro possa essere eseguito, oltre ai relativi output e risultati. +completion.context.github=Informazioni sull''esecuzione e sugli eventi del flusso di lavoro dal contesto GitHub. +completion.context.gitea=Alias compatibile con Gitea per il contesto Azioni GitHub. +completion.context.runner=Informazioni sul corridore che sta eseguendo il lavoro corrente. +completion.secret.githubToken=Token creato automaticamente per ogni esecuzione del flusso di lavoro. +completion.remote.repository=Archivio remoto +completion.uses.local.workflow=Flusso di lavoro riutilizzabile locale +completion.uses.local.action=Azione locale +completion.uses.ref.known=Riferimento al flusso di lavoro noto +completion.uses.ref.remote=Riferimento al flusso di lavoro remoto +completion.uses.remote.known=Azione remota nota o flusso di lavoro riutilizzabile +completion.workflow.syntax=GitHub Sintassi del flusso di lavoro delle azioni +completion.workflow.top.name=Nome visualizzato del flusso di lavoro +completion.workflow.top.run-name=Nome della corsa dinamica +completion.workflow.top.on=Eventi che avviano il flusso di lavoro +completion.workflow.top.permissions=Autorizzazioni GITHUB_TOKEN predefinite +completion.workflow.top.env=Variabili di ambiente a livello di flusso di lavoro +completion.workflow.top.defaults=Impostazioni predefinite del lavoro e dei passaggi +completion.workflow.top.concurrency=Gruppo di concorrenza e cancellazione +completion.workflow.top.jobs=Lavori eseguiti in questo flusso di lavoro +completion.workflow.event.branch_protection_rule=La protezione del ramo รจ cambiata +completion.workflow.event.check_run=L''esecuzione del controllo singolo รจ stata modificata +completion.workflow.event.check_suite=La suite di controllo รจ cambiata +completion.workflow.event.create=Ramo o tag creato +completion.workflow.event.delete=Ramo o tag eliminato +completion.workflow.event.deployment=Distribuzione creata +completion.workflow.event.deployment_status=Lo stato della distribuzione รจ cambiato +completion.workflow.event.discussion=La discussione รจ cambiata +completion.workflow.event.discussion_comment=Il commento alla discussione รจ cambiato +completion.workflow.event.fork=Repository biforcato +completion.workflow.event.gollum=La pagina Wiki รจ cambiata +completion.workflow.event.image_version=La versione dell''immagine del pacchetto รจ stata modificata +completion.workflow.event.issue_comment=Problema o commento PR modificato +completion.workflow.event.issues=Il problema รจ cambiato +completion.workflow.event.label=L''etichetta รจ cambiata +completion.workflow.event.merge_group=Richiesto controllo coda di unione +completion.workflow.event.milestone=La pietra miliare รจ cambiata +completion.workflow.event.page_build=La creazione delle pagine รจ stata eseguita +completion.workflow.event.project=Il progetto classico รจ cambiato +completion.workflow.event.project_card=La scheda del progetto classico รจ stata modificata +completion.workflow.event.project_column=La colonna del progetto classico รจ stata modificata +completion.workflow.event.public=Il repository รจ diventato pubblico +completion.workflow.event.pull_request=La richiesta di pull รจ stata modificata +completion.workflow.event.pull_request_review=La recensione di PR รจ stata modificata +completion.workflow.event.pull_request_review_comment=Il commento di revisione PR รจ stato modificato +completion.workflow.event.pull_request_target=Contesto di destinazione PR. Coltelli affilati. +completion.workflow.event.push=Commit o tag inviato +completion.workflow.event.registry_package=Pacchetto pubblicato o aggiornato +completion.workflow.event.release=La versione รจ cambiata +completion.workflow.event.repository_dispatch=Evento API personalizzato +completion.workflow.event.schedule=Segno di spunta Cron. Meccanismo d''orologeria. +completion.workflow.event.status=Lo stato del commit รจ cambiato +completion.workflow.event.watch=Il repository รจ stato contrassegnato come Speciale +completion.workflow.event.workflow_call=Chiamata del flusso di lavoro riutilizzabile +completion.workflow.event.workflow_dispatch=Pulsante di esecuzione manuale +completion.workflow.event.workflow_run=L''esecuzione del flusso di lavoro รจ stata modificata +completion.workflow.eventFilter.types=Limita i tipi di attivitร  +completion.workflow.eventFilter.branches=Solo questi rami +completion.workflow.eventFilter.branches-ignore=Salta questi rami +completion.workflow.eventFilter.tags=Solo questi tag +completion.workflow.eventFilter.tags-ignore=Salta questi tag +completion.workflow.eventFilter.paths=Solo questi percorsi +completion.workflow.eventFilter.paths-ignore=Salta questi percorsi +completion.workflow.eventFilter.workflows=Nomi dei flussi di lavoro da tenere d''occhio +completion.workflow.eventFilter.cron=Programma Cron. Piccolo orologio. +completion.workflow.permission.actions=Esecuzioni del flusso di lavoro e artefatti delle azioni +completion.workflow.permission.artifact-metadata=Record di metadati degli artefatti +completion.workflow.permission.attestations=Attestazioni di artefatti +completion.workflow.permission.checks=Controlla le corse e le suite +completion.workflow.permission.code-quality=Rapporti sulla qualitร  del codice +completion.workflow.permission.contents=Contenuti del deposito +completion.workflow.permission.deployments=Distribuzioni +completion.workflow.permission.discussions=Discussioni +completion.workflow.permission.id-token=Token OpenID Connect +completion.workflow.permission.issues=Problemi +completion.workflow.permission.models=Modelli GitHub +completion.workflow.permission.packages=Pacchetti GitHub +completion.workflow.permission.pages=GitHub Pagine +completion.workflow.permission.pull-requests=Tirare le richieste +completion.workflow.permission.security-events=Scansione del codice ed eventi di sicurezza +completion.workflow.permission.statuses=Stati di commit +completion.workflow.permission.vulnerability-alerts=Avvisi Dependabot +completion.workflow.permission.value.read=Accesso in lettura +completion.workflow.permission.value.write=Accesso in scrittura, lettura inclusa +completion.workflow.permission.value.none=Nessun accesso +completion.workflow.permission.shorthand.read-all=Tutti i permessi vengono letti. Grande coperta. +completion.workflow.permission.shorthand.write-all=Tutti i permessi scrivono. Grande martello. +completion.workflow.permission.shorthand.empty=Disabilita le autorizzazioni del token +completion.workflow.job.name=Nome visualizzato del lavoro +completion.workflow.job.permissions=Autorizzazioni token lavoro +completion.workflow.job.needs=Lavori da aspettare +completion.workflow.job.if=Condizione lavorativa +completion.workflow.job.runs-on=Etichetta o gruppo del corridore +completion.workflow.job.snapshot=Istantanea del corridore +completion.workflow.job.environment=Ambiente di distribuzione +completion.workflow.job.concurrency=Blocco della concorrenza dei processi +completion.workflow.job.outputs=Output che altri lavori possono leggere +completion.workflow.job.env=Variabili dell''ambiente lavorativo +completion.workflow.job.defaults=Impostazioni predefinite del lavoro +completion.workflow.job.steps=Elenco dei passaggi. Il lavoro vero e proprio. +completion.workflow.job.timeout-minutes=Timeout del lavoro in minuti +completion.workflow.job.strategy=Matrice e strategia di scheduling +completion.workflow.job.continue-on-error=Lascia che questo lavoro fallisca dolcemente +completion.workflow.job.container=Contenitore per questo lavoro +completion.workflow.job.services=Contenitori di servizio sidecar +completion.workflow.job.uses=Flusso di lavoro riutilizzabile per chiamare +completion.workflow.job.with=Input per il flusso di lavoro chiamato +completion.workflow.job.secrets=Segreti per il flusso di lavoro chiamato +completion.workflow.defaultsRun.shell=Shell predefinita per i passaggi di esecuzione +completion.workflow.defaultsRun.working-directory=Directory di lavoro predefinita +completion.workflow.concurrency.group=Blocca il nome per le esecuzioni in coda +completion.workflow.concurrency.cancel-in-progress=Annulla le esecuzioni di corrispondenza precedenti +completion.workflow.environment.name=Nome dell''ambiente +completion.workflow.environment.url=Ambiente URL +completion.workflow.strategy.matrix=Assi e varianti della matrice +completion.workflow.strategy.fail-fast=Annulla i fratelli della matrice in caso di fallimento +completion.workflow.strategy.max-parallel=Limite del parallelismo della matrice +completion.workflow.matrix.include=Aggiungi combinazioni di matrici +completion.workflow.matrix.exclude=Rimuovere le combinazioni di matrici +completion.workflow.step.id=ID passaggio per riferimenti +completion.workflow.step.if=Condizione del passo +completion.workflow.step.name=Nome visualizzato del passaggio +completion.workflow.step.uses=Azione da eseguire +completion.workflow.step.run=Script di shell da eseguire +completion.workflow.step.shell=Shell per questo passaggio di esecuzione +completion.workflow.step.with=Ingressi di azione +completion.workflow.step.env=Passo variabili d''ambiente +completion.workflow.step.continue-on-error=Lascia che questo passaggio fallisca dolcemente +completion.workflow.step.timeout-minutes=Timeout del passaggio in minuti +completion.workflow.step.working-directory=Passaggio alla directory di lavoro +completion.workflow.container.image=Immagine del contenitore +completion.workflow.container.credentials=Credenziali del Registro +completion.workflow.container.env=Variabili di ambiente del contenitore +completion.workflow.container.ports=Porti da esporre +completion.workflow.container.volumes=Volumi da montare +completion.workflow.container.options=Docker crea opzioni +completion.workflow.service.image=Immagine del contenitore del servizio +completion.workflow.service.credentials=Credenziali del Registro +completion.workflow.service.env=Variabili dell''ambiente del servizio +completion.workflow.service.ports=Porti di servizio +completion.workflow.service.volumes=Volumi di servizio +completion.workflow.service.options=Docker crea opzioni +completion.workflow.credentials.username=Nome utente del registro +completion.workflow.credentials.password=Password o token del registro +completion.workflow.inputType.string=Inserimento di testo +completion.workflow.inputType.boolean=Ingresso vero o falso +completion.workflow.inputType.choice=Inserimento scelta a discesa +completion.workflow.inputType.number=Inserimento del numero +completion.workflow.inputType.environment=Input del selettore dell''ambiente +completion.workflow.boolean.true=Sรฌ. Capovolgilo. +completion.workflow.boolean.false=No. Tienilo al buio. +completion.workflow.runner.ubuntu-latest=Ultimo corridore Ubuntu +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 corridore +completion.workflow.runner.ubuntu-22.04=Guida Ubuntu 22.04 +completion.workflow.runner.windows-latest=Ultimo corridore Windows +completion.workflow.runner.windows-2025=Corridore del server Windows 2025 +completion.workflow.runner.windows-2022=Corridore Windows Server 2022 +completion.workflow.runner.macos-latest=Ultimo corridore macOS +completion.workflow.runner.macos-15=Guida macOS 15 +completion.workflow.runner.macos-14=macOS 14 guide +completion.workflow.runner.self-hosted=Il tuo corridore. Il tuo circo. +completion.steps.outputs=L''insieme di output definiti per il passaggio. +completion.steps.conclusion=Il risultato di un passaggio completato dopo l''applicazione della continuazione in caso di errore. +completion.steps.outcome=Il risultato di un passaggio completato prima che venga applicata la continuazione in caso di errore. +completion.jobs.outputs=L''insieme di output definiti per il lavoro. +completion.jobs.result=Il risultato del lavoro. +settings.displayName=Flusso di lavoro GitHub +settings.language.label=Lingua: +settings.language.system=IDE/impostazione predefinita del sistema +settings.cache.title=Cache delle azioni +settings.cache.column.key=Chiave della cache +settings.cache.column.name=Nome +settings.cache.column.kind=Gentile +settings.cache.column.state=Stato +settings.cache.column.expires=Scade +settings.cache.kind.local=locale +settings.cache.kind.remote=remoto +settings.cache.state.resolved=risolto +settings.cache.state.pending=in sospeso +settings.cache.state.expired=stantio +settings.cache.state.suppressed=soppresso +settings.cache.refresh=Tabella Refresh +settings.cache.deleteSelected=Elimina selezionato +settings.cache.deleteAll=Elimina tutto +settings.cache.export=Esportazione +settings.cache.import=Importa +settings.cache.summary=Cache: voci {0}, {1} risolto, {2} remoto, {3} non aggiornato, {4} disattivato. Cache: {5} KB. +settings.cache.noneSelected=Seleziona prima le righe della cache. La scopa rifiuta le supposizioni. +settings.cache.deleteSelected.done=Voci della cache {0} eliminate. Minuscola nuvola di polvere contenuta. +settings.cache.deleteAll.confirm=Eliminare tutte le voci della cache del flusso di lavoro GitHub? +settings.cache.deleteAll.done=Eliminate tutte le voci della cache. La cache ora รจ sospettosamente silenziosa. +settings.cache.export.done=Voci della cache {0} esportate. Piccola bestia d''archivio in gabbia. +settings.cache.import.done=Voci della cache importate. La bestia dell''archivio si รจ comportata bene. +settings.cache.import.unsupported=File di cache del flusso di lavoro GitHub non supportato. +settings.cache.import.brokenLine=Riga della cache del flusso di lavoro GitHub interrotta. +settings.cache.import.brokenKey=Chiave cache del flusso di lavoro GitHub danneggiata. +settings.support.button=Supporta questo plugin +settings.support.tooltip=Apri la pagina di supporto +settings.support.line.0=Alimenta il forno di costruzione +settings.support.line.1=Compra il caffรจ del parser +settings.support.line.2=Sponsorizza meno flussi di lavoro infestati +workflow.run.jobs.title=Lavori del flusso di lavoro +workflow.run.jobs.root=Esecuzione del flusso di lavoro +workflow.run.jobs.description=GitHub Albero dei lavori delle azioni e registro dei lavori selezionati +workflow.run.tree.done=fatto +workflow.run.tree.failed=fallito +workflow.run.tree.skipped=saltato +workflow.run.tree.warn=avvisare +workflow.run.tree.err=errare +workflow.run.delete.tooltip=Elimina corsa +workflow.run.delete.noRun=Nessun ID corsa ancora. +workflow.run.delete.requested=Eliminazione esecuzione {0}. +workflow.run.delete.done=Esegui {0} eliminato. +workflow.run.delete.http=Elimina HTTP {0}. Uffa. +workflow.run.delete.failed=Eliminazione fallita: {0} +workflow.run.rerun.all.tooltip=Rieseguire il flusso di lavoro +workflow.run.rerun.failed.tooltip=Eseguire nuovamente i lavori non riusciti +workflow.run.rerun.noRun=Nessun ID corsa ancora. +workflow.run.rerun.all.requested=Riesecuzione richiesta: {0}. +workflow.run.rerun.failed.requested=Lavori richiesti non riusciti: {0}. +workflow.run.rerun.all.done=Riesecuzione in coda: {0}. +workflow.run.rerun.failed.done=Lavori non riusciti in coda: {0}. +workflow.run.rerun.http=Rieseguire HTTP {0}. Uffa. +workflow.run.rerun.failed=La replica รจ fallita: {0} +workflow.run.download.log.tooltip=Salva registro lavori +workflow.run.download.artifacts.tooltip=Scarica artefatti +workflow.run.download.noRun=Nessun ID corsa ancora. +workflow.run.download.log.requested=Recupero registro per {0}. +workflow.run.download.log.done=Registro salvato: {0}. +workflow.run.download.artifacts.requested=Recupero degli artefatti. +workflow.run.download.artifacts.empty=Nessun artefatto. Piccolo vuoto. +workflow.run.download.artifact.expired=Artefatto scaduto: {0}. +workflow.run.download.artifact.done=Artefatto salvato: {0} -> {1}. +workflow.run.download.failed=Download scaduto: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ja.properties b/src/main/resources/messages/GitHubWorkflowBundle_ja.properties new file mode 100644 index 0000000..9d5c7d9 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_ja.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ +plugin.description=GitHub ใ‚ขใ‚ฏใ‚ทใƒงใƒณ ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใƒ•ใ‚กใ‚คใƒซใฎใ‚ตใƒใƒผใƒˆ +group.GitHubWorkflow.Tools.text=GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ +group.GitHubWorkflow.Tools.description=GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใƒ—ใƒฉใ‚ฐใ‚คใƒณ ใƒ„ใƒผใƒซ +action.GitHubWorkflow.RefreshActionCache.text=Refresh ใ‚ขใ‚ฏใ‚ทใƒงใƒณ ใ‚ญใƒฃใƒƒใ‚ทใƒฅ +action.GitHubWorkflow.RefreshActionCache.description=Refresh ใŒ่งฃๆฑบใ—ใŸใƒชใƒขใƒผใƒˆ GitHub ใ‚ขใ‚ฏใ‚ทใƒงใƒณใจๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใƒกใ‚ฟใƒ‡ใƒผใ‚ฟ +action.GitHubWorkflow.RestoreActionWarnings.text=ๅพฉๅ…ƒใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎ่ญฆๅ‘Š +action.GitHubWorkflow.RestoreActionWarnings.description=ๆŠ‘ๅˆถใ•ใ‚ŒใŸใ‚ขใ‚ฏใ‚ทใƒงใƒณใ€ๅ…ฅๅŠ›ใ€ใŠใ‚ˆใณๅ‡บๅŠ›ใฎๆคœ่จผ่ญฆๅ‘Šใ‚’ๅพฉๅ…ƒใ—ใพใ™ +action.GitHubWorkflow.ClearActionCache.text=ใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚ญใƒฃใƒƒใ‚ทใƒฅใฎใ‚ฏใƒชใ‚ข +action.GitHubWorkflow.ClearActionCache.description=ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ•ใ‚ŒใŸ GitHub ใ‚ขใ‚ฏใ‚ทใƒงใƒณใจๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใƒกใ‚ฟใƒ‡ใƒผใ‚ฟใ‚’ใ‚ฏใƒชใ‚ขใ—ใพใ™ +notification.cache.cleared={0} ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ•ใ‚ŒใŸ GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚จใƒณใƒˆใƒชใ‚’ใ‚ฏใƒชใ‚ขใ—ใพใ—ใŸใ€‚ +notification.cache.refresh.started={0} ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ•ใ‚ŒใŸใƒชใƒขใƒผใƒˆ GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚จใƒณใƒˆใƒชใ‚’ๆ›ดๆ–ฐใ—ใฆใ„ใพใ™ใ€‚ +notification.warnings.restored={0} GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚จใƒณใƒˆใƒชใฎ่ญฆๅ‘ŠใŒๅพฉๅ…ƒใ•ใ‚Œใพใ—ใŸใ€‚ +workflow.run.configuration.display=GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ +workflow.run.configuration.description=GitHub ใ‚ขใ‚ฏใ‚ทใƒงใƒณ ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎๅฎŸ่กŒใ‚’ใƒ‡ใ‚ฃใ‚นใƒ‘ใƒƒใƒใ—ใฆ่ฟฝ่ทกใ™ใ‚‹ +workflow.run.configuration.name=GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=ใ‚ชใƒผใƒŠใƒผ +workflow.run.field.repo=ใƒชใƒใ‚ธใƒˆใƒช +workflow.run.field.workflow=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใƒ•ใ‚กใ‚คใƒซ +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=ใƒˆใƒผใ‚ฏใƒณ็’ฐๅขƒๅค‰ๆ•ฐใƒ•ใ‚ฉใƒผใƒซใƒใƒƒใ‚ฏ +workflow.run.inputs.title=workflow_dispatch ๅ…ฅๅŠ› (ใ‚ญใƒผ=ๅ€ค) +workflow.run.error.apiUrl=GitHub API URLใฏๅฟ…้ ˆใงใ™ใ€‚ +workflow.run.error.repository=GitHub ใƒชใƒใ‚ธใƒˆใƒชใฎๆ‰€ๆœ‰่€…ใจๅๅ‰ใฏๅฟ…้ ˆใงใ™ใ€‚ +workflow.run.error.workflow=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใƒ•ใ‚กใ‚คใƒซใŒๅฟ…่ฆใงใ™ใ€‚ +workflow.run.error.ref=ใƒ–ใƒฉใƒณใƒใพใŸใฏใ‚ฟใ‚ฐๅ‚็…งใŒๅฟ…่ฆใงใ™ใ€‚ +workflow.run.error.inputs=GitHub workflow_dispatch ใฏๆœ€ๅคง 25 ๅ…ฅๅŠ›ใ‚’ใ‚ตใƒใƒผใƒˆใ—ใพใ™ใ€‚ +workflow.run.gutter.stop=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎๅฎŸ่กŒใ‚’ๅœๆญขใ™ใ‚‹ +workflow.run.gutter.stop.text=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎๅฎŸ่กŒใ‚’ๅœๆญขใ™ใ‚‹ +workflow.run.gutter.stop.description=ใ“ใฎๅฎŸ่กŒใ‚’ใ‚ญใƒฃใƒณใ‚ปใƒซใ™ใ‚‹ +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=ๅฎŸ่กŒ: +workflow.log.warning=่ญฆๅ‘Š: +workflow.log.error=ใ‚จใƒฉใƒผ: +workflow.run.cancel.requested=ใ‚ญใƒฃใƒณใ‚ปใƒซใŒ่ฆๆฑ‚ใ•ใ‚Œใพใ—ใŸ: {0}ใ€‚ +workflow.run.stop.before.id=ๅœๆญขใŒ่ฆๆฑ‚ใ•ใ‚Œใพใ—ใŸใ€‚ๅฎŸ่กŒ ID ใŒใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚ +workflow.run.cancel.http=HTTP {0}ใ‚’ใ‚ญใƒฃใƒณใ‚ปใƒซใ—ใพใ™ใ€‚ใŠใฃใจใ€‚ +workflow.run.cancel.failed=ใƒ•ใ‚ฃใ‚บใƒซใ‚’ใ‚ญใƒฃใƒณใ‚ปใƒซ: {0} +workflow.run.interrupted=ไธญๆ–ญใ•ใ‚Œใพใ—ใŸใ€‚ +workflow.run.link=ๅฎŸ่กŒ: {0} +workflow.run.discovery=ๅฎŸ่กŒใŒๅ—ใ‘ๅ…ฅใ‚Œใ‚‰ใ‚Œใพใ—ใŸใ€‚ใƒใƒณใƒ†ใ‚ฃใƒณใ‚ฐใƒฉใƒณIDใ€‚ +workflow.run.discovery.none=ๅฎŸ่กŒ ID ใŒใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚ ใ€Œใ‚ขใ‚ฏใ‚ทใƒงใƒณใ€ใ‚ฟใƒ–ใซใฏใ•ใ‚‰ใซ่ฉณใ—ใ„ๆƒ…ๅ ฑใŒใ‚ใ‚Šใพใ™ใ€‚ +workflow.run.status=ใ‚นใƒ†ใƒผใ‚ฟใ‚น: {0}{1} +workflow.run.job.main=ใ‚ธใƒงใƒ–: {0} {1} [{2}{3}{4}] +workflow.run.job.status=ใ‚นใƒ†ใƒผใ‚ฟใ‚น: {0} {1}{2}{3} +workflow.run.logs.later=GitHub ใŒใƒญใ‚ฐใ‚’ๅ…ฌ้–‹ใ™ใ‚‹ใจใ€ใƒญใ‚ฐใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ +workflow.run.job.logs.later=ใ‚ธใƒงใƒ– {0}: {1} +workflow.run.log.failed=ใƒญใ‚ฐใฎใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใซๅคฑๆ•—ใ—ใพใ—ใŸ: {0} +workflow.run.log.failed.job={0} ใฎใƒญใ‚ฐใฎใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใซๅคฑๆ•—ใ—ใพใ—ใŸ: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=ใ‚ธใƒงใƒ–: {0} +workflow.run.job.fallbackName=ใ‚ธใƒงใƒ– {0} +workflow.run.overview=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅฎŸ่กŒ {0} {1}/{2} ๅฎŒไบ†ใ€{3} ๅฎŸ่กŒไธญ +workflow.run.state.ok=[OK] +workflow.run.state.fail=[ๅคฑๆ•—] +workflow.run.state.running=[่ตฐใ‚‹] +workflow.run.state.waiting=[ๅพ…ใฃใฆ] +workflow.run.dispatch.verbs=ใƒ—ใƒฉใ‚คใƒŸใƒณใ‚ฐ|ใ‚ญใƒฅใƒผใ‚คใƒณใ‚ฐ|ๅฌๅ–š|่ตทๅ‹•|ใƒ–ใƒผใƒˆ +workflow.run.dispatch.objects=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ|่‡ชๅ‹•ๅŒ–|ใƒ‘ใ‚คใƒ—ใƒฉใ‚คใƒณ|ๅฎŸ่กŒ +workflow.run.dispatch={5} ไธŠใฎ {4} ใฎๅ ดๅˆใฏใ€{0} {1} {2}{3}ใ€‚ +workflow.run.notification.auth=GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎใƒ‡ใ‚ฃใ‚นใƒ‘ใƒƒใƒใซใฏใ€่ช่จผใ•ใ‚ŒใŸ GitHub ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใŒๅฟ…่ฆใงใ™ใ€‚ {0} ใงใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‚’่ฟฝๅŠ ใพใŸใฏๆ›ดๆ–ฐใ—ใพใ™ใ€‚ +workflow.run.notification.openSettings=GitHub่จญๅฎšใ‚’้–‹ใ +workflow.cache.progress.title=GitHub ใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎ่งฃๆฑบ +workflow.cache.progress.text={0} {1} ใฎ่งฃๆฑบ +workflow.cache.kind.action=ใ‚ขใ‚ฏใ‚ทใƒงใƒณ +workflow.cache.kind.workflow=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ +inspection.parameter.input=ๅ…ฅๅŠ› +inspection.parameter.secret=็ง˜ๅฏ† +inspection.action.delete.invalid=็„กๅŠนใช {0} [{1}] ใ‚’ๅ‰Š้™คใ—ใพใ™ +inspection.action.update.major=ใ‚ขใ‚ฏใ‚ทใƒงใƒณ [{0}] ใ‚’ [{1}] ใซๆ›ดๆ–ฐใ—ใพใ™ +inspection.warning.toggle=[{1}] ใฎ่ญฆๅ‘Š [{0}] ใ‚’ๅˆ‡ใ‚Šๆ›ฟใˆใพใ™ +inspection.warning.on=ใซ +inspection.warning.off=ใ‚ชใƒ• +inspection.statement.incomplete=ไธๅฎŒๅ…จใชใ‚นใƒ†ใƒผใƒˆใƒกใƒณใƒˆ [{0}] +inspection.invalid.suffix.remove=็„กๅŠนใชใ‚ตใƒ•ใ‚ฃใƒƒใ‚ฏใ‚นใ‚’ๅ‰Š้™ค [{0}] +inspection.replace.with=[{0}]ใซ็ฝฎใๆ›ใˆใพใ™ +inspection.invalid.remove=็„กๅŠนใช [{0}] ใ‚’ๅ‰Š้™คใ—ใพใ™ +inspection.workflow.syntax.unknownTopLevelKey=ไธๆ˜Žใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚ญใƒผ [{0}] +inspection.workflow.syntax.unknownEventKey=ไธๆ˜Žใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚คใƒ™ใƒณใƒˆ [{0}] +inspection.workflow.syntax.unknownTriggerKey=ไธๆ˜Žใชใƒˆใƒชใ‚ฌใƒผ ใ‚ญใƒผ [{0}] +inspection.workflow.syntax.unknownTriggerFilter=ไธๆ˜Žใชใƒˆใƒชใ‚ฌใƒผใƒ•ใ‚ฃใƒซใ‚ฟใƒผ [{0}] +inspection.workflow.syntax.unknownTriggerValue=ไธๆ˜Žใชใƒˆใƒชใ‚ฌใƒผๅ€ค [{0}] +inspection.workflow.syntax.unknownPermission=ไธๆ˜Žใชๆจฉ้™ [{0}] +inspection.workflow.syntax.unknownPermissionValue=ไธๆ˜Žใชๆจฉ้™ๅ€ค [{0}] +inspection.workflow.syntax.unknownJobKey=ไธๆ˜Žใชใ‚ธใƒงใƒ–ใ‚ญใƒผ [{0}] +inspection.workflow.syntax.unknownStepKey=ไธๆ˜Žใชใ‚นใƒ†ใƒƒใƒ—ใ‚ญใƒผ [{0}] +inspection.action.reload=ใƒชใƒญใƒผใƒ‰ [{0}] +inspection.action.unresolved=ๆœช่งฃๆฑบ [{0}] - GitHub ใ‚ขใ‚ซใ‚ฆใƒณใƒˆ ใ‚ขใ‚ฏใ‚ปใ‚นใ€ใƒ—ใƒฉใ‚คใƒ™ใƒผใƒˆ ใƒชใƒใ‚ธใƒˆใƒชใฎใ‚ขใ‚ฏใ‚ปใ‚น่จฑๅฏใ€ใƒฌใƒผใƒˆๅˆถ้™ใ€ๅ‚็…งใฎๆฌ ่ฝใ€ใพใŸใฏใ‚ขใ‚ฏใ‚ทใƒงใƒณ/ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎใƒกใ‚ฟใƒ‡ใƒผใ‚ฟใฎๆฌ ่ฝใ‚’็ขบ่ชใ—ใฆใใ ใ•ใ„ใ€‚ +inspection.action.jump=ใƒ•ใ‚กใ‚คใƒซ[{0}]ใธใ‚ธใƒฃใƒณใƒ— +inspection.output.unused=ๆœชไฝฟ็”จใ€{0}ใ€‘ +inspection.secret.invalid.if=[{0}] ใ‚’ๅ‰Š้™ค - ใ‚ทใƒผใ‚ฏใƒฌใƒƒใƒˆใฏใ€Œifใ€ใ‚นใƒ†ใƒผใƒˆใƒกใƒณใƒˆใงใฏ็„กๅŠนใงใ™ +inspection.secret.replace.runtime=[{0}] ใ‚’ [{1}] ใซ็ฝฎใๆ›ใˆใพใ™ - ๅฎŸ่กŒๆ™‚ใซๆไพ›ใ•ใ‚Œใชใ„ๅ ดๅˆ +inspection.needs.invalid.job=็„กๅŠนใช jobId ใ‚’ๅ‰Š้™ค [{0}] - ใ“ใฎ jobId ใฏไปฅๅ‰ใฎใฉใฎใ‚ธใƒงใƒ–ใซใ‚‚ไธ€่‡ดใ—ใพใ›ใ‚“ +documentation.description=่ชฌๆ˜Ž: {0} +documentation.type=ใ‚ฟใ‚คใƒ—: {0} +documentation.required=ๅฟ…้ ˆ: {0} +documentation.default=ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ: {0} +documentation.deprecated=้žๆŽจๅฅจ: {0} +documentation.open.declaration=ใ‚ชใƒผใƒ—ใƒณๅฎฃ่จ€ ({0}) +documentation.input.label=ๅ…ฅๅŠ› +documentation.secret.label=็ง˜ๅฏ† +documentation.env.label=็’ฐๅขƒๅค‰ๆ•ฐ +documentation.matrix.label=่กŒๅˆ—ใฎใƒ—ใƒญใƒ‘ใƒ†ใ‚ฃ +documentation.need.label=ๅฟ…่ฆใชไป•ไบ‹ +documentation.need.description=็›ดๆŽฅ็š„ใชไป•ไบ‹ใธใฎไพๅญ˜ใ€‚ +documentation.needOutput.label=ๅฟ…่ฆใชใ‚ธใƒงใƒ–ๅ‡บๅŠ› +documentation.reusableJob.label=ๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚ธใƒงใƒ– +documentation.reusableJob.description=ใ“ใฎๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใงๅฎฃ่จ€ใ•ใ‚ŒใŸใ‚ธใƒงใƒ–ใ€‚ +documentation.reusableJobOutput.label=ๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚ธใƒงใƒ–ใฎๅ‡บๅŠ› +documentation.service.label=ใ‚ตใƒผใƒ“ใ‚นใ‚ณใƒณใƒ†ใƒŠ +documentation.servicePort.label=ใ‚ตใƒผใƒ“ใ‚นใƒใƒผใƒˆ +documentation.container.label=ใ‚ธใƒงใƒ–ใ‚ณใƒณใƒ†ใƒŠ +documentation.symbol.label=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎใ‚ทใƒณใƒœใƒซ +documentation.symbol.description=่งฃๆฑบใ•ใ‚ŒใŸใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅผใ€‚ +documentation.workflowOutput.label=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎๅ‡บๅŠ› +documentation.jobOutput.label=ใ‚ธใƒงใƒ–ใฎๅ‡บๅŠ› +documentation.action.label=ใ‚ขใ‚ฏใ‚ทใƒงใƒณ +documentation.externalAction.label=ๅค–้ƒจใ‚ขใ‚ฏใ‚ทใƒงใƒณ +documentation.reusableWorkflow.label=ๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ +documentation.resolvedFrom={0} ใ‹ใ‚‰่งฃๆฑบใ•ใ‚Œใพใ—ใŸ +documentation.notResolved=ใพใ ่งฃๆฑบใ•ใ‚Œใฆใ„ใชใ„ +documentation.inputs.title=ๅ…ฅๅŠ› +documentation.outputs.title=ๅ‡บๅŠ› +documentation.secrets.title=็ง˜ๅฏ† +documentation.value.label=ๅ€ค +documentation.step.title=ใ‚นใƒ†ใƒƒใƒ—{0} +documentation.name.label=ๅๅ‰ +documentation.uses.label=็”จ้€” +documentation.run.label=่ตฐใ‚‹ +documentation.description.label=่ชฌๆ˜Ž +documentation.step.label=ใ‚นใƒ†ใƒƒใƒ— +documentation.source.label=ใ‚ฝใƒผใ‚น +documentation.stepOutput.label=ใ‚นใƒ†ใƒƒใƒ—ๅ‡บๅŠ› +documentation.context.github=ใ‚ฎใƒƒใƒˆใƒใƒ–ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆ +documentation.context.github.description=็พๅœจใฎใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎๅฎŸ่กŒใจใ‚คใƒ™ใƒณใƒˆใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑใ€‚ +documentation.context.gitea=ใ‚ฎใƒ†ใ‚ขใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆ +documentation.context.gitea.description=GitHub ใ‚ขใ‚ฏใ‚ทใƒงใƒณ ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใฎ Gitea ไบ’ๆ›ใฎใ‚จใ‚คใƒชใ‚ขใ‚นใ€‚ +documentation.context.inputs=ๅ…ฅๅŠ›ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆ +documentation.context.inputs.description=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ€ใƒ‡ใ‚ฃใ‚นใƒ‘ใƒƒใƒใ€ใพใŸใฏใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎๅ…ฅๅŠ›ใฏใ“ใ“ใงๅˆฉ็”จใงใใพใ™ใ€‚ +documentation.context.secrets=็ง˜ๅฏ†ใฎใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆ +documentation.context.secrets.description=ใ“ใฎใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใพใŸใฏๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅ‘ผใณๅ‡บใ—ใงไฝฟ็”จใงใใ‚‹ใ‚ทใƒผใ‚ฏใƒฌใƒƒใƒˆๅ€คใ€‚ +documentation.context.env=็’ฐๅขƒใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆ +documentation.context.env.description=ใ“ใฎๅ ดๆ‰€ใซ็’ฐๅขƒๅค‰ๆ•ฐใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ +documentation.context.matrix=ใƒžใƒˆใƒชใƒƒใ‚ฏใ‚นใฎใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆ +documentation.context.matrix.description=็พๅœจใฎใ‚ธใƒงใƒ–ใฎใƒžใƒˆใƒชใƒƒใ‚ฏใ‚นๅ€คใ€‚ +documentation.context.steps=ใ‚นใƒ†ใƒƒใƒ—ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆ +documentation.context.steps.description=็พๅœจใฎใ‚ธใƒงใƒ–ใฎๅ‰ใฎใ‚นใƒ†ใƒƒใƒ— (ๅ‡บๅŠ›ใจใ‚นใƒ†ใƒผใ‚ฟใ‚นใ‚’ๅซใ‚€)ใ€‚ +documentation.context.needs=ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใŒๅฟ…่ฆใงใ™ +documentation.context.needs.description=ใ‚ธใƒงใƒ–ใฎ็›ดๆŽฅใฎไพๅญ˜้–ขไฟ‚ใจใใฎๅ‡บๅŠ›/็ตๆžœใ€‚ +documentation.context.jobs=ใ‚ธใƒงใƒ–ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆ +documentation.context.jobs.description=ๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚ธใƒงใƒ–ใจๅ‡บๅŠ›ใ€‚ +documentation.context.outputs=ๅ‡บๅŠ› +documentation.context.outputs.description=ใ“ใฎใ‚นใƒ†ใƒƒใƒ—ใพใŸใฏใ‚ธใƒงใƒ–ใซใ‚ˆใฃใฆๅ…ฌ้–‹ใ•ใ‚Œใ‚‹ๅ‡บๅŠ›ๅ€คใ€‚ +documentation.context.result=็ตๆžœ +documentation.context.result.description=ใ‚ธใƒงใƒ–ใฎ็ตๆžœ: ๆˆๅŠŸใ€ๅคฑๆ•—ใ€ใ‚ญใƒฃใƒณใ‚ปใƒซใ€ใ‚นใ‚ญใƒƒใƒ—ใ€‚ +documentation.context.outcome=็ตๆžœ +documentation.context.outcome.description=continue-on-error ใŒ้ฉ็”จใ•ใ‚Œใ‚‹ๅ‰ใฎใ‚นใƒ†ใƒƒใƒ—ใฎ็ตๆžœใ€‚ +documentation.context.conclusion=็ต่ซ– +documentation.context.conclusion.description=continue-on-error ใŒ้ฉ็”จใ•ใ‚ŒใŸๅพŒใฎใ‚นใƒ†ใƒƒใƒ—ใฎ็ตๆžœใ€‚ +error.report.action=ไพ‹ๅค–ใ‚’ๅ ฑๅ‘Šใ™ใ‚‹ +error.report.description=่ชฌๆ˜Ž +error.report.steps=ๅ†็พๆ‰‹้ † +error.report.sample=่ฉฒๅฝ“ใ™ใ‚‹ๅ ดๅˆใฏใ‚ณใƒผใƒ‰ใ‚ตใƒณใƒ—ใƒซใ‚’ๆไพ›ใ—ใฆใใ ใ•ใ„ +error.report.message=ใƒกใƒƒใ‚ปใƒผใ‚ธ +error.report.runtime=ใƒฉใƒณใ‚ฟใ‚คใƒ ๆƒ…ๅ ฑ +error.report.pluginVersion=ใƒ—ใƒฉใ‚ฐใ‚คใƒณใฎใƒใƒผใ‚ธใƒงใƒณ: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=ใ‚นใ‚ฟใƒƒใ‚ฏใƒˆใƒฌใƒผใ‚น +completion.shell.bash=Bash ใ‚ทใ‚งใƒซใ€‚ Linux ใŠใ‚ˆใณ macOS ใƒฉใƒณใƒŠใƒผใงใฏ bash ใ‚’ไฝฟ็”จใ—ใ€Windows ใƒฉใƒณใƒŠใƒผใงใฏ Windows bash ใซ Git ใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚ +completion.shell.sh=POSIX ใ‚ทใ‚งใƒซ ใƒ•ใ‚ฉใƒผใƒซใƒใƒƒใ‚ฏใ€‚ +completion.shell.pwsh=PowerShellใ‚ณใ‚ขใ€‚ +completion.shell.powershell=Windows PowerShellใ€‚ +completion.shell.cmd=Windows ใ‚ณใƒžใƒณใƒ‰ ใƒ—ใƒญใƒณใƒ—ใƒˆใ€‚ +completion.shell.python=Python ใ‚ณใƒžใƒณใƒ‰ใƒฉใƒณใƒŠใƒผใ€‚ +completion.runner.name=ใ‚ธใƒงใƒ–ใ‚’ๅฎŸ่กŒใ™ใ‚‹ใƒฉใƒณใƒŠใƒผใฎๅๅ‰ใ€‚ +completion.runner.os=ใ‚ธใƒงใƒ–ใ‚’ๅฎŸ่กŒใ™ใ‚‹ใƒฉใƒณใƒŠใƒผใฎใ‚ชใƒšใƒฌใƒผใƒ†ใ‚ฃใƒณใ‚ฐ ใ‚ทใ‚นใƒ†ใƒ ใ€‚ๅฏ่ƒฝใชๅ€คใฏใ€Linuxใ€Windowsใ€ใพใŸใฏ macOS ใงใ™ใ€‚ +completion.runner.arch=ใ‚ธใƒงใƒ–ใ‚’ๅฎŸ่กŒใ™ใ‚‹ใƒฉใƒณใƒŠใƒผใฎใ‚ขใƒผใ‚ญใƒ†ใ‚ฏใƒใƒฃใ€‚ๅฏ่ƒฝใชๅ€คใฏใ€X86ใ€X64ใ€ARMใ€ใพใŸใฏ ARM64 ใงใ™ใ€‚ +completion.runner.temp=ใƒฉใƒณใƒŠใƒผไธŠใฎไธ€ๆ™‚ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใธใฎใƒ‘ใ‚นใ€‚ใ“ใฎใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใฏใ€ๅ„ใ‚ธใƒงใƒ–ใฎ้–‹ๅง‹ๆ™‚ใจ็ต‚ไบ†ๆ™‚ใซ็ฉบใซใชใ‚Šใพใ™ใ€‚ใƒฉใƒณใƒŠใƒผใฎใƒฆใƒผใ‚ถใƒผ ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใซใƒ•ใ‚กใ‚คใƒซใ‚’ๅ‰Š้™คใ™ใ‚‹ๆจฉ้™ใŒใชใ„ๅ ดๅˆใ€ใƒ•ใ‚กใ‚คใƒซใฏๅ‰Š้™คใ•ใ‚Œใชใ„ใ“ใจใซๆณจๆ„ใ—ใฆใใ ใ•ใ„ใ€‚ +completion.runner.toolCache=GitHub ใงใƒ›ใ‚นใƒˆใ•ใ‚Œใ‚‹ใƒฉใƒณใƒŠใƒผ็”จใฎใƒ—ใƒชใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚ŒใŸใƒ„ใƒผใƒซใ‚’ๅซใ‚€ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใธใฎใƒ‘ใ‚นใ€‚ +completion.runner.debug=ใ“ใ‚Œใฏใƒ‡ใƒใƒƒใ‚ฐ ใƒญใ‚ฐใŒๆœ‰ๅŠนใชๅ ดๅˆใซใฎใฟ่จญๅฎšใ•ใ‚Œใ€ๅ€คใฏๅธธใซ 1 ใซใชใ‚Šใพใ™ใ€‚ +completion.runner.environment=ใ‚ธใƒงใƒ–ใ‚’ๅฎŸ่กŒใ™ใ‚‹ใƒฉใƒณใƒŠใƒผใฎ็’ฐๅขƒใ€‚ๅฏ่ƒฝใชๅ€คใฏใ€github-hosted ใพใŸใฏ self-hosted ใงใ™ใ€‚ +completion.job.status=ใ‚ธใƒงใƒ–ใฎ็พๅœจใฎใ‚นใƒ†ใƒผใ‚ฟใ‚นใ€‚ +completion.job.checkRunId=็พๅœจใฎใ‚ธใƒงใƒ–ใฎใƒใ‚งใƒƒใ‚ฏๅฎŸ่กŒ IDใ€‚ +completion.job.container=ใ‚ธใƒงใƒ–ใฎใ‚ณใƒณใƒ†ใƒŠใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑใ€‚ +completion.job.services=ใ‚ธใƒงใƒ–็”จใซไฝœๆˆใ•ใ‚ŒใŸใ‚ตใƒผใƒ“ใ‚น ใ‚ณใƒณใƒ†ใƒŠใ€‚ +completion.job.workflowRef=็พๅœจใฎใ‚ธใƒงใƒ–ใ‚’ๅฎš็พฉใ™ใ‚‹ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใƒ•ใ‚กใ‚คใƒซใฎๅฎŒๅ…จใชๅ‚็…งใ€‚ +completion.job.workflowSha=็พๅœจใฎใ‚ธใƒงใƒ–ใ‚’ๅฎš็พฉใ™ใ‚‹ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใƒ•ใ‚กใ‚คใƒซใฎใ‚ณใƒŸใƒƒใƒˆ SHAใ€‚ +completion.job.workflowRepository=็พๅœจใฎใ‚ธใƒงใƒ–ใ‚’ๅฎš็พฉใ™ใ‚‹ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใƒ•ใ‚กใ‚คใƒซใ‚’ๅซใ‚€ใƒชใƒใ‚ธใƒˆใƒชใฎๆ‰€ๆœ‰่€…/ใƒชใƒใ‚ธใƒˆใƒชใ€‚ +completion.job.workflowFilePath=ใƒชใƒใ‚ธใƒˆใƒช ใƒซใƒผใƒˆใ‚’ๅŸบๆบ–ใจใ—ใŸใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใƒ•ใ‚กใ‚คใƒซ ใƒ‘ใ‚นใ€‚ +completion.job.containerField=ใ‚ธใƒงใƒ–ใ‚ณใƒณใƒ†ใƒŠใƒ•ใ‚ฃใƒผใƒซใƒ‰ +completion.job.service=ใ‚ธใƒงใƒ–ใ‚ตใƒผใƒ“ใ‚น +completion.job.serviceField=ใ‚ธใƒงใƒ–ใ‚ตใƒผใƒ“ใ‚นๅˆ†้‡Ž +completion.job.mappedServicePort=ใƒžใƒƒใƒ”ใƒณใ‚ฐใ•ใ‚ŒใŸใ‚ตใƒผใƒ“ใ‚นใƒใƒผใƒˆ +completion.strategy.failFast=ใƒžใƒˆใƒชใƒƒใ‚ฏใ‚น ใ‚ธใƒงใƒ–ใŒๅคฑๆ•—ใ—ใŸๅ ดๅˆใซใ€้€ฒ่กŒไธญใฎใ‚ธใƒงใƒ–ใ‚’ใ™ในใฆใ‚ญใƒฃใƒณใ‚ปใƒซใ™ใ‚‹ใ‹ใฉใ†ใ‹ใ€‚ +completion.strategy.jobIndex=ใƒžใƒˆใƒชใƒƒใ‚ฏใ‚นๅ†…ใฎ็พๅœจใฎใ‚ธใƒงใƒ–ใฎใ‚ผใƒญใ‹ใ‚‰ๅง‹ใพใ‚‹ใ‚คใƒณใƒ‡ใƒƒใ‚ฏใ‚นใ€‚ +completion.strategy.jobTotal=ใƒžใƒˆใƒชใƒƒใ‚ฏใ‚นๅ†…ใฎใ‚ธใƒงใƒ–ใฎ็ทๆ•ฐใ€‚ +completion.strategy.maxParallel=ๅŒๆ™‚ใซๅฎŸ่กŒใงใใ‚‹ใƒžใƒˆใƒชใƒƒใ‚ฏใ‚น ใ‚ธใƒงใƒ–ใฎๆœ€ๅคงๆ•ฐใ€‚ +completion.context.inputs=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅ…ฅๅŠ› (workflow_dispatch ใพใŸใฏ workflow_call ใชใฉ)ใ€‚ +completion.context.secrets=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎ็ง˜ๅฏ†ใ€‚ +completion.context.job=็พๅœจๅฎŸ่กŒไธญใฎใ‚ธใƒงใƒ–ใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑใ€‚ +completion.context.jobs=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚ธใƒงใƒ–ใ€‚ +completion.context.matrix=็พๅœจใฎใƒžใƒˆใƒชใƒƒใ‚ฏใ‚น ใ‚ธใƒงใƒ–ใซๅฏพใ—ใฆๅฎš็พฉใ•ใ‚ŒใŸใƒžใƒˆใƒชใƒƒใ‚ฏใ‚น ใƒ—ใƒญใƒ‘ใƒ†ใ‚ฃใ€‚ +completion.context.strategy=็พๅœจใฎใ‚ธใƒงใƒ–ใฎใƒžใƒˆใƒชใƒƒใ‚ฏใ‚นๅฎŸ่กŒๆˆฆ็•ฅๆƒ…ๅ ฑใ€‚ +completion.context.steps=็พๅœจใฎใ‚ธใƒงใƒ–ๅ†…ใฎ ID ใ‚’ๆŒใคใ‚นใƒ†ใƒƒใƒ—ใ€‚ +completion.context.env=ใ‚ธใƒงใƒ–ใŠใ‚ˆใณใ‚นใƒ†ใƒƒใƒ—ใ‹ใ‚‰ใฎ็’ฐๅขƒๅค‰ๆ•ฐใ€‚ +completion.context.vars=็ต„็น”ใ€ใƒชใƒใ‚ธใƒˆใƒชใ€ใŠใ‚ˆใณ็’ฐๅขƒใ‚นใ‚ณใƒผใƒ—ใ‹ใ‚‰ใฎใ‚ซใ‚นใ‚ฟใƒ ๆง‹ๆˆๅค‰ๆ•ฐใ€‚ +completion.context.needs=ใ“ใฎใ‚ธใƒงใƒ–ใ‚’ๅฎŸ่กŒใ™ใ‚‹ๅ‰ใซๅฎŒไบ†ใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚‹ใ‚ธใƒงใƒ–ใจใ€ใใฎๅ‡บๅŠ›ใจ็ตๆžœใ€‚ +completion.context.github=GitHub ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใ‹ใ‚‰ใฎใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅฎŸ่กŒใŠใ‚ˆใณใ‚คใƒ™ใƒณใƒˆๆƒ…ๅ ฑใ€‚ +completion.context.gitea=GitHub ใ‚ขใ‚ฏใ‚ทใƒงใƒณ ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใฎ Gitea ไบ’ๆ›ใฎใ‚จใ‚คใƒชใ‚ขใ‚นใ€‚ +completion.context.runner=็พๅœจใฎใ‚ธใƒงใƒ–ใ‚’ๅฎŸ่กŒใ—ใฆใ„ใ‚‹ใƒฉใƒณใƒŠใƒผใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑใ€‚ +completion.secret.githubToken=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅฎŸ่กŒใ”ใจใซ่‡ชๅ‹•็š„ใซไฝœๆˆใ•ใ‚Œใ‚‹ใƒˆใƒผใ‚ฏใƒณใ€‚ +completion.remote.repository=ใƒชใƒขใƒผใƒˆใƒชใƒใ‚ธใƒˆใƒช +completion.uses.local.workflow=ใƒญใƒผใ‚ซใƒซใงๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ +completion.uses.local.action=ใƒญใƒผใ‚ซใƒซใ‚ขใ‚ฏใ‚ทใƒงใƒณ +completion.uses.ref.known=ๆ—ข็Ÿฅใฎใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎใƒชใƒ•ใ‚กใƒฌใƒณใ‚น +completion.uses.ref.remote=ใƒชใƒขใƒผใƒˆใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใƒชใƒ•ใ‚กใƒฌใƒณใ‚น +completion.uses.remote.known=ๆ—ข็Ÿฅใฎใƒชใƒขใƒผใƒˆ ใ‚ขใ‚ฏใ‚ทใƒงใƒณใพใŸใฏๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ +completion.workflow.syntax=GitHub ใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๆง‹ๆ–‡ +completion.workflow.top.name=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎ่กจ็คบๅ +completion.workflow.top.run-name=ๅ‹•็š„ๅฎŸ่กŒๅ +completion.workflow.top.on=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚’้–‹ๅง‹ใ™ใ‚‹ใ‚คใƒ™ใƒณใƒˆ +completion.workflow.top.permissions=ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎ GITHUB_TOKEN ๆจฉ้™ +completion.workflow.top.env=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅ…จไฝ“ใฎ็’ฐๅขƒๅค‰ๆ•ฐ +completion.workflow.top.defaults=ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใ‚ธใƒงใƒ–ใจใ‚นใƒ†ใƒƒใƒ—ใฎ่จญๅฎš +completion.workflow.top.concurrency=ๅŒๆ™‚ๅฎŸ่กŒใ‚ฐใƒซใƒผใƒ—ใจใ‚ญใƒฃใƒณใ‚ปใƒซ +completion.workflow.top.jobs=ใ“ใฎใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใงๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚ธใƒงใƒ– +completion.workflow.event.branch_protection_rule=ใƒ–ใƒฉใƒณใƒไฟ่ญทใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.check_run=ๅ˜ไธ€ใฎใƒใ‚งใƒƒใ‚ฏๅฎŸ่กŒใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.check_suite=ๅค‰ๆ›ดใ•ใ‚ŒใŸใ‚นใ‚คใƒผใƒˆใ‚’ใƒใ‚งใƒƒใ‚ฏใ™ใ‚‹ +completion.workflow.event.create=ใƒ–ใƒฉใƒณใƒใพใŸใฏใ‚ฟใ‚ฐใŒไฝœๆˆใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.delete=ใƒ–ใƒฉใƒณใƒใพใŸใฏใ‚ฟใ‚ฐใŒๅ‰Š้™คใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.deployment=ใƒ‡ใƒ—ใƒญใ‚คใƒกใƒณใƒˆใŒไฝœๆˆใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.deployment_status=ๅฐŽๅ…ฅใ‚นใƒ†ใƒผใ‚ฟใ‚นใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.discussion=่ญฐ่ซ–ใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.discussion_comment=ใƒ‡ใ‚ฃใ‚นใ‚ซใƒƒใ‚ทใƒงใƒณใฎใ‚ณใƒกใƒณใƒˆใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.fork=ใƒชใƒใ‚ธใƒˆใƒชใŒใƒ•ใ‚ฉใƒผใ‚ฏใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.gollum=Wiki ใƒšใƒผใ‚ธใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.image_version=ใƒ‘ใƒƒใ‚ฑใƒผใ‚ธใ‚คใƒกใƒผใ‚ธใฎใƒใƒผใ‚ธใƒงใƒณใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.issue_comment=ๅ•้กŒใพใŸใฏ PR ใ‚ณใƒกใƒณใƒˆใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.issues=ๅ•้กŒใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.label=ใƒฉใƒ™ใƒซใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.merge_group=ใƒžใƒผใ‚ธใ‚ญใƒฅใƒผใฎใƒใ‚งใƒƒใ‚ฏใŒ่ฆๆฑ‚ใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.milestone=ใƒžใ‚คใƒซใ‚นใƒˆใƒผใƒณใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.page_build=ใƒšใƒผใ‚ธใฎใƒ“ใƒซใƒ‰ใŒๅฎŸ่กŒใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.project=ใ‚ฏใƒฉใ‚ทใƒƒใ‚ฏใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.project_card=ใ‚ฏใƒฉใ‚ทใƒƒใ‚ฏ ใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆ ใ‚ซใƒผใƒ‰ใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.project_column=ใ‚ฏใƒฉใ‚ทใƒƒใ‚ฏ ใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆๅˆ—ใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.public=ใƒชใƒใ‚ธใƒˆใƒชใŒๅ…ฌ้–‹ใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.pull_request=ใƒ—ใƒซใƒชใ‚ฏใ‚จใ‚นใƒˆใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.pull_request_review=PR ใƒฌใƒ“ใƒฅใƒผใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.pull_request_review_comment=PR ใƒฌใƒ“ใƒฅใƒผใฎใ‚ณใƒกใƒณใƒˆใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.pull_request_target=PR ใ‚ฟใƒผใ‚ฒใƒƒใƒˆ ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใ€‚้‹ญใ„ใƒŠใ‚คใƒ•ใ€‚ +completion.workflow.event.push=ใƒ—ใƒƒใ‚ทใƒฅใ•ใ‚ŒใŸใ‚ณใƒŸใƒƒใƒˆใพใŸใฏใ‚ฟใ‚ฐ +completion.workflow.event.registry_package=ใƒ‘ใƒƒใ‚ฑใƒผใ‚ธใŒๅ…ฌ้–‹ใพใŸใฏๆ›ดๆ–ฐใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.release=ใƒชใƒชใƒผใ‚นใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.repository_dispatch=ใ‚ซใ‚นใ‚ฟใƒ APIใ‚คใƒ™ใƒณใƒˆ +completion.workflow.event.schedule=ใ‚ฏใƒญใƒณใƒใƒƒใ‚ฏใ€‚ๆ™‚่จˆไป•ๆŽ›ใ‘ใ€‚ +completion.workflow.event.status=ใ‚ณใƒŸใƒƒใƒˆใ‚นใƒ†ใƒผใ‚ฟใ‚นใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.event.watch=ใƒชใƒใ‚ธใƒˆใƒชใฎใ‚นใ‚ฟใƒผไป˜ใ +completion.workflow.event.workflow_call=ๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅ‘ผใณๅ‡บใ— +completion.workflow.event.workflow_dispatch=ๆ‰‹ๅ‹•ๅฎŸ่กŒใƒœใ‚ฟใƒณ +completion.workflow.event.workflow_run=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎๅฎŸ่กŒใŒๅค‰ๆ›ดใ•ใ‚Œใพใ—ใŸ +completion.workflow.eventFilter.types=ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ“ใƒ†ใ‚ฃใฎ็จฎ้กžใ‚’ๅˆถ้™ใ™ใ‚‹ +completion.workflow.eventFilter.branches=ใ“ใฎๆžใ ใ‘ +completion.workflow.eventFilter.branches-ignore=ใ“ใ‚Œใ‚‰ใฎใƒ–ใƒฉใƒณใƒใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ—ใฆใใ ใ•ใ„ +completion.workflow.eventFilter.tags=ใ“ใฎใ‚ฟใ‚ฐใ ใ‘ +completion.workflow.eventFilter.tags-ignore=ใ“ใ‚Œใ‚‰ใฎใ‚ฟใ‚ฐใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ™ +completion.workflow.eventFilter.paths=ใ“ใ‚Œใ‚‰ใฎ้“ใ ใ‘ +completion.workflow.eventFilter.paths-ignore=ใ“ใ‚Œใ‚‰ใฎใƒ‘ใ‚นใ‚’ใ‚นใ‚ญใƒƒใƒ—ใ—ใฆใใ ใ•ใ„ +completion.workflow.eventFilter.workflows=็›ฃ่ฆ–ใ™ใ‚‹ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅ +completion.workflow.eventFilter.cron=ใ‚ฏใƒญใƒผใƒณใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒซใ€‚ๅฐใ•ใชๆ™‚่จˆไป•ๆŽ›ใ‘ใ€‚ +completion.workflow.permission.actions=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎๅฎŸ่กŒใจใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎใ‚ขใƒผใƒ†ใ‚ฃใƒ•ใ‚กใ‚ฏใƒˆ +completion.workflow.permission.artifact-metadata=ใ‚ขใƒผใƒ†ใ‚ฃใƒ•ใ‚กใ‚ฏใƒˆใฎใƒกใ‚ฟใƒ‡ใƒผใ‚ฟ ใƒฌใ‚ณใƒผใƒ‰ +completion.workflow.permission.attestations=ใ‚ขใƒผใƒ†ใ‚ฃใƒ•ใ‚กใ‚ฏใƒˆใฎ่จผๆ˜Žๆ›ธ +completion.workflow.permission.checks=ใƒฉใƒณใจใ‚นใ‚คใƒผใƒˆใ‚’ใƒใ‚งใƒƒใ‚ฏใ™ใ‚‹ +completion.workflow.permission.code-quality=ใ‚ณใƒผใƒ‰ๅ“่ณชใƒฌใƒใƒผใƒˆ +completion.workflow.permission.contents=ใƒชใƒใ‚ธใƒˆใƒชใฎๅ†…ๅฎน +completion.workflow.permission.deployments=ๅฐŽๅ…ฅ +completion.workflow.permission.discussions=ใƒ‡ใ‚ฃใ‚นใ‚ซใƒƒใ‚ทใƒงใƒณ +completion.workflow.permission.id-token=OpenID Connect ใƒˆใƒผใ‚ฏใƒณ +completion.workflow.permission.issues=ๅ•้กŒ็‚น +completion.workflow.permission.models=GitHubใƒขใƒ‡ใƒซ +completion.workflow.permission.packages=GitHubใƒ‘ใƒƒใ‚ฑใƒผใ‚ธ +completion.workflow.permission.pages=GitHubใƒšใƒผใ‚ธ +completion.workflow.permission.pull-requests=ใƒ—ใƒซใƒชใ‚ฏใ‚จใ‚นใƒˆ +completion.workflow.permission.security-events=ใ‚ณใƒผใƒ‰ใ‚นใ‚ญใƒฃใƒณใจใ‚ปใ‚ญใƒฅใƒชใƒ†ใ‚ฃใ‚คใƒ™ใƒณใƒˆ +completion.workflow.permission.statuses=ใ‚ณใƒŸใƒƒใƒˆใ‚นใƒ†ใƒผใ‚ฟใ‚น +completion.workflow.permission.vulnerability-alerts=Dependabot ใ‚ขใƒฉใƒผใƒˆ +completion.workflow.permission.value.read=่ชญใฟๅ–ใ‚Šใ‚ขใ‚ฏใ‚ปใ‚น +completion.workflow.permission.value.write=ๆ›ธใ่พผใฟใ‚ขใ‚ฏใ‚ปใ‚นใ€่ชญใฟๅ–ใ‚Šใ‚’ๅซใ‚€ +completion.workflow.permission.value.none=ใ‚ขใ‚ฏใ‚ปใ‚นไธๅฏ +completion.workflow.permission.shorthand.read-all=ใ™ในใฆใฎๆจฉ้™ใŒ่ชญใฟๅ–ใ‚‰ใ‚Œใพใ™ใ€‚ๅคงใใชๆฏ›ๅธƒใ€‚ +completion.workflow.permission.shorthand.write-all=ใ™ในใฆใฎใ‚ขใ‚ฏใ‚ปใ‚น่จฑๅฏใŒๆ›ธใ่พผใฟใพใ™ใ€‚ๅคงใใชใƒใƒณใƒžใƒผใ€‚ +completion.workflow.permission.shorthand.empty=ใƒˆใƒผใ‚ฏใƒณๆจฉ้™ใ‚’็„กๅŠนใซใ™ใ‚‹ +completion.workflow.job.name=ใ‚ธใƒงใƒ–่กจ็คบๅ +completion.workflow.job.permissions=ใ‚ธใƒงใƒ–ใƒˆใƒผใ‚ฏใƒณใฎๆจฉ้™ +completion.workflow.job.needs=ๅพ…ใฃใฆใ„ใ‚‹ไป•ไบ‹ +completion.workflow.job.if=ๅ‹คๅ‹™ๆกไปถ +completion.workflow.job.runs-on=ใƒฉใƒณใƒŠใƒผใฎใƒฉใƒ™ใƒซใพใŸใฏใ‚ฐใƒซใƒผใƒ— +completion.workflow.job.snapshot=ใƒฉใƒณใƒŠใƒผใฎใ‚นใƒŠใƒƒใƒ—ใ‚ทใƒงใƒƒใƒˆ +completion.workflow.job.environment=ๅฐŽๅ…ฅ็’ฐๅขƒ +completion.workflow.job.concurrency=ใ‚ธใƒงใƒ–ใฎๅŒๆ™‚ๅฎŸ่กŒใƒญใƒƒใ‚ฏ +completion.workflow.job.outputs=ไป–ใฎใ‚ธใƒงใƒ–ใŒ่ชญใฟๅ–ใ‚‹ใ“ใจใŒใงใใ‚‹ๅ‡บๅŠ› +completion.workflow.job.env=ใ‚ธใƒงใƒ–็’ฐๅขƒๅค‰ๆ•ฐ +completion.workflow.job.defaults=ใ‚ธใƒงใƒ–ใฎใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ่จญๅฎš +completion.workflow.job.steps=ใ‚นใƒ†ใƒƒใƒ—ใƒชใ‚นใƒˆใ€‚ๅฎŸ้š›ใฎไฝœๆฅญใ€‚ +completion.workflow.job.timeout-minutes=ใ‚ธใƒงใƒ–ใฎใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆ (ๅˆ†ๅ˜ไฝ) +completion.workflow.job.strategy=ใƒžใƒˆใƒชใƒƒใ‚ฏใ‚นใจใ‚นใ‚ฑใ‚ธใƒฅใƒผใƒชใƒณใ‚ฐๆˆฆ็•ฅ +completion.workflow.job.continue-on-error=ใ“ใฎไป•ไบ‹ใฏใใฃใจๅคฑๆ•—ใ•ใ›ใพใ—ใ‚‡ใ† +completion.workflow.job.container=ใ“ใฎใ‚ธใƒงใƒ–ใฎใ‚ณใƒณใƒ†ใƒŠ +completion.workflow.job.services=ใ‚ตใ‚คใƒ‰ใ‚ซใƒผใ‚ตใƒผใƒ“ใ‚นใ‚ณใƒณใƒ†ใƒŠ +completion.workflow.job.uses=ๅ‘ผใณๅ‡บใ™ใŸใ‚ใฎๅ†ๅˆฉ็”จๅฏ่ƒฝใชใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ +completion.workflow.job.with=ๅ‘ผใณๅ‡บใ•ใ‚ŒใŸใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎๅ…ฅๅŠ› +completion.workflow.job.secrets=ๅ‘ผใณๅ‡บใ•ใ‚ŒใŸใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎใ‚ทใƒผใ‚ฏใƒฌใƒƒใƒˆ +completion.workflow.defaultsRun.shell=ๅฎŸ่กŒใ‚นใƒ†ใƒƒใƒ—ใฎใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใ‚ทใ‚งใƒซ +completion.workflow.defaultsRun.working-directory=ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎไฝœๆฅญใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช +completion.workflow.concurrency.group=ใ‚ญใƒฅใƒผใซๅ…ฅใ‚Œใ‚‰ใ‚ŒใŸๅฎŸ่กŒใฎใƒญใƒƒใ‚ฏๅ +completion.workflow.concurrency.cancel-in-progress=ๅคใ„ไธ€่‡ดใ™ใ‚‹ๅฎŸ่กŒใ‚’ใ‚ญใƒฃใƒณใ‚ปใƒซใ™ใ‚‹ +completion.workflow.environment.name=็’ฐๅขƒๅ +completion.workflow.environment.url=็’ฐๅขƒ URL +completion.workflow.strategy.matrix=ใƒžใƒˆใƒชใƒƒใ‚ฏใ‚น่ปธใจใƒใƒชใ‚ขใƒณใƒˆ +completion.workflow.strategy.fail-fast=ๅคฑๆ•—ๆ™‚ใซ่กŒๅˆ—ใฎๅ…„ๅผŸใ‚’ใ‚ญใƒฃใƒณใ‚ปใƒซใ™ใ‚‹ +completion.workflow.strategy.max-parallel=่กŒๅˆ—ไธฆๅˆ—ๅบฆใฎไธŠ้™ +completion.workflow.matrix.include=ใƒžใƒˆใƒชใƒƒใ‚ฏใ‚นใฎ็ต„ใฟๅˆใ‚ใ›ใ‚’่ฟฝๅŠ ใ™ใ‚‹ +completion.workflow.matrix.exclude=่กŒๅˆ—ใฎ็ต„ใฟๅˆใ‚ใ›ใ‚’ๅ‰Š้™คใ™ใ‚‹ +completion.workflow.step.id=ๅ‚็…ง็”จใฎใ‚นใƒ†ใƒƒใƒ— ID +completion.workflow.step.if=ใ‚นใƒ†ใƒƒใƒ—ๆกไปถ +completion.workflow.step.name=ใ‚นใƒ†ใƒƒใƒ—่กจ็คบๅ +completion.workflow.step.uses=ๅฎŸ่กŒใ™ใ‚‹ใ‚ขใ‚ฏใ‚ทใƒงใƒณ +completion.workflow.step.run=ๅฎŸ่กŒใ™ใ‚‹ใ‚ทใ‚งใƒซใ‚นใ‚ฏใƒชใƒ—ใƒˆ +completion.workflow.step.shell=ใ“ใฎๅฎŸ่กŒใ‚นใƒ†ใƒƒใƒ—ใฎใ‚ทใ‚งใƒซ +completion.workflow.step.with=ใ‚ขใ‚ฏใ‚ทใƒงใƒณๅ…ฅๅŠ› +completion.workflow.step.env=ใ‚นใƒ†ใƒƒใƒ—็’ฐๅขƒๅค‰ๆ•ฐ +completion.workflow.step.continue-on-error=ใ“ใฎใ‚นใƒ†ใƒƒใƒ—ใฏใใฃใจๅคฑๆ•—ใ•ใ›ใพใ—ใ‚‡ใ† +completion.workflow.step.timeout-minutes=ใ‚นใƒ†ใƒƒใƒ—ใฎใ‚ฟใ‚คใƒ ใ‚ขใ‚ฆใƒˆ (ๅˆ†ๅ˜ไฝ) +completion.workflow.step.working-directory=ใ‚นใƒ†ใƒƒใƒ—ไฝœๆฅญใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช +completion.workflow.container.image=ใ‚ณใƒณใƒ†ใƒŠใ‚คใƒกใƒผใ‚ธ +completion.workflow.container.credentials=ใƒฌใ‚ธใ‚นใƒˆใƒชใฎ่ณ‡ๆ ผๆƒ…ๅ ฑ +completion.workflow.container.env=ใ‚ณใƒณใƒ†ใƒŠ็’ฐๅขƒๅค‰ๆ•ฐ +completion.workflow.container.ports=ๅ…ฌ้–‹ใ™ใ‚‹ใƒใƒผใƒˆ +completion.workflow.container.volumes=ใƒžใ‚ฆใƒณใƒˆใ™ใ‚‹ใƒœใƒชใƒฅใƒผใƒ  +completion.workflow.container.options=Docker ไฝœๆˆใ‚ชใƒ—ใ‚ทใƒงใƒณ +completion.workflow.service.image=ใ‚ตใƒผใƒ“ใ‚นใ‚ณใƒณใƒ†ใƒŠใ‚คใƒกใƒผใ‚ธ +completion.workflow.service.credentials=ใƒฌใ‚ธใ‚นใƒˆใƒชใฎ่ณ‡ๆ ผๆƒ…ๅ ฑ +completion.workflow.service.env=ใ‚ตใƒผใƒ“ใ‚น็’ฐๅขƒๅค‰ๆ•ฐ +completion.workflow.service.ports=ใ‚ตใƒผใƒ“ใ‚นใƒใƒผใƒˆ +completion.workflow.service.volumes=ใ‚ตใƒผใƒ“ใ‚น้‡ +completion.workflow.service.options=Docker ไฝœๆˆใ‚ชใƒ—ใ‚ทใƒงใƒณ +completion.workflow.credentials.username=ใƒฌใ‚ธใ‚นใƒˆใƒชใฎใƒฆใƒผใ‚ถใƒผๅ +completion.workflow.credentials.password=ใƒฌใ‚ธใ‚นใƒˆใƒชใฎใƒ‘ใ‚นใƒฏใƒผใƒ‰ใพใŸใฏใƒˆใƒผใ‚ฏใƒณ +completion.workflow.inputType.string=ใƒ†ใ‚ญใ‚นใƒˆๅ…ฅๅŠ› +completion.workflow.inputType.boolean=็œŸใพใŸใฏๅฝใฎๅ…ฅๅŠ› +completion.workflow.inputType.choice=ใƒ‰ใƒญใƒƒใƒ—ใƒ€ใ‚ฆใƒณ้ธๆŠžๅ…ฅๅŠ› +completion.workflow.inputType.number=ๆ•ฐๅ€คๅ…ฅๅŠ› +completion.workflow.inputType.environment=็’ฐๅขƒใƒ”ใƒƒใ‚ซใƒผใฎๅ…ฅๅŠ› +completion.workflow.boolean.true=ใฏใ„ใ€‚่ฃ่ฟ”ใ—ใฆใ‚ชใƒณใซใ—ใพใ™ใ€‚ +completion.workflow.boolean.false=ใ„ใ„ใˆใ€ๆš—ใใ—ใฆใใ ใ•ใ„ใ€‚ +completion.workflow.runner.ubuntu-latest=ๆœ€ๆ–ฐใฎUbuntuใƒฉใƒณใƒŠใƒผ +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 ใƒฉใƒณใƒŠใƒผ +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 ใƒฉใƒณใƒŠใƒผ +completion.workflow.runner.windows-latest=ๆœ€ๆ–ฐใฎWindowsใƒฉใƒณใƒŠใƒผ +completion.workflow.runner.windows-2025=Windows ใ‚ตใƒผใƒใƒผ 2025 ใƒฉใƒณใƒŠใƒผ +completion.workflow.runner.windows-2022=Windows ใ‚ตใƒผใƒใƒผ 2022 ใƒฉใƒณใƒŠใƒผ +completion.workflow.runner.macos-latest=ๆœ€ๆ–ฐใฎmacOSใƒฉใƒณใƒŠใƒผ +completion.workflow.runner.macos-15=macOS 15 ใƒฉใƒณใƒŠใƒผ +completion.workflow.runner.macos-14=macOS 14 ใƒฉใƒณใƒŠใƒผ +completion.workflow.runner.self-hosted=ใ‚ใชใŸ่‡ช่บซใฎใƒฉใƒณใƒŠใƒผใ€‚ใ‚ใชใŸใฎใ‚ตใƒผใ‚ซใ‚นใ€‚ +completion.steps.outputs=ใ‚นใƒ†ใƒƒใƒ—ใซๅฏพใ—ใฆๅฎš็พฉใ•ใ‚ŒใŸๅ‡บๅŠ›ใฎใ‚ปใƒƒใƒˆใ€‚ +completion.steps.conclusion=continue-on-error ใŒ้ฉ็”จใ•ใ‚ŒใŸๅพŒใซๅฎŒไบ†ใ—ใŸใ‚นใƒ†ใƒƒใƒ—ใฎ็ตๆžœใ€‚ +completion.steps.outcome=ใ‚จใƒฉใƒผๆ™‚็ถš่กŒใŒ้ฉ็”จใ•ใ‚Œใ‚‹ๅ‰ใซๅฎŒไบ†ใ—ใŸใ‚นใƒ†ใƒƒใƒ—ใฎ็ตๆžœใ€‚ +completion.jobs.outputs=ใ‚ธใƒงใƒ–ใซๅฏพใ—ใฆๅฎš็พฉใ•ใ‚ŒใŸๅ‡บๅŠ›ใฎใ‚ปใƒƒใƒˆใ€‚ +completion.jobs.result=ไป•ไบ‹ใฎ็ตๆžœใ€‚ +settings.displayName=GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ +settings.language.label=่จ€่ชž: +settings.language.system=IDE/ใ‚ทใ‚นใƒ†ใƒ ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ +settings.cache.title=ใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚ญใƒฃใƒƒใ‚ทใƒฅ +settings.cache.column.key=ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚ญใƒผ +settings.cache.column.name=ๅๅ‰ +settings.cache.column.kind=็จฎ้กž +settings.cache.column.state=ๅทž +settings.cache.column.expires=ๆœ‰ๅŠนๆœŸ้™ใŒๅˆ‡ใ‚Œใพใ™ +settings.cache.kind.local=ๅœฐๅ…ƒใฎ +settings.cache.kind.remote=ใƒชใƒขใƒผใƒˆ +settings.cache.state.resolved=่งฃๆฑบๆธˆใฟ +settings.cache.state.pending=ไฟ็•™ไธญ +settings.cache.state.expired=ๅคใ„ +settings.cache.state.suppressed=ๆŠ‘ๅˆถใ•ใ‚ŒใŸ +settings.cache.refresh=Refresh ใƒ†ใƒผใƒ–ใƒซ +settings.cache.deleteSelected=้ธๆŠžใ—ใŸใ‚‚ใฎใ‚’ๅ‰Š้™ค +settings.cache.deleteAll=ใ™ในใฆๅ‰Š้™ค +settings.cache.export=ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆ +settings.cache.import=ใ‚คใƒณใƒใƒผใƒˆ +settings.cache.summary=ใ‚ญใƒฃใƒƒใ‚ทใƒฅ: {0} ใ‚จใƒณใƒˆใƒชใ€{1} ่งฃๆฑบๆธˆใฟใ€{2} ใƒชใƒขใƒผใƒˆใ€{3} ็„กๅŠนใ€{4} ใƒŸใƒฅใƒผใƒˆใ€‚ใ‚ญใƒฃใƒƒใ‚ทใƒฅ: {5} KBใ€‚ +settings.cache.noneSelected=ๆœ€ๅˆใซใ‚ญใƒฃใƒƒใ‚ทใƒฅ่กŒใ‚’้ธๆŠžใ—ใพใ™ใ€‚ใปใ†ใใฏๆŽจๆธฌใ‚’ๆ‹’ๅฆใ—ใพใ™ใ€‚ +settings.cache.deleteSelected.done={0} ใ‚ญใƒฃใƒƒใ‚ทใƒฅ ใ‚จใƒณใƒˆใƒชใ‚’ๅ‰Š้™คใ—ใพใ—ใŸใ€‚ๅฐใ•ใชๅกต้›ฒใŒๅซใพใ‚Œใฆใ„ใพใ™ใ€‚ +settings.cache.deleteAll.confirm=ใ™ในใฆใฎ GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚ญใƒฃใƒƒใ‚ทใƒฅ ใ‚จใƒณใƒˆใƒชใ‚’ๅ‰Š้™คใ—ใพใ™ใ‹? +settings.cache.deleteAll.done=ใ™ในใฆใฎใ‚ญใƒฃใƒƒใ‚ทใƒฅ ใ‚จใƒณใƒˆใƒชใ‚’ๅ‰Š้™คใ—ใพใ—ใŸใ€‚ใ‚ญใƒฃใƒƒใ‚ทใƒฅใฏ็–‘ใ‚ใ—ใ„ใปใฉ้™ใ‹ใซใชใ‚Šใพใ—ใŸใ€‚ +settings.cache.export.done={0} ใ‚ญใƒฃใƒƒใ‚ทใƒฅ ใ‚จใƒณใƒˆใƒชใ‚’ใ‚จใ‚ฏใ‚นใƒใƒผใƒˆใ—ใพใ—ใŸใ€‚ๆชปใซๅ…ฅใ‚Œใ‚‰ใ‚ŒใŸๅฐใ•ใชใ‚ขใƒผใ‚ซใ‚คใƒ–ใฎ็ฃใ€‚ +settings.cache.import.done=ใ‚คใƒณใƒใƒผใƒˆใ•ใ‚ŒใŸใ‚ญใƒฃใƒƒใ‚ทใƒฅ ใ‚จใƒณใƒˆใƒชใ€‚ใ‚ขใƒผใ‚ซใ‚คใƒ–ใฎ็ฃใฏ่กŒๅ‹•ใ—ใŸใ€‚ +settings.cache.import.unsupported=ใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใชใ„ GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚ญใƒฃใƒƒใ‚ทใƒฅ ใƒ•ใ‚กใ‚คใƒซใงใ™ใ€‚ +settings.cache.import.brokenLine=GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚ญใƒฃใƒƒใ‚ทใƒฅ ใƒฉใ‚คใƒณใŒๅฃŠใ‚Œใฆใ„ใพใ™ใ€‚ +settings.cache.import.brokenKey=GitHub ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผ ใ‚ญใƒฃใƒƒใ‚ทใƒฅ ใ‚ญใƒผใŒๅฃŠใ‚Œใฆใ„ใพใ™ใ€‚ +settings.support.button=ใ“ใฎใƒ—ใƒฉใ‚ฐใ‚คใƒณใ‚’ใ‚ตใƒใƒผใƒˆใ™ใ‚‹ +settings.support.tooltip=ใ‚ตใƒใƒผใƒˆใƒšใƒผใ‚ธใ‚’้–‹ใ +settings.support.line.0=ใƒ“ใƒซใƒ‰็‚‰ใซไพ›็ตฆใ™ใ‚‹ +settings.support.line.1=ใƒ‘ใƒผใ‚ตใƒผใ‚ณใƒผใƒ’ใƒผใ‚’่ณผๅ…ฅใ™ใ‚‹ +settings.support.line.2=ใ‚นใƒใƒณใ‚ตใƒผใจใชใฃใฆๅนฝ้œŠใฎๅ‡บใ‚‹ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚’ๆธ›ใ‚‰ใ™ +workflow.run.jobs.title=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚ธใƒงใƒ– +workflow.run.jobs.root=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใฎๅฎŸ่กŒ +workflow.run.jobs.description=GitHub ใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎใ‚ธใƒงใƒ– ใƒ„ใƒชใƒผใจ้ธๆŠžใ•ใ‚ŒใŸใ‚ธใƒงใƒ– ใƒญใ‚ฐ +workflow.run.tree.done=ๅฎŒไบ†ใ—ใพใ—ใŸ +workflow.run.tree.failed=ๅคฑๆ•—ใ—ใพใ—ใŸ +workflow.run.tree.skipped=ใ‚นใ‚ญใƒƒใƒ—ใ—ใพใ—ใŸ +workflow.run.tree.warn=่ญฆๅ‘Šใ™ใ‚‹ +workflow.run.tree.err=ใ‚จใƒฉใƒผ +workflow.run.delete.tooltip=ๅฎŸ่กŒใฎๅ‰Š้™ค +workflow.run.delete.noRun=ๅฎŸ่กŒ ID ใŒใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚ +workflow.run.delete.requested=ๅฎŸ่กŒ {0} ใ‚’ๅ‰Š้™คใ—ใฆใ„ใพใ™ใ€‚ +workflow.run.delete.done={0} ใ‚’ๅ‰Š้™คใ—ใพใ—ใŸใ€‚ +workflow.run.delete.http=HTTP {0} ใ‚’ๅ‰Š้™คใ—ใพใ™ใ€‚ใŠใฃใจใ€‚ +workflow.run.delete.failed=ๅ‰Š้™คใŒๅคฑๆ•—ใ—ใพใ—ใŸ: {0} +workflow.run.rerun.all.tooltip=ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚’ๅ†ๅฎŸ่กŒใ™ใ‚‹ +workflow.run.rerun.failed.tooltip=ๅคฑๆ•—ใ—ใŸใ‚ธใƒงใƒ–ใ‚’ๅ†ๅฎŸ่กŒใ™ใ‚‹ +workflow.run.rerun.noRun=ๅฎŸ่กŒ ID ใŒใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚ +workflow.run.rerun.all.requested=ๅ†ๅฎŸ่กŒใŒ่ฆๆฑ‚ใ•ใ‚Œใพใ—ใŸ: {0}ใ€‚ +workflow.run.rerun.failed.requested=่ฆๆฑ‚ใ•ใ‚ŒใŸๅคฑๆ•—ใ—ใŸใ‚ธใƒงใƒ–: {0}ใ€‚ +workflow.run.rerun.all.done=ๅ†ๅฎŸ่กŒใ‚ญใƒฅใƒผ: {0}ใ€‚ +workflow.run.rerun.failed.done=ใ‚ญใƒฅใƒผใซ็™ป้Œฒใ•ใ‚ŒใŸๅคฑๆ•—ใ—ใŸใ‚ธใƒงใƒ–: {0}ใ€‚ +workflow.run.rerun.http=HTTP {0} ใ‚’ๅ†ๅฎŸ่กŒใ—ใพใ™ใ€‚ใŠใฃใจใ€‚ +workflow.run.rerun.failed=ๅ†ๅฎŸ่กŒใŒๅœๆญขใ—ใพใ—ใŸ: {0} +workflow.run.download.log.tooltip=ใ‚ธใƒงใƒ–ใƒญใ‚ฐใฎไฟๅญ˜ +workflow.run.download.artifacts.tooltip=ใ‚ขใƒผใƒ†ใ‚ฃใƒ•ใ‚กใ‚ฏใƒˆใ‚’ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใ™ใ‚‹ +workflow.run.download.noRun=ๅฎŸ่กŒ ID ใŒใพใ ใ‚ใ‚Šใพใ›ใ‚“ใ€‚ +workflow.run.download.log.requested={0} ใฎใƒญใ‚ฐใ‚’ๅ–ๅพ—ใ—ใฆใ„ใพใ™ใ€‚ +workflow.run.download.log.done=ไฟๅญ˜ใ•ใ‚ŒใŸใƒญใ‚ฐ: {0}ใ€‚ +workflow.run.download.artifacts.requested=ใ‚ขใƒผใƒ†ใ‚ฃใƒ•ใ‚กใ‚ฏใƒˆใ‚’ๅ–ๅพ—ใ—ใฆใ„ใพใ™ใ€‚ +workflow.run.download.artifacts.empty=ใ‚ขใƒผใƒ†ใ‚ฃใƒ•ใ‚กใ‚ฏใƒˆใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚ๅฐใ•ใช็ฉบ่™šใ€‚ +workflow.run.download.artifact.expired=ใ‚ขใƒผใƒ†ใ‚ฃใƒ•ใ‚กใ‚ฏใƒˆใฎๆœ‰ๅŠนๆœŸ้™ใŒๅˆ‡ใ‚Œใพใ—ใŸ: {0}ใ€‚ +workflow.run.download.artifact.done=ไฟๅญ˜ใ•ใ‚ŒใŸใ‚ขใƒผใƒ†ใ‚ฃใƒ•ใ‚กใ‚ฏใƒˆ: {0} -> {1}ใ€‚ +workflow.run.download.failed=ใƒ€ใ‚ฆใƒณใƒญใƒผใƒ‰ใŒๅœๆญขใ—ใพใ—ใŸ: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ko.properties b/src/main/resources/messages/GitHubWorkflowBundle_ko.properties new file mode 100644 index 0000000..e7aa7fb --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_ko.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub ์ž‘์—… ํ๋ฆ„ +plugin.description=GitHub ์ž‘์—… ์›Œํฌํ”Œ๋กœ ํŒŒ์ผ ์ง€์› +group.GitHubWorkflow.Tools.text=GitHub ์ž‘์—…ํ๋ฆ„ +group.GitHubWorkflow.Tools.description=GitHub ์›Œํฌํ”Œ๋กœ์šฐ ํ”Œ๋Ÿฌ๊ทธ์ธ ๋„๊ตฌ +action.GitHubWorkflow.RefreshActionCache.text=Refresh ์•ก์…˜ ์บ์‹œ +action.GitHubWorkflow.RefreshActionCache.description=Refresh๋Š” ์›๊ฒฉ GitHub ์ž‘์—… ๋ฐ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. +action.GitHubWorkflow.RestoreActionWarnings.text=๋ณต์› ์ž‘์—… ๊ฒฝ๊ณ  +action.GitHubWorkflow.RestoreActionWarnings.description=์–ต์ œ๋œ ์ž‘์—…, ์ž…๋ ฅ ๋ฐ ์ถœ๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ฒฝ๊ณ ๋ฅผ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค. +action.GitHubWorkflow.ClearActionCache.text=์•ก์…˜ ์บ์‹œ ์ง€์šฐ๊ธฐ +action.GitHubWorkflow.ClearActionCache.description=์บ์‹œ๋œ GitHub ์ž‘์—… ๋ฐ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ง€์šฐ๊ธฐ +notification.cache.cleared={0} ์บ์‹œ๋œ GitHub ์›Œํฌํ”Œ๋กœ ํ•ญ๋ชฉ์„ ์ง€์› ์Šต๋‹ˆ๋‹ค. +notification.cache.refresh.started=Refreshing {0}๋Š” ์›๊ฒฉ GitHub ์›Œํฌํ”Œ๋กœ ํ•ญ๋ชฉ์„ ์บ์‹œํ–ˆ์Šต๋‹ˆ๋‹ค. +notification.warnings.restored={0} GitHub ์›Œํฌํ”Œ๋กœ ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ๊ฒฝ๊ณ ๊ฐ€ ๋ณต์›๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +workflow.run.configuration.display=GitHub ์ž‘์—… ํ๋ฆ„ +workflow.run.configuration.description=GitHub Actions ์›Œํฌํ”Œ๋กœ์šฐ ์‹คํ–‰์„ ๋””์ŠคํŒจ์น˜ํ•˜๊ณ  ๋”ฐ๋ฅด์‹ญ์‹œ์˜ค. +workflow.run.configuration.name=GitHub ์ž‘์—…ํ๋ฆ„: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=์†Œ์œ ์ž +workflow.run.field.repo=์ €์žฅ์†Œ +workflow.run.field.workflow=์›Œํฌํ”Œ๋กœ ํŒŒ์ผ +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=ํ† ํฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋Œ€์ฒด +workflow.run.inputs.title=workflow_dispatch ์ž…๋ ฅ(ํ‚ค=๊ฐ’) +workflow.run.error.apiUrl=GitHub API URL๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. +workflow.run.error.repository=GitHub ์ €์žฅ์†Œ ์†Œ์œ ์ž ๋ฐ ์ด๋ฆ„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. +workflow.run.error.workflow=์›Œํฌํ”Œ๋กœ ํŒŒ์ผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. +workflow.run.error.ref=๋ถ„๊ธฐ ๋˜๋Š” ํƒœ๊ทธ ์ฐธ์กฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. +workflow.run.error.inputs=GitHub workflow_dispatch๋Š” ์ตœ๋Œ€ 25๊ฐœ์˜ ์ž…๋ ฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. +workflow.run.gutter.stop=์›Œํฌํ”Œ๋กœ ์‹คํ–‰ ์ค‘์ง€ +workflow.run.gutter.stop.text=์›Œํฌํ”Œ๋กœ ์‹คํ–‰ ์ค‘์ง€ +workflow.run.gutter.stop.description=์ด ์‹คํ–‰ ์ทจ์†Œ +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=์‹คํ–‰: +workflow.log.warning=๊ฒฝ๊ณ : +workflow.log.error=์˜ค๋ฅ˜: +workflow.run.cancel.requested=์ทจ์†Œ ์š”์ฒญ๋จ: {0}. +workflow.run.stop.before.id=์ค‘์ง€๋ฅผ ์š”์ฒญํ–ˆ์Šต๋‹ˆ๋‹ค. ์•„์ง ์‹คํ–‰ ID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. +workflow.run.cancel.http=HTTP {0}๋ฅผ ์ทจ์†Œํ•ฉ๋‹ˆ๋‹ค. ๋ˆ. +workflow.run.cancel.failed=์–ด์ง€๋Ÿฌ์šด ์ทจ์†Œ: {0} +workflow.run.interrupted=์ค‘๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +workflow.run.link=์‹คํ–‰: {0} +workflow.run.discovery=์‹คํ–‰์ด ์Šน์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ๋ƒฅ ์‹คํ–‰ ID. +workflow.run.discovery.none=์•„์ง ์‹คํ–‰ ID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ž‘์—… ํƒญ์— ๋” ๋งŽ์€ ์ •๋ณด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. +workflow.run.status=์ƒํƒœ: {0}{1} +workflow.run.job.main=์ž‘์—…: {0} {1} [{2}{3}{4}] +workflow.run.job.status=์ƒํƒœ: {0} {1}{2}{3} +workflow.run.logs.later=GitHub๊ฐ€ ๊ฒŒ์‹œํ•˜๋ฉด ๋กœ๊ทธ๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. +workflow.run.job.logs.later=์ž‘์—… {0}: {1} +workflow.run.log.failed=๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ: {0} +workflow.run.log.failed.job={0}: {1}์— ๋Œ€ํ•œ ๋กœ๊ทธ ๋‹ค์šด๋กœ๋“œ ์‹คํŒจ +workflow.run.job.url=URL: {0} +workflow.run.job.header=์ง์—…: {0} +workflow.run.job.fallbackName=์ž‘์—… {0} +workflow.run.overview=์›Œํฌํ”Œ๋กœ ์‹คํ–‰ {0} {1}/{2} ์™„๋ฃŒ, {3} ์‹คํ–‰ +workflow.run.state.ok=[ํ™•์ธ] +workflow.run.state.fail=[์‹คํŒจ] +workflow.run.state.running=[์‹คํ–‰] +workflow.run.state.waiting=[๊ธฐ๋‹ค๋ ค] +workflow.run.dispatch.verbs=ํ”„๋ผ์ด๋ฐ|๋Œ€๊ธฐ ์ค‘|์†Œํ™˜ ์ค‘|๋ฐœ์‚ฌ ์ค‘|๋ถ€ํŒ… ์ค‘ +workflow.run.dispatch.objects=์›Œํฌํ”Œ๋กœ|์ž๋™ํ™”|ํŒŒ์ดํ”„๋ผ์ธ|์‹คํ–‰ +workflow.run.dispatch={5}์˜ {4}์šฉ {0} {1} {2}{3}. +workflow.run.notification.auth=GitHub ์›Œํฌํ”Œ๋กœ ๋””์ŠคํŒจ์น˜์—๋Š” ์ธ์ฆ๋œ GitHub ๊ณ„์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. {0}์—์„œ ๊ณ„์ •์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ ๊ณ ์นฉ๋‹ˆ๋‹ค. +workflow.run.notification.openSettings=GitHub ์„ค์ • ์—ด๊ธฐ +workflow.cache.progress.title=GitHub ์ž‘์—… ํ•ด๊ฒฐ +workflow.cache.progress.text={0} {1} ํ•ด๊ฒฐ +workflow.cache.kind.action=ํ–‰๋™ +workflow.cache.kind.workflow=์ž‘์—… ํ๋ฆ„ +inspection.parameter.input=์ž…๋ ฅ +inspection.parameter.secret=๋น„๋ฐ€ +inspection.action.delete.invalid=์ž˜๋ชป๋œ {0} ์‚ญ์ œ [{1}] +inspection.action.update.major=์ž‘์—… [{0}]๋ฅผ [{1}]๋กœ ์—…๋ฐ์ดํŠธ +inspection.warning.toggle=[{1}]์— ๋Œ€ํ•œ ๊ฒฝ๊ณ  [{0}] ์ „ํ™˜ +inspection.warning.on=์— +inspection.warning.off=๋–จ์–ด์ ธ์„œ +inspection.statement.incomplete=๋ถˆ์™„์ „ํ•œ ๋ฌธ [{0}] +inspection.invalid.suffix.remove=์ž˜๋ชป๋œ ์ ‘๋ฏธ์‚ฌ [{0}] ์ œ๊ฑฐ +inspection.replace.with=[{0}]๋กœ ๊ต์ฒด +inspection.invalid.remove=์ž˜๋ชป๋œ [{0}] ์ œ๊ฑฐ +inspection.workflow.syntax.unknownTopLevelKey=์•Œ ์ˆ˜ ์—†๋Š” ์›Œํฌํ”Œ๋กœ ํ‚ค [{0}] +inspection.workflow.syntax.unknownEventKey=์•Œ ์ˆ˜ ์—†๋Š” ์›Œํฌํ”Œ๋กœ ์ด๋ฒคํŠธ [{0}] +inspection.workflow.syntax.unknownTriggerKey=์•Œ ์ˆ˜ ์—†๋Š” ํŠธ๋ฆฌ๊ฑฐ ํ‚ค [{0}] +inspection.workflow.syntax.unknownTriggerFilter=์•Œ ์ˆ˜ ์—†๋Š” ํŠธ๋ฆฌ๊ฑฐ ํ•„ํ„ฐ [{0}] +inspection.workflow.syntax.unknownTriggerValue=์•Œ ์ˆ˜ ์—†๋Š” ํŠธ๋ฆฌ๊ฑฐ ๊ฐ’ [{0}] +inspection.workflow.syntax.unknownPermission=์•Œ ์ˆ˜ ์—†๋Š” ๊ถŒํ•œ [{0}] +inspection.workflow.syntax.unknownPermissionValue=์•Œ ์ˆ˜ ์—†๋Š” ๊ถŒํ•œ ๊ฐ’ [{0}] +inspection.workflow.syntax.unknownJobKey=์•Œ ์ˆ˜ ์—†๋Š” ์ž‘์—… ํ‚ค [{0}] +inspection.workflow.syntax.unknownStepKey=์•Œ ์ˆ˜ ์—†๋Š” ๋‹จ๊ณ„ ํ‚ค [{0}] +inspection.action.reload=[{0}] ์ƒˆ๋กœ๊ณ ์นจ +inspection.action.unresolved=ํ•ด๊ฒฐ๋˜์ง€ ์•Š์Œ [{0}] - GitHub ๊ณ„์ • ์•ก์„ธ์Šค, ๊ฐœ์ธ ์ €์žฅ์†Œ ๊ถŒํ•œ, ์†๋„ ์ œํ•œ, ๋ˆ„๋ฝ๋œ ์ฐธ์กฐ ๋˜๋Š” ๋ˆ„๋ฝ๋œ ์ž‘์—…/์›Œํฌํ”Œ๋กœ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•˜์„ธ์š”. +inspection.action.jump=[{0}] ํŒŒ์ผ๋กœ ์ด๋™ +inspection.output.unused=๋ฏธ์‚ฌ์šฉ [{0}] +inspection.secret.invalid.if=[{0}] ์ œ๊ฑฐ - `if` ๋ฌธ์—์„œ๋Š” ๋น„๋ฐ€์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +inspection.secret.replace.runtime=[{0}]๋ฅผ [{1}]๋กœ ๊ต์ฒด - ๋Ÿฐํƒ€์ž„ ์‹œ ์ œ๊ณต๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ +inspection.needs.invalid.job=์ž˜๋ชป๋œ ์ž‘์—… ID [{0}] ์ œ๊ฑฐ - ์ด ์ž‘์—… ID๋Š” ์ด์ „ ์ž‘์—…๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +documentation.description=์„ค๋ช…: {0} +documentation.type=์œ ํ˜•: {0} +documentation.required=ํ•„์ˆ˜: {0} +documentation.default=๊ธฐ๋ณธ๊ฐ’: {0} +documentation.deprecated=๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ: {0} +documentation.open.declaration=๊ณต๊ฐœ ์„ ์–ธ({0}) +documentation.input.label=์ž…๋ ฅ +documentation.secret.label=๋น„๋ฐ€ +documentation.env.label=ํ™˜๊ฒฝ๋ณ€์ˆ˜ +documentation.matrix.label=๋งคํŠธ๋ฆญ์Šค ์†์„ฑ +documentation.need.label=ํ•„์š”ํ•œ ์ง์—… +documentation.need.description=์ง์ ‘์ ์ธ ์ง์—… ์˜์กด์„ฑ. +documentation.needOutput.label=ํ•„์š”ํ•œ ์ž‘์—… ์ถœ๋ ฅ +documentation.reusableJob.label=์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ ์ž‘์—… +documentation.reusableJob.description=์ด ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ์— ์ž‘์—…์ด ์„ ์–ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +documentation.reusableJobOutput.label=์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ ์ž‘์—… ์ถœ๋ ฅ +documentation.service.label=์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ +documentation.servicePort.label=์„œ๋น„์Šค ํฌํŠธ +documentation.container.label=์ž‘์—… ์ปจํ…Œ์ด๋„ˆ +documentation.symbol.label=์›Œํฌํ”Œ๋กœ ๊ธฐํ˜ธ +documentation.symbol.description=์›Œํฌํ”Œ๋กœ ํ‘œํ˜„์ด ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +documentation.workflowOutput.label=์›Œํฌํ”Œ๋กœ ์ถœ๋ ฅ +documentation.jobOutput.label=์ž‘์—… ์ถœ๋ ฅ +documentation.action.label=์•ก์…˜ +documentation.externalAction.label=์™ธ๋ถ€ ์กฐ์น˜ +documentation.reusableWorkflow.label=์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ +documentation.resolvedFrom={0}์—์„œ ํ•ด๊ฒฐ๋จ +documentation.notResolved=์•„์ง ํ•ด๊ฒฐ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค +documentation.inputs.title=์ž…๋ ฅ +documentation.outputs.title=์ถœ๋ ฅ +documentation.secrets.title=๋น„๋ฐ€ +documentation.value.label=๊ฐ€์น˜ +documentation.step.title=๋‹จ๊ณ„ {0} +documentation.name.label=์ด๋ฆ„ +documentation.uses.label=์šฉ๋„ +documentation.run.label=์‹คํ–‰ +documentation.description.label=์„ค๋ช… +documentation.step.label=๋‹จ๊ณ„ +documentation.source.label=์†Œ์Šค +documentation.stepOutput.label=๋‹จ๊ณ„ ์ถœ๋ ฅ +documentation.context.github=๊นƒํ—ˆ๋ธŒ ์ปจํ…์ŠคํŠธ +documentation.context.github.description=ํ˜„์žฌ ์›Œํฌํ”Œ๋กœ ์‹คํ–‰ ๋ฐ ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค. +documentation.context.gitea=๊ธฐํ…Œ์•„ ์ปจํ…์ŠคํŠธ +documentation.context.gitea.description=GitHub ์ž‘์—… ์ปจํ…์ŠคํŠธ์— ๋Œ€ํ•œ Gitea ํ˜ธํ™˜ ๋ณ„์นญ์ž…๋‹ˆ๋‹ค. +documentation.context.inputs=์ž…๋ ฅ ์ปจํ…์ŠคํŠธ +documentation.context.inputs.description=์—ฌ๊ธฐ์—์„œ ์›Œํฌํ”Œ๋กœ, ๋””์ŠคํŒจ์น˜ ๋˜๋Š” ์ž‘์—… ์ž…๋ ฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. +documentation.context.secrets=๋น„๋ฐ€ ์ปจํ…์ŠคํŠธ +documentation.context.secrets.description=์ด ์›Œํฌํ”Œ๋กœ ๋˜๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ ํ˜ธ์ถœ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋น„๋ฐ€ ๊ฐ’์ž…๋‹ˆ๋‹ค. +documentation.context.env=ํ™˜๊ฒฝ ์ปจํ…์ŠคํŠธ +documentation.context.env.description=์ด ์œ„์น˜์— ํ‘œ์‹œ๋˜๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. +documentation.context.matrix=๋งคํŠธ๋ฆญ์Šค ์ปจํ…์ŠคํŠธ +documentation.context.matrix.description=ํ˜„์žฌ ์ž‘์—…์— ๋Œ€ํ•œ ๋งคํŠธ๋ฆญ์Šค ๊ฐ’์ž…๋‹ˆ๋‹ค. +documentation.context.steps=๋‹จ๊ณ„ ์ปจํ…์ŠคํŠธ +documentation.context.steps.description=์ถœ๋ ฅ ๋ฐ ์ƒํƒœ๋ฅผ ํฌํ•จํ•˜์—ฌ ํ˜„์žฌ ์ž‘์—…์˜ ์ด์ „ ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค. +documentation.context.needs=๋งฅ๋ฝ์ด ํ•„์š”ํ•˜๋‹ค +documentation.context.needs.description=์ง์ ‘์ ์ธ ์ž‘์—… ์ข…์†์„ฑ๊ณผ ํ•ด๋‹น ์ถœ๋ ฅ/๊ฒฐ๊ณผ. +documentation.context.jobs=์ง์—… ๋งฅ๋ฝ +documentation.context.jobs.description=์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ ์ž‘์—… ๋ฐ ์ถœ๋ ฅ. +documentation.context.outputs=์ถœ๋ ฅ +documentation.context.outputs.description=์ด ๋‹จ๊ณ„ ๋˜๋Š” ์ž‘์—…์—์„œ ๋…ธ์ถœ๋˜๋Š” ์ถœ๋ ฅ ๊ฐ’์ž…๋‹ˆ๋‹ค. +documentation.context.result=๊ฒฐ๊ณผ +documentation.context.result.description=์ž‘์—… ๊ฒฐ๊ณผ: ์„ฑ๊ณต, ์‹คํŒจ, ์ทจ์†Œ ๋˜๋Š” ๊ฑด๋„ˆ๋›ฐ๊ธฐ. +documentation.context.outcome=๊ฒฐ๊ณผ +documentation.context.outcome.description=์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๊ณ„์† ์ ์šฉ ์ „ ๋‹จ๊ณ„ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. +documentation.context.conclusion=๊ฒฐ๋ก  +documentation.context.conclusion.description=์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๊ณ„์† ์ ์šฉ ํ›„ ๋‹จ๊ณ„ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. +error.report.action=์˜ˆ์™ธ ๋ณด๊ณ  +error.report.description=์„ค๋ช… +error.report.steps=์žฌํ˜„ ๋‹จ๊ณ„ +error.report.sample=ํ•ด๋‹น๋˜๋Š” ๊ฒฝ์šฐ ์ฝ”๋“œ ์ƒ˜ํ”Œ์„ ์ œ๊ณตํ•ด ์ฃผ์„ธ์š”. +error.report.message=๋ฉ”์‹œ์ง€ +error.report.runtime=๋Ÿฐํƒ€์ž„ ์ •๋ณด +error.report.pluginVersion=ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฒ„์ „: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=์ŠคํƒํŠธ๋ ˆ์ด์Šค +completion.shell.bash=Bash ์‰˜. Linux ๋ฐ macOS ์‹คํ–‰๊ธฐ์—์„œ๋Š” bash๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , Windows ์‹คํ–‰๊ธฐ์—์„œ๋Š” Windows bash์šฉ Git์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. +completion.shell.sh=POSIX ์‰˜ ๋Œ€์ฒด. +completion.shell.pwsh=PowerShell ์ฝ”์–ด. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Windows ๋ช…๋ น ํ”„๋กฌํ”„ํŠธ. +completion.shell.python=Python ๋ช…๋ น ์‹คํ–‰๊ธฐ. +completion.runner.name=์ž‘์—…์„ ์‹คํ–‰ํ•˜๋Š” ์‹คํ–‰์ž์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. +completion.runner.os=์ž‘์—…์„ ์‹คํ–‰ํ•˜๋Š” ์‹คํ–‰๊ธฐ์˜ ์šด์˜ ์ฒด์ œ์ž…๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•œ ๊ฐ’์€ Linux, Windows ๋˜๋Š” macOS์ž…๋‹ˆ๋‹ค. +completion.runner.arch=์ž‘์—…์„ ์‹คํ–‰ํ•˜๋Š” ์‹คํ–‰๊ธฐ์˜ ์•„ํ‚คํ…์ฒ˜์ž…๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•œ ๊ฐ’์€ X86, X64, ARM ๋˜๋Š” ARM64์ž…๋‹ˆ๋‹ค. +completion.runner.temp=์‹คํ–‰๊ธฐ์˜ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค. ์ด ๋””๋ ‰ํ„ฐ๋ฆฌ๋Š” ๊ฐ ์ž‘์—…์˜ ์‹œ์ž‘๊ณผ ๋์—์„œ ๋น„์›Œ์ง‘๋‹ˆ๋‹ค. ๋Ÿฌ๋„ˆ์˜ ์‚ฌ์šฉ์ž ๊ณ„์ •์— ํŒŒ์ผ ์‚ญ์ œ ๊ถŒํ•œ์ด ์—†์œผ๋ฉด ํŒŒ์ผ์€ ์ œ๊ฑฐ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +completion.runner.toolCache=GitHub ํ˜ธ์ŠคํŒ… ์‹คํ–‰๊ธฐ๋ฅผ ์œ„ํ•œ ์‚ฌ์ „ ์„ค์น˜๋œ ๋„๊ตฌ๊ฐ€ ํฌํ•จ๋œ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค. +completion.runner.debug=๋””๋ฒ„๊ทธ ๋กœ๊น…์ด ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ์—๋งŒ ์„ค์ •๋˜๋ฉฐ ํ•ญ์ƒ ๊ฐ’์ด 1์ž…๋‹ˆ๋‹ค. +completion.runner.environment=์ž‘์—…์„ ์‹คํ–‰ํ•˜๋Š” ๋Ÿฌ๋„ˆ์˜ ํ™˜๊ฒฝ์ž…๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•œ ๊ฐ’์€ github ํ˜ธ์ŠคํŒ… ๋˜๋Š” ์ž์ฒด ํ˜ธ์ŠคํŒ…์ž…๋‹ˆ๋‹ค. +completion.job.status=์ž‘์—…์˜ ํ˜„์žฌ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. +completion.job.checkRunId=ํ˜„์žฌ ์ž‘์—…์˜ ํ™•์ธ ์‹คํ–‰ ID์ž…๋‹ˆ๋‹ค. +completion.job.container=์ž‘์—… ์ปจํ…Œ์ด๋„ˆ์— ๋Œ€ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค. +completion.job.services=์ž‘์—…์„ ์œ„ํ•ด ์ƒ์„ฑ๋œ ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ์ž…๋‹ˆ๋‹ค. +completion.job.workflowRef=ํ˜„์žฌ ์ž‘์—…์„ ์ •์˜ํ•˜๋Š” ์›Œํฌํ”Œ๋กœ ํŒŒ์ผ์˜ ์ „์ฒด ์ฐธ์กฐ์ž…๋‹ˆ๋‹ค. +completion.job.workflowSha=ํ˜„์žฌ ์ž‘์—…์„ ์ •์˜ํ•˜๋Š” ์›Œํฌํ”Œ๋กœ ํŒŒ์ผ์˜ ์ปค๋ฐ‹ SHA์ž…๋‹ˆ๋‹ค. +completion.job.workflowRepository=ํ˜„์žฌ ์ž‘์—…์„ ์ •์˜ํ•˜๋Š” ์›Œํฌํ”Œ๋กœ ํŒŒ์ผ์ด ํฌํ•จ๋œ ์ €์žฅ์†Œ์˜ ์†Œ์œ ์ž/์ €์žฅ์†Œ์ž…๋‹ˆ๋‹ค. +completion.job.workflowFilePath=๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋ฃจํŠธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•œ ์›Œํฌํ”Œ๋กœ ํŒŒ์ผ ๊ฒฝ๋กœ์ž…๋‹ˆ๋‹ค. +completion.job.containerField=์ž‘์—… ์ปจํ…Œ์ด๋„ˆ ํ•„๋“œ +completion.job.service=์ทจ์—… ์„œ๋น„์Šค +completion.job.serviceField=์ทจ์—…์„œ๋น„์Šค ๋ถ„์•ผ +completion.job.mappedServicePort=๋งคํ•‘๋œ ์„œ๋น„์Šค ํฌํŠธ +completion.strategy.failFast=๋งคํŠธ๋ฆญ์Šค ์ž‘์—…์ด ์‹คํŒจํ•  ๊ฒฝ์šฐ ์ง„ํ–‰ ์ค‘์ธ ๋ชจ๋“  ์ž‘์—…์ด ์ทจ์†Œ๋˜๋Š”์ง€ ์—ฌ๋ถ€์ž…๋‹ˆ๋‹ค. +completion.strategy.jobIndex=๋งคํŠธ๋ฆญ์Šค์— ์žˆ๋Š” ํ˜„์žฌ ์ž‘์—…์˜ 0๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋Š” ์ธ๋ฑ์Šค์ž…๋‹ˆ๋‹ค. +completion.strategy.jobTotal=๋งคํŠธ๋ฆญ์Šค์˜ ์ด ์ž‘์—… ์ˆ˜์ž…๋‹ˆ๋‹ค. +completion.strategy.maxParallel=๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ๋งคํŠธ๋ฆญ์Šค ์ž‘์—… ์ˆ˜์ž…๋‹ˆ๋‹ค. +completion.context.inputs=workflow_dispatch ๋˜๋Š” workflow_call์™€ ๊ฐ™์€ ์›Œํฌํ”Œ๋กœ ์ž…๋ ฅ์ž…๋‹ˆ๋‹ค. +completion.context.secrets=์›Œํฌํ”Œ๋กœ ๋น„๋ฐ€. +completion.context.job=ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ์ž‘์—…์— ๋Œ€ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค. +completion.context.jobs=์›Œํฌํ”Œ๋กœ ์ž‘์—…. +completion.context.matrix=ํ˜„์žฌ ๋งคํŠธ๋ฆญ์Šค ์ž‘์—…์— ๋Œ€ํ•ด ์ •์˜๋œ ๋งคํŠธ๋ฆญ์Šค ์†์„ฑ์ž…๋‹ˆ๋‹ค. +completion.context.strategy=ํ˜„์žฌ ์ž‘์—…์— ๋Œ€ํ•œ ๋งคํŠธ๋ฆญ์Šค ์‹คํ–‰ ์ „๋žต ์ •๋ณด์ž…๋‹ˆ๋‹ค. +completion.context.steps=ํ˜„์žฌ ์ž‘์—…์— ID๊ฐ€ ์žˆ๋Š” ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค. +completion.context.env=์ž‘์—… ๋ฐ ๋‹จ๊ณ„์˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. +completion.context.vars=์กฐ์ง, ์ €์žฅ์†Œ ๋ฐ ํ™˜๊ฒฝ ๋ฒ”์œ„์˜ ์‚ฌ์šฉ์ž ์ •์˜ ๊ตฌ์„ฑ ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค. +completion.context.needs=์ด ์ž‘์—…์„ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ์™„๋ฃŒํ•ด์•ผ ํ•˜๋Š” ์ž‘์—…๊ณผ ํ•ด๋‹น ์ถœ๋ ฅ ๋ฐ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. +completion.context.github=GitHub ์ปจํ…์ŠคํŠธ์˜ ์›Œํฌํ”Œ๋กœ ์‹คํ–‰ ๋ฐ ์ด๋ฒคํŠธ ์ •๋ณด์ž…๋‹ˆ๋‹ค. +completion.context.gitea=GitHub ์ž‘์—… ์ปจํ…์ŠคํŠธ์— ๋Œ€ํ•œ Gitea ํ˜ธํ™˜ ๋ณ„์นญ์ž…๋‹ˆ๋‹ค. +completion.context.runner=ํ˜„์žฌ ์ž‘์—…์„ ์‹คํ–‰ํ•˜๋Š” ๋Ÿฌ๋„ˆ์— ๋Œ€ํ•œ ์ •๋ณด์ž…๋‹ˆ๋‹ค. +completion.secret.githubToken=๊ฐ ์›Œํฌํ”Œ๋กœ ์‹คํ–‰์— ๋Œ€ํ•ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ํ† ํฐ์ž…๋‹ˆ๋‹ค. +completion.remote.repository=์›๊ฒฉ ์ €์žฅ์†Œ +completion.uses.local.workflow=๋กœ์ปฌ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ์šฐ +completion.uses.local.action=์ง€์—ญ ํ™œ๋™ +completion.uses.ref.known=์•Œ๋ ค์ง„ ์›Œํฌํ”Œ๋กœ ์ฐธ์กฐ +completion.uses.ref.remote=์›๊ฒฉ ์›Œํฌํ”Œ๋กœ ์ฐธ์กฐ +completion.uses.remote.known=์•Œ๋ ค์ง„ ์›๊ฒฉ ์ž‘์—… ๋˜๋Š” ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ž‘์—… ํ๋ฆ„ +completion.workflow.syntax=GitHub ์ž‘์—… ์›Œํฌํ”Œ๋กœ ๊ตฌ๋ฌธ +completion.workflow.top.name=์›Œํฌํ”Œ๋กœ ํ‘œ์‹œ ์ด๋ฆ„ +completion.workflow.top.run-name=๋™์  ์‹คํ–‰ ์ด๋ฆ„ +completion.workflow.top.on=์›Œํฌํ”Œ๋กœ๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ์ด๋ฒคํŠธ +completion.workflow.top.permissions=๊ธฐ๋ณธ GITHUB_TOKEN ๊ถŒํ•œ +completion.workflow.top.env=์›Œํฌํ”Œ๋กœ ์ „์ฒด ํ™˜๊ฒฝ ๋ณ€์ˆ˜ +completion.workflow.top.defaults=๊ธฐ๋ณธ ์ž‘์—… ๋ฐ ๋‹จ๊ณ„ ์„ค์ • +completion.workflow.top.concurrency=๋™์‹œ์„ฑ ๊ทธ๋ฃน ๋ฐ ์ทจ์†Œ +completion.workflow.top.jobs=์ด ์›Œํฌํ”Œ๋กœ์—์„œ ์‹คํ–‰๋˜๋Š” ์ž‘์—… +completion.workflow.event.branch_protection_rule=๋ถ„๊ธฐ ๋ณดํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋จ +completion.workflow.event.check_run=๋‹จ์ผ ์ ๊ฒ€ ์‹คํ–‰์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.check_suite=๋ณ€๊ฒฝ๋œ ์Šค์œ„ํŠธ ํ™•์ธ +completion.workflow.event.create=๋ถ„๊ธฐ ๋˜๋Š” ํƒœ๊ทธ๊ฐ€ ์ƒ์„ฑ๋จ +completion.workflow.event.delete=๋ถ„๊ธฐ ๋˜๋Š” ํƒœ๊ทธ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.deployment=๋ฐฐํฌ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.deployment_status=๋ฐฐํฌ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋จ +completion.workflow.event.discussion=ํ† ๋ก ์ด ๋ณ€๊ฒฝ๋จ +completion.workflow.event.discussion_comment=ํ† ๋ก  ๋Œ“๊ธ€์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.fork=์ €์žฅ์†Œ๊ฐ€ ํฌํฌ๋จ +completion.workflow.event.gollum=์œ„ํ‚ค ํŽ˜์ด์ง€๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.image_version=ํŒจํ‚ค์ง€ ์ด๋ฏธ์ง€ ๋ฒ„์ „์ด ๋ณ€๊ฒฝ๋จ +completion.workflow.event.issue_comment=๋ฌธ์ œ ๋˜๋Š” PR ์„ค๋ช…์ด ๋ณ€๊ฒฝ๋จ +completion.workflow.event.issues=๋ฌธ์ œ๊ฐ€ ๋ณ€๊ฒฝ๋จ +completion.workflow.event.label=๋ผ๋ฒจ์ด ๋ณ€๊ฒฝ๋จ +completion.workflow.event.merge_group=๋ณ‘ํ•ฉ ๋Œ€๊ธฐ์—ด ํ™•์ธ์ด ์š”์ฒญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.milestone=๋งˆ์ผ์Šคํ†ค์ด ๋ณ€๊ฒฝ๋จ +completion.workflow.event.page_build=ํŽ˜์ด์ง€ ๋นŒ๋“œ๊ฐ€ ์‹คํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.project=ํด๋ž˜์‹ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.project_card=ํด๋ž˜์‹ ํ”„๋กœ์ ํŠธ ์นด๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.project_column=ํด๋ž˜์‹ ํ”„๋กœ์ ํŠธ ์—ด์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.public=์ €์žฅ์†Œ๊ฐ€ ๊ณต๊ฐœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค +completion.workflow.event.pull_request=ํ’€ ์š”์ฒญ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.pull_request_review=PR ๋ฆฌ๋ทฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.pull_request_review_comment=PR ๋ฆฌ๋ทฐ ๋Œ“๊ธ€์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.pull_request_target=PR ๋Œ€์ƒ ์ปจํ…์ŠคํŠธ. ๋‚ ์นด๋กœ์šด ์นผ. +completion.workflow.event.push=์ปค๋ฐ‹ ๋˜๋Š” ํƒœ๊ทธ ํ‘ธ์‹œ +completion.workflow.event.registry_package=ํŒจํ‚ค์ง€๊ฐ€ ๊ฒŒ์‹œ๋˜๊ฑฐ๋‚˜ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.release=๋ฆด๋ฆฌ์Šค๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +completion.workflow.event.repository_dispatch=์ปค์Šคํ…€ API ์ด๋ฒคํŠธ +completion.workflow.event.schedule=ํฌ๋ก  ํ‹ฑ. ์‹œ๊ณ„ ์žฅ์น˜. +completion.workflow.event.status=์ปค๋ฐ‹ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋จ +completion.workflow.event.watch=์ €์žฅ์†Œ์— ๋ณ„ํ‘œํ‘œ์‹œ๋จ +completion.workflow.event.workflow_call=์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ ํ˜ธ์ถœ +completion.workflow.event.workflow_dispatch=์ˆ˜๋™ ์‹คํ–‰ ๋ฒ„ํŠผ +completion.workflow.event.workflow_run=์›Œํฌํ”Œ๋กœ ์‹คํ–‰์ด ๋ณ€๊ฒฝ๋จ +completion.workflow.eventFilter.types=ํ™œ๋™ ์œ ํ˜• ์ œํ•œ +completion.workflow.eventFilter.branches=์ด ์ง€์ ๋“ค๋งŒ +completion.workflow.eventFilter.branches-ignore=์ด ๋ถ„๊ธฐ๋ฅผ ๊ฑด๋„ˆ๋›ฐ์„ธ์š” +completion.workflow.eventFilter.tags=์ด ํƒœ๊ทธ๋งŒ +completion.workflow.eventFilter.tags-ignore=์ด ํƒœ๊ทธ ๊ฑด๋„ˆ๋›ฐ๊ธฐ +completion.workflow.eventFilter.paths=์ด ๊ฒฝ๋กœ๋“ค๋งŒ +completion.workflow.eventFilter.paths-ignore=์ด ๊ฒฝ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ +completion.workflow.eventFilter.workflows=์‹œ์ฒญํ•  ์›Œํฌํ”Œ๋กœ ์ด๋ฆ„ +completion.workflow.eventFilter.cron=ํฌ๋ก  ์ผ์ •. ์ž‘์€ ์‹œ๊ณ„ ์žฅ์น˜. +completion.workflow.permission.actions=์›Œํฌํ”Œ๋กœ ์‹คํ–‰ ๋ฐ ์ž‘์—… ์•„ํ‹ฐํŒฉํŠธ +completion.workflow.permission.artifact-metadata=์•„ํ‹ฐํŒฉํŠธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๋ ˆ์ฝ”๋“œ +completion.workflow.permission.attestations=์•„ํ‹ฐํŒฉํŠธ ์ฆ๋ช… +completion.workflow.permission.checks=์‹คํ–‰ ๋ฐ ์Šค์œ„ํŠธ ํ™•์ธ +completion.workflow.permission.code-quality=์ฝ”๋“œ ํ’ˆ์งˆ ๋ณด๊ณ ์„œ +completion.workflow.permission.contents=์ €์žฅ์†Œ ๋‚ด์šฉ +completion.workflow.permission.deployments=๋ฐฐํฌ +completion.workflow.permission.discussions=ํ† ๋ก  +completion.workflow.permission.id-token=OpenID Connect ํ† ํฐ +completion.workflow.permission.issues=๋ฌธ์ œ +completion.workflow.permission.models=GitHub ๋ชจ๋ธ +completion.workflow.permission.packages=GitHub ํŒจํ‚ค์ง€ +completion.workflow.permission.pages=GitHub ํŽ˜์ด์ง€ +completion.workflow.permission.pull-requests=ํ’€ ์š”์ฒญ +completion.workflow.permission.security-events=์ฝ”๋“œ ์Šค์บ๋‹ ๋ฐ ๋ณด์•ˆ ์ด๋ฒคํŠธ +completion.workflow.permission.statuses=์ปค๋ฐ‹ ์ƒํƒœ +completion.workflow.permission.vulnerability-alerts=Dependabot ๊ฒฝ๊ณ  +completion.workflow.permission.value.read=์ฝ๊ธฐ ์•ก์„ธ์Šค +completion.workflow.permission.value.write=์“ฐ๊ธฐ ์•ก์„ธ์Šค, ์ฝ๊ธฐ ํฌํ•จ +completion.workflow.permission.value.none=์ ‘๊ทผ ๋ถˆ๊ฐ€ +completion.workflow.permission.shorthand.read-all=๋ชจ๋“  ๊ถŒํ•œ์„ ์ฝ์—ˆ์Šต๋‹ˆ๋‹ค. ํฐ ๋‹ด์š”. +completion.workflow.permission.shorthand.write-all=๋ชจ๋“  ๊ถŒํ•œ ์“ฐ๊ธฐ. ํฐ ๋ง์น˜. +completion.workflow.permission.shorthand.empty=ํ† ํฐ ๊ถŒํ•œ ๋น„ํ™œ์„ฑํ™” +completion.workflow.job.name=์ž‘์—… ํ‘œ์‹œ ์ด๋ฆ„ +completion.workflow.job.permissions=์ž‘์—… ํ† ํฐ ๊ถŒํ•œ +completion.workflow.job.needs=๊ธฐ๋‹ค๋ ค์ง€๋Š” ์ง์—… +completion.workflow.job.if=์ง์—…์กฐ๊ฑด +completion.workflow.job.runs-on=๋Ÿฌ๋„ˆ ๋ ˆ์ด๋ธ” ๋˜๋Š” ๊ทธ๋ฃน +completion.workflow.job.snapshot=๋Ÿฌ๋„ˆ ์Šค๋ƒ…์ƒท +completion.workflow.job.environment=๋ฐฐํฌ ํ™˜๊ฒฝ +completion.workflow.job.concurrency=์ž‘์—… ๋™์‹œ์„ฑ ์ž ๊ธˆ +completion.workflow.job.outputs=๋‹ค๋ฅธ ์ž‘์—…์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์ถœ๋ ฅ +completion.workflow.job.env=์ž‘์—… ํ™˜๊ฒฝ ๋ณ€์ˆ˜ +completion.workflow.job.defaults=์ž‘์—… ๊ธฐ๋ณธ ์„ค์ • +completion.workflow.job.steps=๋‹จ๊ณ„ ๋ชฉ๋ก. ์‹ค์ œ ์ž‘์—…. +completion.workflow.job.timeout-minutes=์ž‘์—… ์‹œ๊ฐ„ ์ดˆ๊ณผ(๋ถ„) +completion.workflow.job.strategy=๋งคํŠธ๋ฆญ์Šค ๋ฐ ์Šค์ผ€์ค„๋ง ์ „๋žต +completion.workflow.job.continue-on-error=์ด ์ž‘์—…์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์‹คํŒจํ•˜๋„๋ก ํ•˜์„ธ์š”. +completion.workflow.job.container=์ด ์ž‘์—…์˜ ์ปจํ…Œ์ด๋„ˆ +completion.workflow.job.services=์‚ฌ์ด๋“œ์นด ์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ +completion.workflow.job.uses=ํ˜ธ์ถœ์— ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์›Œํฌํ”Œ๋กœ +completion.workflow.job.with=ํ˜ธ์ถœ๋œ ์›Œํฌํ”Œ๋กœ์— ๋Œ€ํ•œ ์ž…๋ ฅ +completion.workflow.job.secrets=ํ˜ธ์ถœ๋œ ์›Œํฌํ”Œ๋กœ์˜ ๋น„๋ฐ€ +completion.workflow.defaultsRun.shell=์‹คํ–‰ ๋‹จ๊ณ„์˜ ๊ธฐ๋ณธ ์…ธ +completion.workflow.defaultsRun.working-directory=๊ธฐ๋ณธ ์ž‘์—… ๋””๋ ‰ํ„ฐ๋ฆฌ +completion.workflow.concurrency.group=๋Œ€๊ธฐ ์ค‘์ธ ์‹คํ–‰์— ๋Œ€ํ•œ ์ž ๊ธˆ ์ด๋ฆ„ +completion.workflow.concurrency.cancel-in-progress=์ด์ „ ์ผ์น˜ ์‹คํ–‰ ์ทจ์†Œ +completion.workflow.environment.name=ํ™˜๊ฒฝ ์ด๋ฆ„ +completion.workflow.environment.url=ํ™˜๊ฒฝ URL +completion.workflow.strategy.matrix=ํ–‰๋ ฌ ์ถ• ๋ฐ ๋ณ€ํ˜• +completion.workflow.strategy.fail-fast=์‹คํŒจ ์‹œ ํ–‰๋ ฌ ํ˜•์ œ ์ทจ์†Œ +completion.workflow.strategy.max-parallel=๋งคํŠธ๋ฆญ์Šค ๋ณ‘๋ ฌ์„ฑ ์บก +completion.workflow.matrix.include=ํ–‰๋ ฌ ์กฐํ•ฉ ์ถ”๊ฐ€ +completion.workflow.matrix.exclude=๋งคํŠธ๋ฆญ์Šค ์กฐํ•ฉ ์ œ๊ฑฐ +completion.workflow.step.id=์ฐธ์กฐ์šฉ ๋‹จ๊ณ„ ID +completion.workflow.step.if=๋‹จ๊ณ„ ์กฐ๊ฑด +completion.workflow.step.name=๋‹จ๊ณ„ ํ‘œ์‹œ ์ด๋ฆ„ +completion.workflow.step.uses=์‹คํ–‰ํ•  ์ž‘์—… +completion.workflow.step.run=์‹คํ–‰ํ•  ์‰˜ ์Šคํฌ๋ฆฝํŠธ +completion.workflow.step.shell=์ด ์‹คํ–‰ ๋‹จ๊ณ„์˜ ์…ธ +completion.workflow.step.with=์ž‘์—… ์ž…๋ ฅ +completion.workflow.step.env=๋‹จ๊ณ„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ +completion.workflow.step.continue-on-error=์ด ๋‹จ๊ณ„๊ฐ€ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์‹คํŒจํ•˜๋„๋ก ํ•˜์„ธ์š”. +completion.workflow.step.timeout-minutes=๋‹จ๊ณ„ ์ œํ•œ ์‹œ๊ฐ„(๋ถ„) +completion.workflow.step.working-directory=๋‹จ๊ณ„ ์ž‘์—… ๋””๋ ‰ํ„ฐ๋ฆฌ +completion.workflow.container.image=์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ +completion.workflow.container.credentials=๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ž๊ฒฉ ์ฆ๋ช… +completion.workflow.container.env=์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ +completion.workflow.container.ports=๋…ธ์ถœํ•  ํฌํŠธ +completion.workflow.container.volumes=๋งˆ์šดํŠธํ•  ๋ณผ๋ฅจ +completion.workflow.container.options=Docker ์ƒ์„ฑ ์˜ต์…˜ +completion.workflow.service.image=์„œ๋น„์Šค ์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ +completion.workflow.service.credentials=๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ž๊ฒฉ ์ฆ๋ช… +completion.workflow.service.env=์„œ๋น„์Šค ํ™˜๊ฒฝ ๋ณ€์ˆ˜ +completion.workflow.service.ports=์„œ๋น„์Šค ํฌํŠธ +completion.workflow.service.volumes=์„œ๋น„์Šค๋Ÿ‰ +completion.workflow.service.options=Docker ์ƒ์„ฑ ์˜ต์…˜ +completion.workflow.credentials.username=๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์‚ฌ์šฉ์ž ์ด๋ฆ„ +completion.workflow.credentials.password=๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋˜๋Š” ํ† ํฐ +completion.workflow.inputType.string=ํ…์ŠคํŠธ ์ž…๋ ฅ +completion.workflow.inputType.boolean=์ฐธ ๋˜๋Š” ๊ฑฐ์ง“ ์ž…๋ ฅ +completion.workflow.inputType.choice=๋“œ๋กญ๋‹ค์šด ์„ ํƒ ์ž…๋ ฅ +completion.workflow.inputType.number=์ˆซ์ž์ž…๋ ฅ +completion.workflow.inputType.environment=ํ™˜๊ฒฝ ์„ ํƒ๊ธฐ ์ž…๋ ฅ +completion.workflow.boolean.true=๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์„ ์ผœ์‹ญ์‹œ์˜ค. +completion.workflow.boolean.false=์•„๋‹ˆ์š”. ์–ด๋‘ก๊ฒŒ ์œ ์ง€ํ•˜์„ธ์š”. +completion.workflow.runner.ubuntu-latest=์ตœ์‹  Ubuntu ๋Ÿฌ๋„ˆ +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 ๋Ÿฌ๋„ˆ +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 ๋Ÿฌ๋„ˆ +completion.workflow.runner.windows-latest=์ตœ์‹  Windows ๋Ÿฌ๋„ˆ +completion.workflow.runner.windows-2025=Windows ์„œ๋ฒ„ 2025 ๋Ÿฌ๋„ˆ +completion.workflow.runner.windows-2022=Windows ์„œ๋ฒ„ 2022 ๋Ÿฌ๋„ˆ +completion.workflow.runner.macos-latest=์ตœ์‹  macOS ๋Ÿฌ๋„ˆ +completion.workflow.runner.macos-15=macOS 15 ๋Ÿฌ๋„ˆ +completion.workflow.runner.macos-14=macOS 14 ๋Ÿฌ๋„ˆ +completion.workflow.runner.self-hosted=๋‚˜๋งŒ์˜ ์ฃผ์ž. ๋‹น์‹ ์˜ ์„œ์ปค์Šค. +completion.steps.outputs=๋‹จ๊ณ„์— ๋Œ€ํ•ด ์ •์˜๋œ ์ถœ๋ ฅ ์„ธํŠธ์ž…๋‹ˆ๋‹ค. +completion.steps.conclusion=์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๊ณ„์† ์ ์šฉ ํ›„ ์™„๋ฃŒ๋œ ๋‹จ๊ณ„์˜ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. +completion.steps.outcome=์˜ค๋ฅ˜ ์‹œ ๊ณ„์†์ด ์ ์šฉ๋˜๊ธฐ ์ „ ์™„๋ฃŒ๋œ ๋‹จ๊ณ„์˜ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. +completion.jobs.outputs=์ž‘์—…์— ๋Œ€ํ•ด ์ •์˜๋œ ์ถœ๋ ฅ ์„ธํŠธ์ž…๋‹ˆ๋‹ค. +completion.jobs.result=์ž‘์—…์˜ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. +settings.displayName=GitHub ์ž‘์—… ํ๋ฆ„ +settings.language.label=์–ธ์–ด: +settings.language.system=IDE/์‹œ์Šคํ…œ ๊ธฐ๋ณธ๊ฐ’ +settings.cache.title=์•ก์…˜ ์บ์‹œ +settings.cache.column.key=์บ์‹œ ํ‚ค +settings.cache.column.name=์ด๋ฆ„ +settings.cache.column.kind=์ข…๋ฅ˜ +settings.cache.column.state=์ƒํƒœ +settings.cache.column.expires=๋งŒ๋ฃŒ +settings.cache.kind.local=์ง€์—ญ +settings.cache.kind.remote=์›๊ฒฉ +settings.cache.state.resolved=ํ•ด๊ฒฐ๋จ +settings.cache.state.pending=๋ณด๋ฅ˜ ์ค‘ +settings.cache.state.expired=๋ถ€์‹คํ•œ +settings.cache.state.suppressed=์–ต์••๋œ +settings.cache.refresh=Refresh ํ…Œ์ด๋ธ” +settings.cache.deleteSelected=์„ ํƒ ํ•ญ๋ชฉ ์‚ญ์ œ +settings.cache.deleteAll=๋ชจ๋‘ ์‚ญ์ œ +settings.cache.export=์ˆ˜์ถœ +settings.cache.import=๊ฐ€์ ธ์˜ค๊ธฐ +settings.cache.summary=์บ์‹œ: {0} ํ•ญ๋ชฉ, {1} ํ™•์ธ, {2} ์›๊ฒฉ, {3} ์˜ค๋ž˜๋จ, {4} ์Œ์†Œ๊ฑฐ. ์บ์‹œ: {5} KB. +settings.cache.noneSelected=๋จผ์ € ์บ์‹œ ํ–‰์„ ์„ ํƒํ•˜์„ธ์š”. ๋น—์ž๋ฃจ๋Š” ์ถ”์ธก์„ ๊ฑฐ๋ถ€ํ•ฉ๋‹ˆ๋‹ค. +settings.cache.deleteSelected.done={0} ์บ์‹œ ํ•ญ๋ชฉ์„ ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž‘์€ ๋จผ์ง€ ๊ตฌ๋ฆ„์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. +settings.cache.deleteAll.confirm=๋ชจ๋“  GitHub ์›Œํฌํ”Œ๋กœ ์บ์‹œ ํ•ญ๋ชฉ์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? +settings.cache.deleteAll.done=๋ชจ๋“  ์บ์‹œ ํ•ญ๋ชฉ์„ ์‚ญ์ œํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์บ์‹œ๊ฐ€ ์˜์‹ฌ์Šค๋Ÿฌ์šธ ์ •๋„๋กœ ์กฐ์šฉํ•ด์กŒ์Šต๋‹ˆ๋‹ค. +settings.cache.export.done={0} ์บ์‹œ ํ•ญ๋ชฉ์„ ๋‚ด๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์— ๊ฐ‡ํžŒ ์ž‘์€ ๊ธฐ๋ก ๋ณด๊ด€์†Œ. +settings.cache.import.done=๊ฐ€์ ธ์˜จ ์บ์‹œ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค. ์•„์นด์ด๋ธŒ ์ง์Šน์ด ํ–‰๋™ํ–ˆ์Šต๋‹ˆ๋‹ค. +settings.cache.import.unsupported=์ง€์›๋˜์ง€ ์•Š๋Š” GitHub ์›Œํฌํ”Œ๋กœ ์บ์‹œ ํŒŒ์ผ์ž…๋‹ˆ๋‹ค. +settings.cache.import.brokenLine=GitHub ์›Œํฌํ”Œ๋กœ ์บ์‹œ ๋ผ์ธ์ด ์†์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +settings.cache.import.brokenKey=GitHub ์›Œํฌํ”Œ๋กœ ์บ์‹œ ํ‚ค๊ฐ€ ์†์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +settings.support.button=์ด ํ”Œ๋Ÿฌ๊ทธ์ธ ์ง€์› +settings.support.tooltip=์ง€์› ํŽ˜์ด์ง€ ์—ด๊ธฐ +settings.support.line.0=๋นŒ๋“œ ํผ๋‹ˆ์Šค์— ๊ณต๊ธ‰ +settings.support.line.1=ํŒŒ์„œ ์ปคํ”ผ๋ฅผ ์‚ฌ์„ธ์š” +settings.support.line.2=์œ ๋ น์ด ๋‚˜์˜ค๋Š” ์ž‘์—… ํ๋ฆ„์„ ๋œ ํ›„์›ํ•ฉ๋‹ˆ๋‹ค. +workflow.run.jobs.title=์›Œํฌํ”Œ๋กœ ์ž‘์—… +workflow.run.jobs.root=์›Œํฌํ”Œ๋กœ ์‹คํ–‰ +workflow.run.jobs.description=GitHub ์ž‘์—… ์ž‘์—… ํŠธ๋ฆฌ ๋ฐ ์„ ํƒํ•œ ์ž‘์—… ๋กœ๊ทธ +workflow.run.tree.done=์™„๋ฃŒ +workflow.run.tree.failed=์‹คํŒจํ–ˆ๋‹ค +workflow.run.tree.skipped=๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค +workflow.run.tree.warn=๊ฒฝ๊ณ ํ•˜๋‹ค +workflow.run.tree.err=์‹ค์ˆ˜ +workflow.run.delete.tooltip=์‹คํ–‰ ์‚ญ์ œ +workflow.run.delete.noRun=์•„์ง ์‹คํ–‰ ID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. +workflow.run.delete.requested=์‹คํ–‰ {0}๋ฅผ ์‚ญ์ œํ•˜๋Š” ์ค‘์ž…๋‹ˆ๋‹ค. +workflow.run.delete.done={0} ์‹คํ–‰์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. +workflow.run.delete.http=HTTP {0}๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ๋ˆ. +workflow.run.delete.failed=์–ด์ง€๋Ÿฌ์šด ์‚ญ์ œ: {0} +workflow.run.rerun.all.tooltip=์›Œํฌํ”Œ๋กœ ๋‹ค์‹œ ์‹คํ–‰ +workflow.run.rerun.failed.tooltip=์‹คํŒจํ•œ ์ž‘์—… ๋‹ค์‹œ ์‹คํ–‰ +workflow.run.rerun.noRun=์•„์ง ์‹คํ–‰ ID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. +workflow.run.rerun.all.requested=์žฌ์‹คํ–‰ ์š”์ฒญ๋จ: {0}. +workflow.run.rerun.failed.requested=์š”์ฒญํ•œ ์‹คํŒจํ•œ ์ž‘์—…: {0}. +workflow.run.rerun.all.done=๋Œ€๊ธฐ ์ค‘์ธ ์žฌ์‹คํ–‰: {0}. +workflow.run.rerun.failed.done=๋Œ€๊ธฐ ์ค‘์ธ ์‹คํŒจํ•œ ์ž‘์—…: {0}. +workflow.run.rerun.http=HTTP {0}๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋ˆ. +workflow.run.rerun.failed=์–ด์ง€๋Ÿฌ์šด ์žฌ์‹คํ–‰: {0} +workflow.run.download.log.tooltip=์ž‘์—… ๋กœ๊ทธ ์ €์žฅ +workflow.run.download.artifacts.tooltip=์•„ํ‹ฐํŒฉํŠธ ๋‹ค์šด๋กœ๋“œ +workflow.run.download.noRun=์•„์ง ์‹คํ–‰ ID๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. +workflow.run.download.log.requested={0}์— ๋Œ€ํ•œ ๋กœ๊ทธ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค. +workflow.run.download.log.done=์ €์žฅ๋œ ๋กœ๊ทธ: {0}. +workflow.run.download.artifacts.requested=์•„ํ‹ฐํŒฉํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค. +workflow.run.download.artifacts.empty=์œ ๋ฌผ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ž‘์€ ๊ณตํ—ˆ. +workflow.run.download.artifact.expired=์•„ํ‹ฐํŒฉํŠธ ๋งŒ๋ฃŒ๋จ: {0}. +workflow.run.download.artifact.done=์•„ํ‹ฐํŒฉํŠธ ์ €์žฅ๋จ: {0} -> {1}. +workflow.run.download.failed=์–ด๋ฆฌ๋‘ฅ์ ˆํ•œ ๋‹ค์šด๋กœ๋“œ: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_nl.properties b/src/main/resources/messages/GitHubWorkflowBundle_nl.properties new file mode 100644 index 0000000..cf84812 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_nl.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub-workflow +plugin.description=Ondersteuning voor GitHub Actions-workflowbestanden +group.GitHubWorkflow.Tools.text=GitHub-workflow +group.GitHubWorkflow.Tools.description=GitHub Workflow-plug-intools +action.GitHubWorkflow.RefreshActionCache.text=Refresh actiecache +action.GitHubWorkflow.RefreshActionCache.description=Refresh heeft externe GitHub-acties en herbruikbare workflow-metagegevens opgelost +action.GitHubWorkflow.RestoreActionWarnings.text=Waarschuwingen voor herstelacties +action.GitHubWorkflow.RestoreActionWarnings.description=Herstel onderdrukte actie-, invoer- en uitvoervalidatiewaarschuwingen +action.GitHubWorkflow.ClearActionCache.text=Wis actiecache +action.GitHubWorkflow.ClearActionCache.description=Wis in de cache opgeslagen GitHub-acties en herbruikbare workflow-metagegevens +notification.cache.cleared={0}-gecachte GitHub-workflowgegevens gewist. +notification.cache.refresh.started=Refreshing {0} heeft externe GitHub-workflowgegevens in de cache opgeslagen. +notification.warnings.restored=Waarschuwingen voor {0} GitHub Workflow-items hersteld. +workflow.run.configuration.display=GitHub-workflow +workflow.run.configuration.description=Verzend en volg de workflow-uitvoeringen van GitHub-acties +workflow.run.configuration.name=GitHub-workflow: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=Eigenaar +workflow.run.field.repo=Bewaarplaats +workflow.run.field.workflow=Workflow-bestand +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Token env var terugval +workflow.run.inputs.title=workflow_dispatch-ingangen (sleutel=waarde) +workflow.run.error.apiUrl=GitHub API URL is vereist. +workflow.run.error.repository=Eigenaar en naam van de GitHub-repository zijn vereist. +workflow.run.error.workflow=Workflowbestand is vereist. +workflow.run.error.ref=Tak- of tagreferentie is vereist. +workflow.run.error.inputs=GitHub workflow_dispatch ondersteunt maximaal 25 ingangen. +workflow.run.gutter.stop=Stop de werkstroomuitvoering +workflow.run.gutter.stop.text=Stop de werkstroomuitvoering +workflow.run.gutter.stop.description=Annuleer deze uitvoering +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=rennen: +workflow.log.warning=waarschuwing: +workflow.log.error=fout: +workflow.run.cancel.requested=Annuleren aangevraagd: {0}. +workflow.run.stop.before.id=Stop aangevraagd. Nog geen run-ID. +workflow.run.cancel.http=Annuleer HTTP {0}. Oef. +workflow.run.cancel.failed=Annuleren mislukt: {0} +workflow.run.interrupted=Onderbroken. +workflow.run.link=Voer uit: {0} +workflow.run.discovery=Uitvoering geaccepteerd. Jachtrun-id. +workflow.run.discovery.none=Nog geen run-ID. Het tabblad Acties weet meer. +workflow.run.status=Status: {0}{1} +workflow.run.job.main=Taak: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Status: {0} {1}{2}{3} +workflow.run.logs.later=Logboeken zullen verschijnen wanneer GitHub ze publiceert. +workflow.run.job.logs.later=Taak {0}: {1} +workflow.run.log.failed=Downloaden van logboek mislukt: {0} +workflow.run.log.failed.job=Logboekdownload mislukt voor {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Taak: {0} +workflow.run.job.fallbackName=Taak {0} +workflow.run.overview=Workflow uitgevoerd {0} {1}/{2} voltooid, {3} actief +workflow.run.state.ok=[OK] +workflow.run.state.fail=[mislukt] +workflow.run.state.running=[RENNEN] +workflow.run.state.waiting=[WACHT] +workflow.run.dispatch.verbs=Primen|Wachtrij|Oproepen|Lanceren|Opstarten +workflow.run.dispatch.objects=workflow|automatisering|pijplijn|uitvoeren +workflow.run.dispatch={0} {1} {2}{3} voor {4} op {5}. +workflow.run.notification.auth=Voor het verzenden van GitHub-workflows is een geverifieerd GitHub-account vereist. Accounts toevoegen of vernieuwen in {0}. +workflow.run.notification.openSettings=Open GitHub-instellingen +workflow.cache.progress.title=GitHub-acties oplossen +workflow.cache.progress.text={0} {1} oplossen +workflow.cache.kind.action=actie +workflow.cache.kind.workflow=werkstroom +inspection.parameter.input=invoer +inspection.parameter.secret=geheim +inspection.action.delete.invalid=Verwijder ongeldige {0} [{1}] +inspection.action.update.major=Actie [{0}] bijwerken naar [{1}] +inspection.warning.toggle=Schakel waarschuwingen [{0}] in voor [{1}] +inspection.warning.on=op +inspection.warning.off=uit +inspection.statement.incomplete=Onvolledige verklaring [{0}] +inspection.invalid.suffix.remove=Ongeldig achtervoegsel [{0}] verwijderen +inspection.replace.with=Vervangen door [{0}] +inspection.invalid.remove=Ongeldige [{0}] verwijderen +inspection.workflow.syntax.unknownTopLevelKey=Onbekende workflowsleutel [{0}] +inspection.workflow.syntax.unknownEventKey=Onbekende workflowgebeurtenis [{0}] +inspection.workflow.syntax.unknownTriggerKey=Onbekende triggersleutel [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Onbekend triggerfilter [{0}] +inspection.workflow.syntax.unknownTriggerValue=Onbekende triggerwaarde [{0}] +inspection.workflow.syntax.unknownPermission=Onbekende toestemming [{0}] +inspection.workflow.syntax.unknownPermissionValue=Onbekende machtigingswaarde [{0}] +inspection.workflow.syntax.unknownJobKey=Onbekende taaksleutel [{0}] +inspection.workflow.syntax.unknownStepKey=Onbekende stapsleutel [{0}] +inspection.action.reload=Herladen [{0}] +inspection.action.unresolved=Onopgelost [{0}] - controleer GitHub-accounttoegang, machtigingen voor privรฉrepository, snelheidslimieten, ontbrekende referenties of ontbrekende metadata van acties/workflows +inspection.action.jump=Ga naar bestand [{0}] +inspection.output.unused=Ongebruikt [{0}] +inspection.secret.invalid.if=Verwijder [{0}] - Geheimen zijn niet geldig in `if`-instructies +inspection.secret.replace.runtime=Vervang [{0}] door [{1}] - als dit niet beschikbaar is tijdens runtime +inspection.needs.invalid.job=Verwijder ongeldige jobId [{0}] - deze jobId komt niet overeen met een eerdere vacature +documentation.description=Beschrijving: {0} +documentation.type=Soort: {0} +documentation.required=Vereist: {0} +documentation.default=Standaard: {0} +documentation.deprecated=Verouderd: {0} +documentation.open.declaration=Open aangifte ({0}) +documentation.input.label=Invoer +documentation.secret.label=Geheim +documentation.env.label=Omgevingsvariabele +documentation.matrix.label=Matrix-eigenschap +documentation.need.label=Benodigde baan +documentation.need.description=Directe afhankelijkheid van werk. +documentation.needOutput.label=Benodigde taakuitvoer +documentation.reusableJob.label=Herbruikbare workflowtaak +documentation.reusableJob.description=Taak gedeclareerd in deze herbruikbare workflow. +documentation.reusableJobOutput.label=Herbruikbare workflow-taakuitvoer +documentation.service.label=Servicecontainer +documentation.servicePort.label=Servicepoort +documentation.container.label=Baancontainer +documentation.symbol.label=Workflow-symbool +documentation.symbol.description=Opgeloste workflow-expressie. +documentation.workflowOutput.label=Workflow-uitvoer +documentation.jobOutput.label=Taakuitvoer +documentation.action.label=Actie +documentation.externalAction.label=Externe actie +documentation.reusableWorkflow.label=Herbruikbare werkstroom +documentation.resolvedFrom=opgelost vanuit {0} +documentation.notResolved=nog niet opgelost +documentation.inputs.title=Ingangen +documentation.outputs.title=Uitgangen +documentation.secrets.title=Geheimen +documentation.value.label=Waarde +documentation.step.title=Stap {0} +documentation.name.label=Naam +documentation.uses.label=Gebruik +documentation.run.label=Rennen +documentation.description.label=Beschrijving +documentation.step.label=Stap +documentation.source.label=Bron +documentation.stepOutput.label=Stap-uitvoer +documentation.context.github=github-context +documentation.context.github.description=Informatie over de huidige werkstroomuitvoering en gebeurtenis. +documentation.context.gitea=Gitea context +documentation.context.gitea.description=Gitea-compatibele alias voor de GitHub Actions-context. +documentation.context.inputs=context invoert +documentation.context.inputs.description=Workflow-, verzendings- of actie-invoer is hier beschikbaar. +documentation.context.secrets=geheimen context +documentation.context.secrets.description=Geheime waarden die beschikbaar zijn voor deze werkstroom of herbruikbare werkstroomaanroep. +documentation.context.env=env-context +documentation.context.env.description=Omgevingsvariabelen zichtbaar op deze locatie. +documentation.context.matrix=matrixcontext +documentation.context.matrix.description=Matrixwaarden voor de huidige taak. +documentation.context.steps=stappencontext +documentation.context.steps.description=Vorige stappen in de huidige taak, inclusief uitvoer en status. +documentation.context.needs=heeft context nodig +documentation.context.needs.description=Directe taakafhankelijkheden en hun output/resultaten. +documentation.context.jobs=banencontext +documentation.context.jobs.description=Herbruikbare workflowtaken en -uitvoer. +documentation.context.outputs=uitgangen +documentation.context.outputs.description=Uitvoerwaarden die door deze stap of taak worden weergegeven. +documentation.context.result=resultaat +documentation.context.result.description=Taakresultaat: succes, mislukking, geannuleerd of overgeslagen. +documentation.context.outcome=uitkomst +documentation.context.outcome.description=Stapresultaat voordat doorgaan bij fout wordt toegepast. +documentation.context.conclusion=conclusie +documentation.context.conclusion.description=Stapresultaat nadat doorgaan bij fout is toegepast. +error.report.action=Rapportuitzondering +error.report.description=Beschrijving +error.report.steps=Stappen om te reproduceren +error.report.sample=Geef indien van toepassing een codevoorbeeld op +error.report.message=Bericht +error.report.runtime=Runtime-informatie +error.report.pluginVersion=Plug-inversie: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=Stapeltrace +completion.shell.bash=Bash-shell. Gebruikt bash op Linux- en macOS-hardlopers, en Git voor Windows bash op Windows-hardlopers. +completion.shell.sh=POSIX shell-terugval. +completion.shell.pwsh=PowerShell-kern. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Windows-opdrachtprompt. +completion.shell.python=Python-opdrachtloper. +completion.runner.name=De naam van de hardloper die de taak uitvoert. +completion.runner.os=Het besturingssysteem van de hardloper die de taak uitvoert. Mogelijke waarden zijn Linux, Windows of macOS. +completion.runner.arch=De architectuur van de hardloper die de taak uitvoert. Mogelijke waarden zijn X86, X64, ARM of ARM64. +completion.runner.temp=Het pad naar een tijdelijke map op de runner. Deze map wordt aan het begin en einde van elke taak geleegd. Houd er rekening mee dat bestanden niet worden verwijderd als het gebruikersaccount van de hardloper geen toestemming heeft om ze te verwijderen. +completion.runner.toolCache=Het pad naar de map met vooraf geรฏnstalleerde hulpprogramma''s voor door GitHub gehoste hardlopers. +completion.runner.debug=Dit wordt alleen ingesteld als logboekregistratie voor foutopsporing is ingeschakeld en heeft altijd de waarde 1. +completion.runner.environment=De omgeving van de hardloper die de taak uitvoert. Mogelijke waarden zijn door github gehost of door uzelf gehost. +completion.job.status=De huidige status van de taak. +completion.job.checkRunId=De controlerun-ID van de huidige taak. +completion.job.container=Informatie over de container van de taak. +completion.job.services=De servicecontainers die voor een taak zijn gemaakt. +completion.job.workflowRef=De volledige referentie van het workflowbestand dat de huidige taak definieert. +completion.job.workflowSha=De commit SHA van het workflowbestand dat de huidige taak definieert. +completion.job.workflowRepository=De eigenaar/repository van de repository die het workflowbestand bevat dat de huidige taak definieert. +completion.job.workflowFilePath=Het werkstroombestandspad, relatief ten opzichte van de hoofdmap van de repository. +completion.job.containerField=Veld voor taakcontainer +completion.job.service=Baandienst +completion.job.serviceField=Vacatureserviceveld +completion.job.mappedServicePort=In kaart gebrachte servicepoort +completion.strategy.failFast=Of alle lopende taken worden geannuleerd als een matrixtaak mislukt. +completion.strategy.jobIndex=De op nul gebaseerde index van de huidige functie in de matrix. +completion.strategy.jobTotal=Het totale aantal banen in de matrix. +completion.strategy.maxParallel=Het maximale aantal matrixtaken dat gelijktijdig kan worden uitgevoerd. +completion.context.inputs=Workflow-invoer, zoals workflow_dispatch of workflow_call. +completion.context.secrets=Werkstroomgeheimen. +completion.context.job=Informatie over de momenteel actieve taak. +completion.context.jobs=Workflow-taken. +completion.context.matrix=Matrixeigenschappen gedefinieerd voor de huidige matrixtaak. +completion.context.strategy=Informatie over de matrixuitvoeringsstrategie voor de huidige taak. +completion.context.steps=Stappen met een id in de huidige taak. +completion.context.env=Omgevingsvariabelen uit taken en stappen. +completion.context.vars=Aangepaste configuratievariabelen uit organisatie-, repository- en omgevingsbereiken. +completion.context.needs=Taken die moeten worden voltooid voordat deze taak kan worden uitgevoerd, plus hun uitvoer en resultaten. +completion.context.github=Workflowuitvoering en gebeurtenisinformatie uit de GitHub-context. +completion.context.gitea=Gitea-compatibele alias voor de GitHub Actions-context. +completion.context.runner=Informatie over de hardloper die de huidige taak uitvoert. +completion.secret.githubToken=Automatisch aangemaakt token voor elke workflowuitvoering. +completion.remote.repository=Externe opslagplaats +completion.uses.local.workflow=Lokale herbruikbare workflow +completion.uses.local.action=Lokale actie +completion.uses.ref.known=Bekende workflowreferentie +completion.uses.ref.remote=Referentie voor externe workflows +completion.uses.remote.known=Bekende actie op afstand of herbruikbare workflow +completion.workflow.syntax=GitHub Syntaxis van de werkstroom voor acties +completion.workflow.top.name=Weergavenaam van de werkstroom +completion.workflow.top.run-name=Dynamische runnaam +completion.workflow.top.on=Gebeurtenissen die de werkstroom starten +completion.workflow.top.permissions=Standaard GITHUB_TOKEN-machtigingen +completion.workflow.top.env=Omgevingsvariabelen voor de hele workflow +completion.workflow.top.defaults=Standaard taak- en stapinstellingen +completion.workflow.top.concurrency=Gelijktijdigheidsgroep en annulering +completion.workflow.top.jobs=Taken die in deze workflow worden uitgevoerd +completion.workflow.event.branch_protection_rule=Branchebescherming gewijzigd +completion.workflow.event.check_run=Enkele controlerun gewijzigd +completion.workflow.event.check_suite=Controleer suite gewijzigd +completion.workflow.event.create=Tak of tag gemaakt +completion.workflow.event.delete=Tak of tag verwijderd +completion.workflow.event.deployment=Implementatie gemaakt +completion.workflow.event.deployment_status=Implementatiestatus gewijzigd +completion.workflow.event.discussion=Discussie veranderd +completion.workflow.event.discussion_comment=Discussiecommentaar gewijzigd +completion.workflow.event.fork=Repository gevorkt +completion.workflow.event.gollum=Wiki-pagina gewijzigd +completion.workflow.event.image_version=Versie van pakketimage gewijzigd +completion.workflow.event.issue_comment=Probleem of PR-opmerking gewijzigd +completion.workflow.event.issues=Probleem gewijzigd +completion.workflow.event.label=Etiket gewijzigd +completion.workflow.event.merge_group=Controle van samenvoegwachtrij aangevraagd +completion.workflow.event.milestone=Mijlpaal veranderd +completion.workflow.event.page_build=Het bouwen van pagina''s is uitgevoerd +completion.workflow.event.project=Klassiek project gewijzigd +completion.workflow.event.project_card=Klassieke projectkaart gewijzigd +completion.workflow.event.project_column=Klassieke projectkolom gewijzigd +completion.workflow.event.public=Repository werd openbaar +completion.workflow.event.pull_request=Pull-verzoek gewijzigd +completion.workflow.event.pull_request_review=PR-recensie gewijzigd +completion.workflow.event.pull_request_review_comment=PR recensiecommentaar gewijzigd +completion.workflow.event.pull_request_target=PR doelcontext. Scherpe messen. +completion.workflow.event.push=Commit of tag gepusht +completion.workflow.event.registry_package=Pakket gepubliceerd of bijgewerkt +completion.workflow.event.release=Vrijgave gewijzigd +completion.workflow.event.repository_dispatch=Aangepaste API-gebeurtenis +completion.workflow.event.schedule=Cron-tik. Uurwerk. +completion.workflow.event.status=Commitstatus gewijzigd +completion.workflow.event.watch=Repository met ster +completion.workflow.event.workflow_call=Herbruikbare workflow-oproep +completion.workflow.event.workflow_dispatch=Handmatige run-knop +completion.workflow.event.workflow_run=Workflowuitvoering gewijzigd +completion.workflow.eventFilter.types=Beperk activiteitstypen +completion.workflow.eventFilter.branches=Alleen deze takken +completion.workflow.eventFilter.branches-ignore=Sla deze takken over +completion.workflow.eventFilter.tags=Alleen deze labels +completion.workflow.eventFilter.tags-ignore=Sla deze tags over +completion.workflow.eventFilter.paths=Alleen deze paden +completion.workflow.eventFilter.paths-ignore=Sla deze paden over +completion.workflow.eventFilter.workflows=Workflownamen om in de gaten te houden +completion.workflow.eventFilter.cron=Cron-schema. Klein uurwerk. +completion.workflow.permission.actions=Workflowuitvoeringen en actieartefacten +completion.workflow.permission.artifact-metadata=Metagegevensrecords voor artefacten +completion.workflow.permission.attestations=Artefactattesten +completion.workflow.permission.checks=Bekijk runs en suites +completion.workflow.permission.code-quality=Codekwaliteitsrapporten +completion.workflow.permission.contents=Inhoud van de opslagplaats +completion.workflow.permission.deployments=Implementaties +completion.workflow.permission.discussions=Discussies +completion.workflow.permission.id-token=OpenID Connect-tokens +completion.workflow.permission.issues=Problemen +completion.workflow.permission.models=GitHub-modellen +completion.workflow.permission.packages=GitHub-pakketten +completion.workflow.permission.pages=GitHub-pagina''s +completion.workflow.permission.pull-requests=Pull-verzoeken +completion.workflow.permission.security-events=Codescanning en beveiligingsgebeurtenissen +completion.workflow.permission.statuses=Commit-statussen +completion.workflow.permission.vulnerability-alerts=Dependabot-waarschuwingen +completion.workflow.permission.value.read=Leestoegang +completion.workflow.permission.value.write=Schrijftoegang, inclusief lezen +completion.workflow.permission.value.none=Geen toegang +completion.workflow.permission.shorthand.read-all=Alle rechten gelezen. Grote deken. +completion.workflow.permission.shorthand.write-all=Alle machtigingen schrijven. Grote hamer. +completion.workflow.permission.shorthand.empty=Schakel tokenrechten uit +completion.workflow.job.name=Weergavenaam taak +completion.workflow.job.permissions=Machtigingen voor taaktokens +completion.workflow.job.needs=Banen om op te wachten +completion.workflow.job.if=Conditie van de baan +completion.workflow.job.runs-on=Runnerlabel of groep +completion.workflow.job.snapshot=Een momentopname van de loper +completion.workflow.job.environment=Implementatieomgeving +completion.workflow.job.concurrency=Gelijktijdigheidsvergrendeling van taken +completion.workflow.job.outputs=Uitgangen die andere taken kunnen lezen +completion.workflow.job.env=Variabelen in de werkomgeving +completion.workflow.job.defaults=Standaardinstellingen voor taak +completion.workflow.job.steps=Stappenlijst. Het eigenlijke werk. +completion.workflow.job.timeout-minutes=Taaktime-out in minuten +completion.workflow.job.strategy=Matrix- en planningsstrategie +completion.workflow.job.continue-on-error=Laat deze klus zachtjes mislukken +completion.workflow.job.container=Container voor deze klus +completion.workflow.job.services=Zijspan servicecontainers +completion.workflow.job.uses=Herbruikbare workflow om te bellen +completion.workflow.job.with=Ingangen voor de opgeroepen workflow +completion.workflow.job.secrets=Geheimen voor de opgeroepen workflow +completion.workflow.defaultsRun.shell=Standaardshell voor uitvoeringsstappen +completion.workflow.defaultsRun.working-directory=Standaard werkmap +completion.workflow.concurrency.group=Naam van vergrendeling voor uitvoeringen in de wachtrij +completion.workflow.concurrency.cancel-in-progress=Annuleer oudere matching runs +completion.workflow.environment.name=Naam van de omgeving +completion.workflow.environment.url=Omgeving URL +completion.workflow.strategy.matrix=Matrixassen en varianten +completion.workflow.strategy.fail-fast=Annuleer matrix-broers en zussen bij mislukking +completion.workflow.strategy.max-parallel=Matrix-parallellismekap +completion.workflow.matrix.include=Voeg matrixcombinaties toe +completion.workflow.matrix.exclude=Matrixcombinaties verwijderen +completion.workflow.step.id=Stap-ID voor referenties +completion.workflow.step.if=Stap voorwaarde +completion.workflow.step.name=Weergavenaam van stap +completion.workflow.step.uses=Actie om uit te voeren +completion.workflow.step.run=Shell-script om uit te voeren +completion.workflow.step.shell=Shell voor deze runstap +completion.workflow.step.with=Actie-ingangen +completion.workflow.step.env=Stap omgevingsvariabelen +completion.workflow.step.continue-on-error=Laat deze stap zachtjes mislukken +completion.workflow.step.timeout-minutes=Time-out van stap in minuten +completion.workflow.step.working-directory=Stap werkmap +completion.workflow.container.image=Containerafbeelding +completion.workflow.container.credentials=Registerreferenties +completion.workflow.container.env=Omgevingsvariabelen voor containers +completion.workflow.container.ports=Poorten om bloot te leggen +completion.workflow.container.volumes=Volumes om te monteren +completion.workflow.container.options=Docker opties maken +completion.workflow.service.image=Servicecontainerimage +completion.workflow.service.credentials=Registerreferenties +completion.workflow.service.env=Variabelen van de serviceomgeving +completion.workflow.service.ports=Servicepoorten +completion.workflow.service.volumes=Servicevolumes +completion.workflow.service.options=Docker opties maken +completion.workflow.credentials.username=Register-gebruikersnaam +completion.workflow.credentials.password=Registerwachtwoord of token +completion.workflow.inputType.string=Tekstinvoer +completion.workflow.inputType.boolean=Waar of onwaar invoer +completion.workflow.inputType.choice=Keuze-invoer via vervolgkeuzelijst +completion.workflow.inputType.number=Nummerinvoer +completion.workflow.inputType.environment=Invoer van omgevingskiezer +completion.workflow.boolean.true=Ja. Draai het aan. +completion.workflow.boolean.false=Nee. Houd het donker. +completion.workflow.runner.ubuntu-latest=Nieuwste Ubuntu-hardloper +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 hardloper +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 hardloper +completion.workflow.runner.windows-latest=Nieuwste Windows-hardloper +completion.workflow.runner.windows-2025=Windows Server 2025-loper +completion.workflow.runner.windows-2022=Windows Server 2022-loper +completion.workflow.runner.macos-latest=Nieuwste macOS-hardloper +completion.workflow.runner.macos-15=macOS 15 loper +completion.workflow.runner.macos-14=macOS 14 loper +completion.workflow.runner.self-hosted=Je eigen loper. Jouw circus. +completion.steps.outputs=De set uitvoer die voor de stap is gedefinieerd. +completion.steps.conclusion=Het resultaat van een voltooide stap na doorgaan bij fout wordt toegepast. +completion.steps.outcome=Het resultaat van een voltooide stap vรณรณr doorgaan bij fout wordt toegepast. +completion.jobs.outputs=De set uitvoer die voor de taak is gedefinieerd. +completion.jobs.result=Het resultaat van de klus. +settings.displayName=GitHub-workflow +settings.language.label=Taal: +settings.language.system=IDE/systeemstandaard +settings.cache.title=Actiecache +settings.cache.column.key=Cachesleutel +settings.cache.column.name=Naam +settings.cache.column.kind=Soort +settings.cache.column.state=Staat +settings.cache.column.expires=Verloopt +settings.cache.kind.local=lokaal +settings.cache.kind.remote=afgelegen +settings.cache.state.resolved=opgelost +settings.cache.state.pending=in afwachting +settings.cache.state.expired=muf +settings.cache.state.suppressed=onderdrukt +settings.cache.refresh=Refresh-tabel +settings.cache.deleteSelected=Geselecteerde verwijderen +settings.cache.deleteAll=Alles verwijderen +settings.cache.export=Exporteren +settings.cache.import=Importeren +settings.cache.summary=Cache: {0}-vermeldingen, {1} opgelost, {2} op afstand, {3} verouderd, {4} gedempt. Cache: {5} KB. +settings.cache.noneSelected=Selecteer eerst cacherijen. De bezem weigert giswerk. +settings.cache.deleteSelected.done={0}-cachegegevens verwijderd. Kleine stofwolk bevatte. +settings.cache.deleteAll.confirm=Alle GitHub Workflow cache-items verwijderen? +settings.cache.deleteAll.done=Alle cachegegevens verwijderd. De cache is nu verdacht stil. +settings.cache.export.done=Geรซxporteerde {0}-cachegegevens. Klein archiefbeest gekooid. +settings.cache.import.done=Geรฏmporteerde cachegegevens. Het archiefbeest gedroeg zich. +settings.cache.import.unsupported=Niet-ondersteund GitHub Workflow-cachebestand. +settings.cache.import.brokenLine=Kapotte GitHub Workflow-cacheregel. +settings.cache.import.brokenKey=Kapotte GitHub Workflow-cachesleutel. +settings.support.button=Ondersteun deze plug-in +settings.support.tooltip=Open de ondersteuningspagina +settings.support.line.0=Voed de bouwoven +settings.support.line.1=Koop de parserkoffie +settings.support.line.2=Sponsor minder spookachtige workflows +workflow.run.jobs.title=Workflow-taken +workflow.run.jobs.root=Workflow uitgevoerd +workflow.run.jobs.description=GitHub Acties taakboom en geselecteerd taaklogboek +workflow.run.tree.done=gedaan +workflow.run.tree.failed=mislukt +workflow.run.tree.skipped=overgeslagen +workflow.run.tree.warn=waarschuwen +workflow.run.tree.err=fout +workflow.run.delete.tooltip=Uitvoering verwijderen +workflow.run.delete.noRun=Nog geen run-ID. +workflow.run.delete.requested=Run {0} wordt verwijderd. +workflow.run.delete.done=Voer {0} uit verwijderd. +workflow.run.delete.http=Verwijder HTTP {0}. Oef. +workflow.run.delete.failed=Verwijder fizzled: {0} +workflow.run.rerun.all.tooltip=Voer de werkstroom opnieuw uit +workflow.run.rerun.failed.tooltip=Voer mislukte taken opnieuw uit +workflow.run.rerun.noRun=Nog geen run-ID. +workflow.run.rerun.all.requested=Opnieuw uitvoeren aangevraagd: {0}. +workflow.run.rerun.failed.requested=Mislukte taken aangevraagd: {0}. +workflow.run.rerun.all.done=Opnieuw uitvoeren in wachtrij: {0}. +workflow.run.rerun.failed.done=Mislukte taken in de wachtrij: {0}. +workflow.run.rerun.http=Voer HTTP {0} opnieuw uit. Oef. +workflow.run.rerun.failed=Herhaling mislukt: {0} +workflow.run.download.log.tooltip=Taaklogboek opslaan +workflow.run.download.artifacts.tooltip=Artefacten downloaden +workflow.run.download.noRun=Nog geen run-ID. +workflow.run.download.log.requested=Logboek ophalen voor {0}. +workflow.run.download.log.done=Log opgeslagen: {0}. +workflow.run.download.artifacts.requested=Artefacten ophalen. +workflow.run.download.artifacts.empty=Geen artefacten. Kleine leegte. +workflow.run.download.artifact.expired=Artefact verlopen: {0}. +workflow.run.download.artifact.done=Artefact opgeslagen: {0} -> {1}. +workflow.run.download.failed=Download bruisend: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_pl.properties b/src/main/resources/messages/GitHubWorkflowBundle_pl.properties new file mode 100644 index 0000000..d3d27a4 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_pl.properties @@ -0,0 +1,442 @@ +plugin.name=Przebieg pracy GitHub +plugin.description=Obsล‚uga plikรณw przepล‚ywu pracy akcji GitHub +group.GitHubWorkflow.Tools.text=Przebieg pracy GitHub +group.GitHubWorkflow.Tools.description=Narzฤ™dzia wtyczki GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Pamiฤ™ฤ‡ podrฤ™czna akcji Refresh +action.GitHubWorkflow.RefreshActionCache.description=Refresh rozwiฤ…zaล‚ zdalne akcje GitHub i metadane przepล‚ywu pracy do ponownego wykorzystania +action.GitHubWorkflow.RestoreActionWarnings.text=Przywrรณฤ‡ ostrzeลผenia dotyczฤ…ce akcji +action.GitHubWorkflow.RestoreActionWarnings.description=Przywrรณฤ‡ pominiฤ™te dziaล‚ania, ostrzeลผenia dotyczฤ…ce sprawdzania danych wejล›ciowych i wyjล›ciowych +action.GitHubWorkflow.ClearActionCache.text=Wyczyล›ฤ‡ pamiฤ™ฤ‡ podrฤ™cznฤ… akcji +action.GitHubWorkflow.ClearActionCache.description=Wyczyล›ฤ‡ buforowane akcje GitHub i metadane przepล‚ywu pracy do ponownego wykorzystania +notification.cache.cleared=Wyczyszczono wpisy przepล‚ywu pracy {0} w pamiฤ™ci podrฤ™cznej GitHub. +notification.cache.refresh.started=Refreshing {0} buforowane zdalne wpisy przepล‚ywu pracy GitHub. +notification.warnings.restored=Przywrรณcono ostrzeลผenia dla wpisรณw przepล‚ywu pracy {0} GitHub. +workflow.run.configuration.display=Przebieg pracy GitHub +workflow.run.configuration.description=Wysyล‚aj i ล›ledลบ przebiegi przepล‚ywu pracy akcji GitHub +workflow.run.configuration.name=Przebieg pracy GitHub: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=Wล‚aล›ciciel +workflow.run.field.repo=Repozytorium +workflow.run.field.workflow=Plik przepล‚ywu pracy +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Token env var rezerwowy +workflow.run.inputs.title=Wejล›cia workflow_dispatch (klucz=wartoล›ฤ‡) +workflow.run.error.apiUrl=Wymagane jest GitHub API URL. +workflow.run.error.repository=Wymagany jest wล‚aล›ciciel i nazwa repozytorium GitHub. +workflow.run.error.workflow=Wymagany jest plik przepล‚ywu pracy. +workflow.run.error.ref=Wymagany jest numer oddziaล‚u lub tagu. +workflow.run.error.inputs=GitHub workflow_dispatch obsล‚uguje maksymalnie 25 wejล›ฤ‡. +workflow.run.gutter.stop=Zatrzymaj przebieg przepล‚ywu pracy +workflow.run.gutter.stop.text=Zatrzymaj przebieg przepล‚ywu pracy +workflow.run.gutter.stop.description=Anuluj ten bieg +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=biegnij: +workflow.log.warning=ostrzeลผenie: +workflow.log.error=bล‚ฤ…d: +workflow.run.cancel.requested=Zaลผฤ…dano anulowania: {0}. +workflow.run.stop.before.id=Zaลผฤ…dano zatrzymania. Nie ma jeszcze identyfikatora uruchomienia. +workflow.run.cancel.http=Anuluj HTTP {0}. Uff. +workflow.run.cancel.failed=Anuluj fizzed: {0} +workflow.run.interrupted=Przerwano. +workflow.run.link=Uruchom: {0} +workflow.run.discovery=Bieg zaakceptowany. Identyfikator biegu myล›liwskiego. +workflow.run.discovery.none=Nie ma jeszcze identyfikatora uruchomienia. Zakล‚adka Akcje wie wiฤ™cej. +workflow.run.status=Stan: {0}{1} +workflow.run.job.main=Stanowisko: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Stan: {0} {1}{2}{3} +workflow.run.logs.later=Dzienniki pojawiฤ… siฤ™, gdy GitHub je opublikuje. +workflow.run.job.logs.later=Zadanie {0}: {1} +workflow.run.log.failed=Pobieranie dziennika nie powiodล‚o siฤ™: {0} +workflow.run.log.failed.job=Pobieranie dziennika nie powiodล‚o siฤ™ dla {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Stanowisko: {0} +workflow.run.job.fallbackName=Zadanie {0} +workflow.run.overview=Przebieg pracy {0} {1}/{2} wykonany, {3} uruchomiony +workflow.run.state.ok=[OK] +workflow.run.state.fail=[NIEPOWODZENIE] +workflow.run.state.running=[BIEG] +workflow.run.state.waiting=[CZEKAJ] +workflow.run.dispatch.verbs=Przygotowanie|Kolejkowanie|Przywoล‚ywanie|Uruchamianie|Uruchamianie +workflow.run.dispatch.objects=przepล‚yw pracy|automatyzacja|potok|uruchamianie +workflow.run.dispatch={0} {1} {2}{3} dla {4} na {5}. +workflow.run.notification.auth=Wysyล‚anie przepล‚ywu pracy GitHub wymaga uwierzytelnionego konta GitHub. Dodaj lub odล›wieลผ konta w {0}. +workflow.run.notification.openSettings=Otwรณrz ustawienia GitHub +workflow.cache.progress.title=Rozwiฤ…zywanie dziaล‚aล„ GitHub +workflow.cache.progress.text=Rozwiฤ…zywanie {0} {1} +workflow.cache.kind.action=akcja +workflow.cache.kind.workflow=przepล‚yw pracy +inspection.parameter.input=wejล›cie +inspection.parameter.secret=sekret +inspection.action.delete.invalid=Usuล„ nieprawidล‚owe {0} [{1}] +inspection.action.update.major=Zaktualizuj akcjฤ™ [{0}] do [{1}] +inspection.warning.toggle=Przeล‚ฤ…cz ostrzeลผenia [{0}] dla [{1}] +inspection.warning.on=na +inspection.warning.off=wyล‚ฤ…czone +inspection.statement.incomplete=Niekompletne oล›wiadczenie [{0}] +inspection.invalid.suffix.remove=Usuล„ nieprawidล‚owy przyrostek [{0}] +inspection.replace.with=Zamieล„ na [{0}] +inspection.invalid.remove=Usuล„ nieprawidล‚owy [{0}] +inspection.workflow.syntax.unknownTopLevelKey=Nieznany klucz przepล‚ywu pracy [{0}] +inspection.workflow.syntax.unknownEventKey=Nieznane zdarzenie przepล‚ywu pracy [{0}] +inspection.workflow.syntax.unknownTriggerKey=Nieznany klucz wyzwalajฤ…cy [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Nieznany filtr wyzwalajฤ…cy [{0}] +inspection.workflow.syntax.unknownTriggerValue=Nieznana wartoล›ฤ‡ wyzwalacza [{0}] +inspection.workflow.syntax.unknownPermission=Nieznane uprawnienia [{0}] +inspection.workflow.syntax.unknownPermissionValue=Nieznana wartoล›ฤ‡ uprawnienia [{0}] +inspection.workflow.syntax.unknownJobKey=Nieznany klucz zadania [{0}] +inspection.workflow.syntax.unknownStepKey=Nieznany klucz kroku [{0}] +inspection.action.reload=Zaล‚aduj ponownie [{0}] +inspection.action.unresolved=Nierozwiฤ…zany [{0}] โ€” sprawdลบ dostฤ™p do konta GitHub, uprawnienia do prywatnego repozytorium, limity szybkoล›ci, brakujฤ…ce referencje lub brakujฤ…ce metadane akcji/przepล‚ywu pracy +inspection.action.jump=Skocz do pliku [{0}] +inspection.output.unused=Nieuลผywane [{0}] +inspection.secret.invalid.if=Usuล„ [{0}] โ€” wpisy tajne nie sฤ… prawidล‚owe w instrukcjach โ€žifโ€. +inspection.secret.replace.runtime=Zamieล„ [{0}] na [{1}] โ€“ jeล›li nie jest podany w czasie wykonywania +inspection.needs.invalid.job=Usuล„ nieprawidล‚owy identyfikator zadania [{0}] โ€” ten identyfikator zadania nie pasuje do ลผadnego poprzedniego zadania +documentation.description=Opis: {0} +documentation.type=Typ: {0} +documentation.required=Wymagane: {0} +documentation.default=Wartoล›ฤ‡ domyล›lna: {0} +documentation.deprecated=Przestarzaล‚e: {0} +documentation.open.declaration=Otwarta deklaracja ({0}) +documentation.input.label=Wejล›cie +documentation.secret.label=Sekret +documentation.env.label=Zmienna ล›rodowiskowa +documentation.matrix.label=Wล‚asnoล›ฤ‡ macierzy +documentation.need.label=Potrzebna praca +documentation.need.description=Bezpoล›rednia zaleลผnoล›ฤ‡ od pracy. +documentation.needOutput.label=Potrzebne wyniki pracy +documentation.reusableJob.label=Zadanie przepล‚ywu pracy wielokrotnego uลผytku +documentation.reusableJob.description=Zadanie zadeklarowane w tym przepล‚ywie pracy wielokrotnego uลผytku. +documentation.reusableJobOutput.label=Dane wyjล›ciowe zadania przepล‚ywu pracy wielokrotnego uลผytku +documentation.service.label=Kontener serwisowy +documentation.servicePort.label=Port serwisowy +documentation.container.label=Kontener pracy +documentation.symbol.label=Symbol przepล‚ywu pracy +documentation.symbol.description=Rozwiฤ…zane wyraลผenie przepล‚ywu pracy. +documentation.workflowOutput.label=Dane wyjล›ciowe przepล‚ywu pracy +documentation.jobOutput.label=Dane wyjล›ciowe zadania +documentation.action.label=Akcja +documentation.externalAction.label=Dziaล‚ania zewnฤ™trzne +documentation.reusableWorkflow.label=Przepล‚yw pracy wielokrotnego uลผytku +documentation.resolvedFrom=rozwiฤ…zany z {0} +documentation.notResolved=jeszcze nie rozwiฤ…zany +documentation.inputs.title=Wejล›cia +documentation.outputs.title=Wyjล›cia +documentation.secrets.title=Sekrety +documentation.value.label=Wartoล›ฤ‡ +documentation.step.title=Krok {0} +documentation.name.label=Imiฤ™ +documentation.uses.label=Uลผywa +documentation.run.label=Biegnij +documentation.description.label=Opis +documentation.step.label=Krok +documentation.source.label=ลนrรณdล‚o +documentation.stepOutput.label=Wyjล›cie krokowe +documentation.context.github=kontekst githuba +documentation.context.github.description=Informacje o bieลผฤ…cym przebiegu przepล‚ywu pracy i zdarzeniu. +documentation.context.gitea=kontekst gitea +documentation.context.gitea.description=Alias zgodny z Gitea dla kontekstu akcji GitHub. +documentation.context.inputs=kontekst wejล›ciowy +documentation.context.inputs.description=Dane wejล›ciowe przepล‚ywu pracy, wysyล‚ki lub akcji sฤ… dostฤ™pne tutaj. +documentation.context.secrets=kontekst tajemnic +documentation.context.secrets.description=Tajne wartoล›ci dostฤ™pne dla tego przepล‚ywu pracy lub wywoล‚ania przepล‚ywu pracy wielokrotnego uลผytku. +documentation.context.env=kontekst ล›rodowiska +documentation.context.env.description=Zmienne ล›rodowiskowe widoczne w tej lokalizacji. +documentation.context.matrix=kontekst matrycy +documentation.context.matrix.description=Wartoล›ci macierzy dla bieลผฤ…cego zadania. +documentation.context.steps=kontekst krokรณw +documentation.context.steps.description=Poprzednie kroki w bieลผฤ…cym zadaniu, w tym wyniki i status. +documentation.context.needs=potrzebuje kontekstu +documentation.context.needs.description=Bezpoล›rednie zaleลผnoล›ci miฤ™dzy zadaniami i ich produkty/rezultaty. +documentation.context.jobs=kontekst pracy +documentation.context.jobs.description=Zadania i wyniki przepล‚ywu pracy wielokrotnego uลผytku. +documentation.context.outputs=wyjล›cia +documentation.context.outputs.description=Wartoล›ci wyjล›ciowe ujawnione w tym kroku lub zadaniu. +documentation.context.result=wynik +documentation.context.result.description=Wynik zadania: sukces, poraลผka, anulowanie lub pominiฤ™cie. +documentation.context.outcome=wynik +documentation.context.outcome.description=Wynik kroku przed zastosowaniem bล‚ฤ™du kontynuacji. +documentation.context.conclusion=wniosek +documentation.context.conclusion.description=Wynik kroku po zastosowaniu bล‚ฤ™du kontynuacji. +error.report.action=Zgล‚oล› wyjฤ…tek +error.report.description=Opis +error.report.steps=Kroki do reprodukcji +error.report.sample=Proszฤ™ o podanie przykล‚adowego kodu, jeล›li ma to zastosowanie +error.report.message=Wiadomoล›ฤ‡ +error.report.runtime=Informacje o czasie wykonywania +error.report.pluginVersion=Wersja wtyczki: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=ลšledzenie stosu +completion.shell.bash=Powล‚oka Bash. Uลผywa bash na biegaczach Linux i macOS oraz Git dla bash Windows na biegaczach Windows. +completion.shell.sh=Awaryjna powล‚oka POSIX. +completion.shell.pwsh=Rdzeล„ PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Wiersz poleceล„ Windows. +completion.shell.python=Program uruchamiajฤ…cy polecenia Python. +completion.runner.name=Imiฤ™ i nazwisko biegacza wykonujฤ…cego zadanie. +completion.runner.os=System operacyjny biegacza wykonujฤ…cego zadanie. Moลผliwe wartoล›ci to Linux, Windows lub macOS. +completion.runner.arch=Architektura biegacza wykonujฤ…cego zadanie. Moลผliwe wartoล›ci to X86, X64, ARM lub ARM64. +completion.runner.temp=ลšcieลผka do katalogu tymczasowego w module runner. Katalog ten jest oprรณลผniany na poczฤ…tku i na koล„cu kaลผdego zadania. Naleลผy pamiฤ™taฤ‡, ลผe pliki nie zostanฤ… usuniฤ™te, jeล›li konto uลผytkownika biegacza nie ma uprawnieล„ do ich usuniฤ™cia. +completion.runner.toolCache=ลšcieลผka do katalogu zawierajฤ…cego preinstalowane narzฤ™dzia dla moduล‚รณw uruchamiajฤ…cych hostowanych przez GitHub. +completion.runner.debug=Jest to ustawiane tylko wtedy, gdy wล‚ฤ…czone jest rejestrowanie debugowania i zawsze ma wartoล›ฤ‡ 1. +completion.runner.environment=ลšrodowisko biegacza wykonujฤ…cego zadanie. Moลผliwe wartoล›ci to hostowane na Githubie lub hostowane samodzielnie. +completion.job.status=Aktualny status zadania. +completion.job.checkRunId=Identyfikator przebiegu kontroli bieลผฤ…cego zadania. +completion.job.container=Informacje o kontenerze pracy. +completion.job.services=Kontenery usล‚ug utworzone dla zadania. +completion.job.workflowRef=Peล‚ny odnoล›nik pliku przepล‚ywu pracy, ktรณry definiuje bieลผฤ…ce zadanie. +completion.job.workflowSha=Zatwierdzenie SHA pliku przepล‚ywu pracy, ktรณry definiuje bieลผฤ…ce zadanie. +completion.job.workflowRepository=Wล‚aล›ciciel/repozytorium repozytorium zawierajฤ…cego plik przepล‚ywu pracy, ktรณry definiuje bieลผฤ…ce zadanie. +completion.job.workflowFilePath=ลšcieลผka pliku przepล‚ywu pracy wzglฤ™dem katalogu gล‚รณwnego repozytorium. +completion.job.containerField=Pole kontenera zadaล„ +completion.job.service=Usล‚uga pracy +completion.job.serviceField=Pole usล‚ugi pracy +completion.job.mappedServicePort=Mapowany port serwisowy +completion.strategy.failFast=Okreล›la, czy wszystkie zadania w toku zostanฤ… anulowane, jeล›li ktรณrekolwiek zadanie macierzy zakoล„czy siฤ™ niepowodzeniem. +completion.strategy.jobIndex=Indeks od zera bieลผฤ…cego zadania w macierzy. +completion.strategy.jobTotal=Caล‚kowita liczba zadaล„ w macierzy. +completion.strategy.maxParallel=Maksymalna liczba zadaล„ macierzy, ktรณre mogฤ… byฤ‡ uruchamiane jednoczeล›nie. +completion.context.inputs=Wejล›cia przepล‚ywu pracy, takie jak workflow_dispatch lub workflow_call. +completion.context.secrets=Sekrety przepล‚ywu pracy. +completion.context.job=Informacje o aktualnie wykonywanej pracy. +completion.context.jobs=Zadania przepล‚ywu pracy. +completion.context.matrix=Wล‚aล›ciwoล›ci macierzy zdefiniowane dla bieลผฤ…cego zadania macierzy. +completion.context.strategy=Informacje o strategii wykonywania macierzy dla bieลผฤ…cego zadania. +completion.context.steps=Kroki z identyfikatorem w bieลผฤ…cym zadaniu. +completion.context.env=Zmienne ล›rodowiskowe z zadaล„ i krokรณw. +completion.context.vars=Niestandardowe zmienne konfiguracyjne z zakresรณw organizacji, repozytorium i ล›rodowiska. +completion.context.needs=Zadania, ktรณre muszฤ… zostaฤ‡ ukoล„czone, zanim bฤ™dzie moลผna uruchomiฤ‡ to zadanie, wraz z ich wynikami i wynikami. +completion.context.github=Informacje o przebiegu przepล‚ywu pracy i zdarzeniach z kontekstu GitHub. +completion.context.gitea=Alias zgodny z Gitea dla kontekstu akcji GitHub. +completion.context.runner=Informacja o biegaczu wykonujฤ…cym bieลผฤ…ce zadanie. +completion.secret.githubToken=Automatycznie tworzony token dla kaลผdego uruchomienia przepล‚ywu pracy. +completion.remote.repository=Zdalne repozytorium +completion.uses.local.workflow=Lokalny przepล‚yw pracy wielokrotnego uลผytku +completion.uses.local.action=Akcja lokalna +completion.uses.ref.known=Znane odniesienie do przepล‚ywu pracy +completion.uses.ref.remote=Odniesienie do zdalnego przepล‚ywu pracy +completion.uses.remote.known=Znane zdalne dziaล‚anie lub przepล‚yw pracy do ponownego wykorzystania +completion.workflow.syntax=GitHub Skล‚adnia przepล‚ywu pracy dziaล‚aล„ +completion.workflow.top.name=Nazwa wyล›wietlana przepล‚ywu pracy +completion.workflow.top.run-name=Dynamiczna nazwa uruchomienia +completion.workflow.top.on=Zdarzenia rozpoczynajฤ…ce przepล‚yw pracy +completion.workflow.top.permissions=Domyล›lne uprawnienia GITHUB_TOKEN +completion.workflow.top.env=Zmienne ล›rodowiskowe obejmujฤ…ce caล‚y przepล‚yw pracy +completion.workflow.top.defaults=Domyล›lne ustawienia zadania i kroku +completion.workflow.top.concurrency=Grupa wspรณล‚bieลผnoล›ci i anulowanie +completion.workflow.top.jobs=Zadania uruchamiane w tym przepล‚ywie pracy +completion.workflow.event.branch_protection_rule=Zmieniono ochronฤ™ oddziaล‚รณw +completion.workflow.event.check_run=Zmieniono przebieg pojedynczego testu +completion.workflow.event.check_suite=Sprawdลบ, czy pakiet zostaล‚ zmieniony +completion.workflow.event.create=Utworzono gaล‚ฤ…ลบ lub tag +completion.workflow.event.delete=Gaล‚ฤ…ลบ lub etykieta zostaล‚a usuniฤ™ta +completion.workflow.event.deployment=Wdroลผenie zostaล‚o utworzone +completion.workflow.event.deployment_status=Stan wdroลผenia zostaล‚ zmieniony +completion.workflow.event.discussion=Dyskusja ulegล‚a zmianie +completion.workflow.event.discussion_comment=Komentarz do dyskusji zostaล‚ zmieniony +completion.workflow.event.fork=Repozytorium rozwidlone +completion.workflow.event.gollum=Strona Wiki zostaล‚a zmieniona +completion.workflow.event.image_version=Zmieniono wersjฤ™ obrazu pakietu +completion.workflow.event.issue_comment=Zmieniono problem lub komentarz PR +completion.workflow.event.issues=Problem zmieniony +completion.workflow.event.label=Etykieta zmieniona +completion.workflow.event.merge_group=Zaลผฤ…dano sprawdzenia kolejki scalania +completion.workflow.event.milestone=Kamieล„ milowy zmieniony +completion.workflow.event.page_build=Budowa stron przebiegล‚a +completion.workflow.event.project=Klasyczny projekt zmieniony +completion.workflow.event.project_card=Zmieniono klasycznฤ… kartฤ™ projektu +completion.workflow.event.project_column=Zmieniono kolumnฤ™ klasycznego projektu +completion.workflow.event.public=Repozytorium staล‚o siฤ™ publiczne +completion.workflow.event.pull_request=ลปฤ…danie ล›ciฤ…gniฤ™cia zostaล‚o zmienione +completion.workflow.event.pull_request_review=Recenzja PR zostaล‚a zmieniona +completion.workflow.event.pull_request_review_comment=Zmieniono komentarz do recenzji PR +completion.workflow.event.pull_request_target=Kontekst docelowy PR. Ostre noลผe. +completion.workflow.event.push=Zatwierdzono lub wypchniฤ™to tag +completion.workflow.event.registry_package=Pakiet opublikowany lub zaktualizowany +completion.workflow.event.release=Wydanie zmienione +completion.workflow.event.repository_dispatch=Niestandardowe zdarzenie API +completion.workflow.event.schedule=Kleszcz Crona. Mechanizm zegarowy. +completion.workflow.event.status=Stan zatwierdzenia zmieniony +completion.workflow.event.watch=Repozytorium oznaczone gwiazdkฤ… +completion.workflow.event.workflow_call=Wywoล‚anie przepล‚ywu pracy wielokrotnego uลผytku +completion.workflow.event.workflow_dispatch=Przycisk uruchamiania rฤ™cznego +completion.workflow.event.workflow_run=Zmieniono przebieg przepล‚ywu pracy +completion.workflow.eventFilter.types=Ogranicz rodzaje aktywnoล›ci +completion.workflow.eventFilter.branches=Tylko te gaล‚ฤ™zie +completion.workflow.eventFilter.branches-ignore=Pomiล„ te gaล‚ฤ™zie +completion.workflow.eventFilter.tags=Tylko te tagi +completion.workflow.eventFilter.tags-ignore=Pomiล„ te tagi +completion.workflow.eventFilter.paths=Tylko te ล›cieลผki +completion.workflow.eventFilter.paths-ignore=Pomiล„ te ล›cieลผki +completion.workflow.eventFilter.workflows=Nazwy przepล‚ywรณw pracy do obejrzenia +completion.workflow.eventFilter.cron=Harmonogram Crona. Malutki mechanizm zegarowy. +completion.workflow.permission.actions=Przepล‚ywy pracy i artefakty akcji +completion.workflow.permission.artifact-metadata=Rekordy metadanych artefaktรณw +completion.workflow.permission.attestations=Atesty artefaktรณw +completion.workflow.permission.checks=Sprawdลบ trasy i apartamenty +completion.workflow.permission.code-quality=Raporty dotyczฤ…ce jakoล›ci kodu +completion.workflow.permission.contents=Zawartoล›ฤ‡ repozytorium +completion.workflow.permission.deployments=Wdroลผenia +completion.workflow.permission.discussions=Dyskusje +completion.workflow.permission.id-token=Tokeny OpenID Connect +completion.workflow.permission.issues=Problemy +completion.workflow.permission.models=Modele GitHub +completion.workflow.permission.packages=Pakiety GitHub +completion.workflow.permission.pages=Strony GitHub +completion.workflow.permission.pull-requests=ลšciฤ…gnij ลผฤ…dania +completion.workflow.permission.security-events=Skanowanie kodu i zdarzenia zwiฤ…zane z bezpieczeล„stwem +completion.workflow.permission.statuses=Statusy zatwierdzania +completion.workflow.permission.vulnerability-alerts=Alerty Dependabot +completion.workflow.permission.value.read=Dostฤ™p do odczytu +completion.workflow.permission.value.write=Dostฤ™p do zapisu, odczyt wliczony w cenฤ™ +completion.workflow.permission.value.none=Brak dostฤ™pu +completion.workflow.permission.shorthand.read-all=Wszystkie uprawnienia przeczytane. Duลผy koc. +completion.workflow.permission.shorthand.write-all=Wszystkie uprawnienia zapisuj. Duลผy mล‚otek. +completion.workflow.permission.shorthand.empty=Wyล‚ฤ…cz uprawnienia tokena +completion.workflow.job.name=Wyล›wietlana nazwa zadania +completion.workflow.job.permissions=Uprawnienia tokenu zadania +completion.workflow.job.needs=Praca, na ktรณrฤ… trzeba czekaฤ‡ +completion.workflow.job.if=Stan pracy +completion.workflow.job.runs-on=Etykieta lub grupa biegacza +completion.workflow.job.snapshot=Zdjฤ™cie biegacza +completion.workflow.job.environment=ลšrodowisko wdroลผenia +completion.workflow.job.concurrency=Blokada wspรณล‚bieลผnoล›ci zadaล„ +completion.workflow.job.outputs=Dane wyjล›ciowe, ktรณre mogฤ… odczytaฤ‡ inne zadania +completion.workflow.job.env=Zmienne ล›rodowiskowe zadania +completion.workflow.job.defaults=Domyล›lne ustawienia zadania +completion.workflow.job.steps=Lista krokรณw. Rzeczywista praca. +completion.workflow.job.timeout-minutes=Limit czasu zadania w minutach +completion.workflow.job.strategy=Strategia macierzowa i harmonogramujฤ…ca +completion.workflow.job.continue-on-error=Niech ta praca zakoล„czy siฤ™ niepowodzeniem +completion.workflow.job.container=Kontener do tego zadania +completion.workflow.job.services=Kontenery serwisowe z wรณzkiem bocznym +completion.workflow.job.uses=Przepล‚yw pracy wielokrotnego uลผytku, do ktรณrego moลผna zadzwoniฤ‡ +completion.workflow.job.with=Dane wejล›ciowe dla wywoล‚ywanego przepล‚ywu pracy +completion.workflow.job.secrets=Sekrety wywoล‚ywanego przepล‚ywu pracy +completion.workflow.defaultsRun.shell=Domyล›lna powล‚oka dla krokรณw uruchamiania +completion.workflow.defaultsRun.working-directory=Domyล›lny katalog roboczy +completion.workflow.concurrency.group=Zablokuj nazwฤ™ dla uruchomieล„ w kolejce +completion.workflow.concurrency.cancel-in-progress=Anuluj starsze przebiegi dopasowywania +completion.workflow.environment.name=Nazwa ล›rodowiska +completion.workflow.environment.url=ลšrodowisko URL +completion.workflow.strategy.matrix=Osie i warianty macierzy +completion.workflow.strategy.fail-fast=Anuluj rodzeล„stwo macierzy w przypadku niepowodzenia +completion.workflow.strategy.max-parallel=Ograniczenie rรณwnolegล‚oล›ci macierzy +completion.workflow.matrix.include=Dodaj kombinacje macierzy +completion.workflow.matrix.exclude=Usuล„ kombinacje macierzy +completion.workflow.step.id=Identyfikator kroku dla referencji +completion.workflow.step.if=Warunek kroku +completion.workflow.step.name=Nazwa wyล›wietlana kroku +completion.workflow.step.uses=Akcja do uruchomienia +completion.workflow.step.run=Skrypt powล‚oki do uruchomienia +completion.workflow.step.shell=Shell dla tego kroku uruchomienia +completion.workflow.step.with=Dane wejล›ciowe akcji +completion.workflow.step.env=Zmienne ล›rodowiskowe kroku +completion.workflow.step.continue-on-error=Niech ten krok zakoล„czy siฤ™ niepowodzeniem +completion.workflow.step.timeout-minutes=Limit czasu kroku w minutach +completion.workflow.step.working-directory=Krok katalogu roboczego +completion.workflow.container.image=Obraz kontenera +completion.workflow.container.credentials=Poล›wiadczenia rejestru +completion.workflow.container.env=Zmienne ล›rodowiskowe kontenera +completion.workflow.container.ports=Porty do odsล‚oniฤ™cia +completion.workflow.container.volumes=Woluminy do zamontowania +completion.workflow.container.options=Opcje tworzenia Docker +completion.workflow.service.image=Obraz kontenera usล‚ug +completion.workflow.service.credentials=Poล›wiadczenia rejestru +completion.workflow.service.env=Zmienne ล›rodowiskowe usล‚ugi +completion.workflow.service.ports=Porty serwisowe +completion.workflow.service.volumes=Wolumen usล‚ug +completion.workflow.service.options=Opcje tworzenia Docker +completion.workflow.credentials.username=Nazwa uลผytkownika rejestru +completion.workflow.credentials.password=Hasล‚o lub token rejestru +completion.workflow.inputType.string=Wprowadzanie tekstu +completion.workflow.inputType.boolean=Dane wejล›ciowe prawdziwe lub faล‚szywe +completion.workflow.inputType.choice=Wejล›cie wyboru rozwijanego +completion.workflow.inputType.number=Wprowadzanie numeru +completion.workflow.inputType.environment=Dane wejล›ciowe selektora ล›rodowiska +completion.workflow.boolean.true=Tak. Wล‚ฤ…cz to. +completion.workflow.boolean.false=Nie. Zachowaj ciemnoล›ฤ‡. +completion.workflow.runner.ubuntu-latest=Najnowszy biegacz Ubuntu +completion.workflow.runner.ubuntu-24.04=Biegacz Ubuntu 24.04 +completion.workflow.runner.ubuntu-22.04=Biegacz Ubuntu 22.04 +completion.workflow.runner.windows-latest=Najnowszy biegacz Windows +completion.workflow.runner.windows-2025=Serwer Windows 2025 +completion.workflow.runner.windows-2022=Serwer Windows 2022 +completion.workflow.runner.macos-latest=Najnowszy biegacz macOS +completion.workflow.runner.macos-15=Prowadnica macOS 15 +completion.workflow.runner.macos-14=Prowadnica macOS 14 +completion.workflow.runner.self-hosted=Twรณj wล‚asny biegacz. Twรณj cyrk. +completion.steps.outputs=Zestaw wyjล›ฤ‡ zdefiniowany dla kroku. +completion.steps.conclusion=Stosowany jest wynik zakoล„czonego kroku po bล‚ฤ™dzie kontynuacji. +completion.steps.outcome=Wynik zakoล„czonego kroku przed zastosowaniem bล‚ฤ™du kontynuacji. +completion.jobs.outputs=Zestaw wynikรณw zdefiniowany dla zadania. +completion.jobs.result=Wynik pracy. +settings.displayName=Przebieg pracy GitHub +settings.language.label=Jฤ™zyk: +settings.language.system=Wartoล›ฤ‡ domyล›lna IDE/systemowa +settings.cache.title=Pamiฤ™ฤ‡ akcji +settings.cache.column.key=Klucz pamiฤ™ci podrฤ™cznej +settings.cache.column.name=Imiฤ™ +settings.cache.column.kind=Miล‚y +settings.cache.column.state=Stan +settings.cache.column.expires=Wygasa +settings.cache.kind.local=lokalny +settings.cache.kind.remote=zdalny +settings.cache.state.resolved=rozwiฤ…zany +settings.cache.state.pending=w toku +settings.cache.state.expired=nieaktualne +settings.cache.state.suppressed=stล‚umiony +settings.cache.refresh=Tabela Refresh +settings.cache.deleteSelected=Usuล„ wybrane +settings.cache.deleteAll=Usuล„ wszystko +settings.cache.export=Eksportuj +settings.cache.import=Importuj +settings.cache.summary=Pamiฤ™ฤ‡ podrฤ™czna: wpisy {0}, rozwiฤ…zane {1}, zdalne {2}, nieaktualne {3}, wyciszone {4}. Pamiฤ™ฤ‡ podrฤ™czna: {5} KB. +settings.cache.noneSelected=Najpierw wybierz wiersze pamiฤ™ci podrฤ™cznej. Miotล‚a nie dopuszcza domysล‚รณw. +settings.cache.deleteSelected.done=Usuniฤ™to wpisy pamiฤ™ci podrฤ™cznej {0}. Zawarta niewielka chmura pyล‚u. +settings.cache.deleteAll.confirm=Usunฤ…ฤ‡ wszystkie wpisy pamiฤ™ci podrฤ™cznej przepล‚ywu pracy GitHub? +settings.cache.deleteAll.done=Usuniฤ™to wszystkie wpisy pamiฤ™ci podrฤ™cznej. Pamiฤ™ฤ‡ podrฤ™czna jest teraz podejrzanie cicha. +settings.cache.export.done=Wyeksportowano wpisy pamiฤ™ci podrฤ™cznej {0}. Maล‚a archiwalna bestia w klatce. +settings.cache.import.done=Zaimportowane wpisy pamiฤ™ci podrฤ™cznej. Bestia z archiwum zachowaล‚a siฤ™. +settings.cache.import.unsupported=Nieobsล‚ugiwany plik pamiฤ™ci podrฤ™cznej przepล‚ywu pracy GitHub. +settings.cache.import.brokenLine=Uszkodzona linia pamiฤ™ci podrฤ™cznej przepล‚ywu pracy GitHub. +settings.cache.import.brokenKey=Uszkodzony klucz pamiฤ™ci podrฤ™cznej GitHub przepล‚ywu pracy. +settings.support.button=Wesprzyj tฤ™ wtyczkฤ™ +settings.support.tooltip=Otwรณrz stronฤ™ wsparcia +settings.support.line.0=Zasil piec budowlany +settings.support.line.1=Kup kawฤ™ parserowฤ… +settings.support.line.2=Sponsoruj mniej nawiedzonych przepล‚ywรณw pracy +workflow.run.jobs.title=Zadania przepล‚ywu pracy +workflow.run.jobs.root=Uruchomienie przepล‚ywu pracy +workflow.run.jobs.description=GitHub Drzewo zadaล„ akcji i wybrany protokรณล‚ zadania +workflow.run.tree.done=zrobione +workflow.run.tree.failed=nie powiodล‚o siฤ™ +workflow.run.tree.skipped=pominiฤ™te +workflow.run.tree.warn=ostrzegaฤ‡ +workflow.run.tree.err=bล‚ฤ…d +workflow.run.delete.tooltip=Usuล„ bieg +workflow.run.delete.noRun=Nie ma jeszcze identyfikatora uruchomienia. +workflow.run.delete.requested=Usuwanie przebiegu {0}. +workflow.run.delete.done=Uruchomienie {0} zostaล‚o usuniฤ™te. +workflow.run.delete.http=Usuล„ HTTP {0}. Uff. +workflow.run.delete.failed=Usuล„ fizzled: {0} +workflow.run.rerun.all.tooltip=Uruchom ponownie przepล‚yw pracy +workflow.run.rerun.failed.tooltip=Uruchom ponownie nieudane zadania +workflow.run.rerun.noRun=Nie ma jeszcze identyfikatora uruchomienia. +workflow.run.rerun.all.requested=Zaลผฤ…dano ponownego uruchomienia: {0}. +workflow.run.rerun.failed.requested=Zaลผฤ…dano nieudanych zadaล„: {0}. +workflow.run.rerun.all.done=Uruchom ponownie w kolejce: {0}. +workflow.run.rerun.failed.done=Nieudane zadania w kolejce: {0}. +workflow.run.rerun.http=Uruchom ponownie HTTP {0}. Uff. +workflow.run.rerun.failed=Ponowne uruchomienie zakoล„czyล‚o siฤ™ fiaskiem: {0} +workflow.run.download.log.tooltip=Zapisz dziennik zadaล„ +workflow.run.download.artifacts.tooltip=Pobierz artefakty +workflow.run.download.noRun=Nie ma jeszcze identyfikatora uruchomienia. +workflow.run.download.log.requested=Pobieram dziennik dla {0}. +workflow.run.download.log.done=Dziennik zostaล‚ zapisany: {0}. +workflow.run.download.artifacts.requested=Pobieranie artefaktรณw. +workflow.run.download.artifacts.empty=ลปadnych artefaktรณw. Maล‚a pustka. +workflow.run.download.artifact.expired=Artefakt wygasล‚: {0}. +workflow.run.download.artifact.done=Zapisano artefakt: {0} -> {1}. +workflow.run.download.failed=Pobieranie zakoล„czyล‚o siฤ™ fiaskiem: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties b/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties new file mode 100644 index 0000000..1bcae49 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties @@ -0,0 +1,442 @@ +plugin.name=Fluxo de trabalho GitHub +plugin.description=Suporte para arquivos de fluxo de trabalho de aรงรตes GitHub +group.GitHubWorkflow.Tools.text=Fluxo de trabalho GitHub +group.GitHubWorkflow.Tools.description=Ferramentas de plug-in de fluxo de trabalho GitHub +action.GitHubWorkflow.RefreshActionCache.text=Cache de aรงรฃo Refresh +action.GitHubWorkflow.RefreshActionCache.description=Refresh resolveu aรงรตes GitHub remotas e metadados de fluxo de trabalho reutilizรกveis +action.GitHubWorkflow.RestoreActionWarnings.text=Avisos de aรงรฃo de restauraรงรฃo +action.GitHubWorkflow.RestoreActionWarnings.description=Restaurar avisos de validaรงรฃo de aรงรฃo, entrada e saรญda suprimidos +action.GitHubWorkflow.ClearActionCache.text=Limpar cache de aรงรตes +action.GitHubWorkflow.ClearActionCache.description=Limpar aรงรตes GitHub em cache e metadados de fluxo de trabalho reutilizรกveis +notification.cache.cleared=Entradas de fluxo de trabalho GitHub armazenadas em cache do {0} foram limpas. +notification.cache.refresh.started=Refreshing {0} armazenou em cache entradas remotas do fluxo de trabalho GitHub. +notification.warnings.restored=Avisos restaurados para entradas do fluxo de trabalho {0} GitHub. +workflow.run.configuration.display=Fluxo de trabalho GitHub +workflow.run.configuration.description=Despachar e seguir execuรงรตes de fluxo de trabalho de aรงรตes GitHub +workflow.run.configuration.name=Fluxo de trabalho GitHub: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=Proprietรกrio +workflow.run.field.repo=Repositรณrio +workflow.run.field.workflow=Arquivo de fluxo de trabalho +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Fallback de var de ambiente de token +workflow.run.inputs.title=Entradas workflow_dispatch (chave=valor) +workflow.run.error.apiUrl=GitHub API URL รฉ necessรกrio. +workflow.run.error.repository=O proprietรกrio e o nome do repositรณrio GitHub sรฃo obrigatรณrios. +workflow.run.error.workflow=O arquivo de fluxo de trabalho รฉ obrigatรณrio. +workflow.run.error.ref=A referรชncia de ramificaรงรฃo ou tag รฉ obrigatรณria. +workflow.run.error.inputs=GitHub workflow_dispatch suporta no mรกximo 25 entradas. +workflow.run.gutter.stop=Interromper a execuรงรฃo do fluxo de trabalho +workflow.run.gutter.stop.text=Interromper a execuรงรฃo do fluxo de trabalho +workflow.run.gutter.stop.description=Cancelar esta execuรงรฃo +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=execute: +workflow.log.warning=aviso: +workflow.log.error=erro: +workflow.run.cancel.requested=Cancelamento solicitado: {0}. +workflow.run.stop.before.id=Parada solicitada. Ainda nรฃo hรก ID de execuรงรฃo. +workflow.run.cancel.http=Cancele HTTP {0}. Ufa. +workflow.run.cancel.failed=Cancelamento fracassou: {0} +workflow.run.interrupted=Interrompido. +workflow.run.link=Execute: {0} +workflow.run.discovery=Corrida aceita. ID da corrida de caรงa. +workflow.run.discovery.none=Ainda nรฃo hรก ID de execuรงรฃo. A guia Aรงรตes sabe mais. +workflow.run.status=Status: {0}{1} +workflow.run.job.main=Trabalho: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Status: {0} {1}{2}{3} +workflow.run.logs.later=Os logs aparecerรฃo quando GitHub os publicar. +workflow.run.job.logs.later=Trabalho {0}: {1} +workflow.run.log.failed=Falha no download do registro: {0} +workflow.run.log.failed.job=Falha no download do log para {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Trabalho: {0} +workflow.run.job.fallbackName=Trabalho {0} +workflow.run.overview=Fluxo de trabalho executado {0} {1}/{2} concluรญdo, {3} em execuรงรฃo +workflow.run.state.ok=[OK] +workflow.run.state.fail=[FALHA] +workflow.run.state.running=[EXECUTAR] +workflow.run.state.waiting=[ESPERE] +workflow.run.dispatch.verbs=Preparando | Enfileirando | Convocando | Inicializando | Inicializando +workflow.run.dispatch.objects=fluxo de trabalho | automaรงรฃo | pipeline | execuรงรฃo +workflow.run.dispatch={0} {1} {2}{3} para {4} em {5}. +workflow.run.notification.auth=O despacho do fluxo de trabalho GitHub precisa de uma conta GitHub autenticada. Adicione ou atualize contas em {0}. +workflow.run.notification.openSettings=Abra as configuraรงรตes do GitHub +workflow.cache.progress.title=Resolvendo aรงรตes GitHub +workflow.cache.progress.text=Resolvendo {0} {1} +workflow.cache.kind.action=aรงรฃo +workflow.cache.kind.workflow=fluxo de trabalho +inspection.parameter.input=entrada +inspection.parameter.secret=segredo +inspection.action.delete.invalid=Excluir {0} invรกlido [{1}] +inspection.action.update.major=Atualizar aรงรฃo [{0}] para [{1}] +inspection.warning.toggle=Alternar avisos [{0}] para [{1}] +inspection.warning.on=ligado +inspection.warning.off=desligado +inspection.statement.incomplete=Declaraรงรฃo incompleta [{0}] +inspection.invalid.suffix.remove=Remover sufixo invรกlido [{0}] +inspection.replace.with=Substitua por [{0}] +inspection.invalid.remove=Remover [{0}] invรกlido +inspection.workflow.syntax.unknownTopLevelKey=Chave de fluxo de trabalho desconhecida [{0}] +inspection.workflow.syntax.unknownEventKey=Evento de fluxo de trabalho desconhecido [{0}] +inspection.workflow.syntax.unknownTriggerKey=Chave de acionamento desconhecida [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Filtro de gatilho desconhecido [{0}] +inspection.workflow.syntax.unknownTriggerValue=Valor de acionamento desconhecido [{0}] +inspection.workflow.syntax.unknownPermission=Permissรฃo desconhecida [{0}] +inspection.workflow.syntax.unknownPermissionValue=Valor de permissรฃo desconhecido [{0}] +inspection.workflow.syntax.unknownJobKey=Chave de trabalho desconhecida [{0}] +inspection.workflow.syntax.unknownStepKey=Chave de etapa desconhecida [{0}] +inspection.action.reload=Recarregar [{0}] +inspection.action.unresolved=Nรฃo resolvido [{0}] - verifique o acesso ร  conta GitHub, permissรตes de repositรณrio privado, limites de taxa, referรชncias ausentes ou metadados de aรงรฃo/fluxo de trabalho ausentes +inspection.action.jump=Ir para o arquivo [{0}] +inspection.output.unused=Nรฃo utilizado [{0}] +inspection.secret.invalid.if=Remover [{0}] - Os segredos nรฃo sรฃo vรกlidos em instruรงรตes `if` +inspection.secret.replace.runtime=Substitua [{0}] por [{1}] - se nรฃo for fornecido em tempo de execuรงรฃo +inspection.needs.invalid.job=Remover jobId invรกlido [{0}] - este jobId nรฃo corresponde a nenhum trabalho anterior +documentation.description=Descriรงรฃo: {0} +documentation.type=Tipo: {0} +documentation.required=Obrigatรณrio: {0} +documentation.default=Padrรฃo: {0} +documentation.deprecated=Obsoleto: {0} +documentation.open.declaration=Declaraรงรฃo aberta ({0}) +documentation.input.label=Entrada +documentation.secret.label=Segredo +documentation.env.label=Variรกvel de ambiente +documentation.matrix.label=Propriedade matricial +documentation.need.label=Trabalho necessรกrio +documentation.need.description=Dependรชncia direta do trabalho. +documentation.needOutput.label=Saรญda de trabalho necessรกria +documentation.reusableJob.label=Trabalho de fluxo de trabalho reutilizรกvel +documentation.reusableJob.description=Trabalho declarado neste fluxo de trabalho reutilizรกvel. +documentation.reusableJobOutput.label=Saรญda de trabalho de fluxo de trabalho reutilizรกvel +documentation.service.label=Contรชiner de serviรงo +documentation.servicePort.label=Porta de serviรงo +documentation.container.label=Contรชiner de trabalho +documentation.symbol.label=Sรญmbolo de fluxo de trabalho +documentation.symbol.description=Expressรฃo de fluxo de trabalho resolvida. +documentation.workflowOutput.label=Saรญda do fluxo de trabalho +documentation.jobOutput.label=Saรญda do trabalho +documentation.action.label=Aรงรฃo +documentation.externalAction.label=Aรงรฃo externa +documentation.reusableWorkflow.label=Fluxo de trabalho reutilizรกvel +documentation.resolvedFrom=resolvido de {0} +documentation.notResolved=ainda nรฃo resolvido +documentation.inputs.title=Entradas +documentation.outputs.title=Resultados +documentation.secrets.title=Segredos +documentation.value.label=Valor +documentation.step.title=Etapa {0} +documentation.name.label=Nome +documentation.uses.label=Usos +documentation.run.label=Corre +documentation.description.label=Descriรงรฃo +documentation.step.label=Passo +documentation.source.label=Fonte +documentation.stepOutput.label=Saรญda de etapa +documentation.context.github=contexto do github +documentation.context.github.description=Informaรงรตes sobre a execuรงรฃo e o evento do fluxo de trabalho atual. +documentation.context.gitea=contexto gitea +documentation.context.gitea.description=Alias compatรญvel com Gitea para o contexto de aรงรตes GitHub. +documentation.context.inputs=contexto de entradas +documentation.context.inputs.description=Entradas de fluxo de trabalho, despacho ou aรงรฃo disponรญveis aqui. +documentation.context.secrets=contexto de segredos +documentation.context.secrets.description=Valores secretos disponรญveis para este fluxo de trabalho ou chamada de fluxo de trabalho reutilizรกvel. +documentation.context.env=contexto ambiental +documentation.context.env.description=Variรกveis de ambiente visรญveis neste local. +documentation.context.matrix=contexto matricial +documentation.context.matrix.description=Valores de matriz para o trabalho atual. +documentation.context.steps=contexto das etapas +documentation.context.steps.description=Etapas anteriores do trabalho atual, incluindo resultados e status. +documentation.context.needs=precisa de contexto +documentation.context.needs.description=Dependรชncias diretas do trabalho e suas saรญdas/resultados. +documentation.context.jobs=contexto de empregos +documentation.context.jobs.description=Trabalhos e saรญdas de fluxo de trabalho reutilizรกveis. +documentation.context.outputs=saรญdas +documentation.context.outputs.description=Valores de saรญda expostos por esta etapa ou trabalho. +documentation.context.result=resultado +documentation.context.result.description=Resultado do trabalho: sucesso, falha, cancelado ou ignorado. +documentation.context.outcome=resultado +documentation.context.outcome.description=Resultado da etapa antes da aplicaรงรฃo da continuaรงรฃo em caso de erro. +documentation.context.conclusion=conclusรฃo +documentation.context.conclusion.description=Resultado da etapa apรณs a aplicaรงรฃo do continue-on-error. +error.report.action=Exceรงรฃo de relatรณrio +error.report.description=Descriรงรฃo +error.report.steps=Etapas para reproduzir +error.report.sample=Forneรงa um exemplo de cรณdigo, se aplicรกvel +error.report.message=Mensagem +error.report.runtime=Informaรงรตes de tempo de execuรงรฃo +error.report.pluginVersion=Versรฃo do plug-in: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=Stacktrace +completion.shell.bash=Concha Bash. Usa bash em executores Linux e macOS e Git para bash Windows em executores Windows. +completion.shell.sh=Reserva de shell POSIX. +completion.shell.pwsh=Nรบcleo PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Prompt de comando Windows. +completion.shell.python=Executor de comandos Python. +completion.runner.name=O nome do executor que executa o trabalho. +completion.runner.os=O sistema operacional do executor que executa o trabalho. Os valores possรญveis sรฃo Linux, Windows ou macOS. +completion.runner.arch=A arquitetura do executor que executa o trabalho. Os valores possรญveis sรฃo X86, X64, ARM ou ARM64. +completion.runner.temp=O caminho para um diretรณrio temporรกrio no executor. Este diretรณrio รฉ esvaziado no inรญcio e no final de cada trabalho. Observe que os arquivos nรฃo serรฃo removidos se a conta de usuรกrio do corredor nรฃo tiver permissรฃo para excluรญ-los. +completion.runner.toolCache=O caminho para o diretรณrio que contรฉm ferramentas prรฉ-instaladas para executores hospedados em GitHub. +completion.runner.debug=Isso serรก definido somente se o log de depuraรงรฃo estiver ativado e sempre terรก o valor 1. +completion.runner.environment=O ambiente do executor que executa o trabalho. Os valores possรญveis sรฃo hospedados no github ou auto-hospedados. +completion.job.status=O status atual do trabalho. +completion.job.checkRunId=O ID de execuรงรฃo de verificaรงรฃo do trabalho atual. +completion.job.container=Informaรงรตes sobre o contรชiner do trabalho. +completion.job.services=Os contรชineres de serviรงo criados para um trabalho. +completion.job.workflowRef=A referรชncia completa do arquivo de fluxo de trabalho que define o trabalho atual. +completion.job.workflowSha=O commit SHA do arquivo de fluxo de trabalho que define o trabalho atual. +completion.job.workflowRepository=O proprietรกrio/repositรณrio do repositรณrio que contรฉm o arquivo de fluxo de trabalho que define o trabalho atual. +completion.job.workflowFilePath=O caminho do arquivo de fluxo de trabalho, relativo ร  raiz do repositรณrio. +completion.job.containerField=Campo de contรชiner de trabalho +completion.job.service=Serviรงo de trabalho +completion.job.serviceField=Campo de serviรงo de trabalho +completion.job.mappedServicePort=Porta de serviรงo mapeada +completion.strategy.failFast=Se todos os trabalhos em andamento serรฃo cancelados se algum trabalho de matriz falhar. +completion.strategy.jobIndex=O รญndice baseado em zero do trabalho atual na matriz. +completion.strategy.jobTotal=O nรบmero total de empregos na matriz. +completion.strategy.maxParallel=O nรบmero mรกximo de trabalhos de matriz que podem ser executados simultaneamente. +completion.context.inputs=Entradas de fluxo de trabalho, como workflow_dispatch ou workflow_call. +completion.context.secrets=Segredos do fluxo de trabalho. +completion.context.job=Informaรงรตes sobre o trabalho atualmente em execuรงรฃo. +completion.context.jobs=Trabalhos de fluxo de trabalho. +completion.context.matrix=Propriedades de matriz definidas para o trabalho de matriz atual. +completion.context.strategy=Informaรงรตes da estratรฉgia de execuรงรฃo da matriz para o trabalho atual. +completion.context.steps=Etapas com um ID no trabalho atual. +completion.context.env=Variรกveis de ambiente de trabalhos e etapas. +completion.context.vars=Variรกveis de configuraรงรฃo personalizadas de escopos de organizaรงรฃo, repositรณrio e ambiente. +completion.context.needs=Trabalhos que devem ser concluรญdos antes que este trabalho possa ser executado, alรฉm de suas saรญdas e resultados. +completion.context.github=Informaรงรตes de evento e execuรงรฃo de fluxo de trabalho do contexto GitHub. +completion.context.gitea=Alias compatรญvel com Gitea para o contexto de aรงรตes GitHub. +completion.context.runner=Informaรงรตes sobre o executor que estรก executando o trabalho atual. +completion.secret.githubToken=Token criado automaticamente para cada execuรงรฃo de fluxo de trabalho. +completion.remote.repository=Repositรณrio remoto +completion.uses.local.workflow=Fluxo de trabalho reutilizรกvel local +completion.uses.local.action=Aรงรฃo local +completion.uses.ref.known=Referรชncia de fluxo de trabalho conhecida +completion.uses.ref.remote=Referรชncia de fluxo de trabalho remoto +completion.uses.remote.known=Aรงรฃo remota conhecida ou fluxo de trabalho reutilizรกvel +completion.workflow.syntax=Sintaxe do fluxo de trabalho de aรงรตes GitHub +completion.workflow.top.name=Nome de exibiรงรฃo do fluxo de trabalho +completion.workflow.top.run-name=Nome da execuรงรฃo dinรขmica +completion.workflow.top.on=Eventos que iniciam o fluxo de trabalho +completion.workflow.top.permissions=Permissรตes GITHUB_TOKEN padrรฃo +completion.workflow.top.env=Variรกveis de ambiente em todo o fluxo de trabalho +completion.workflow.top.defaults=Configuraรงรตes padrรฃo de tarefa e etapa +completion.workflow.top.concurrency=Grupo de simultaneidade e cancelamento +completion.workflow.top.jobs=Jobs executados neste fluxo de trabalho +completion.workflow.event.branch_protection_rule=Proteรงรฃo de filial alterada +completion.workflow.event.check_run=Execuรงรฃo de verificaรงรฃo รบnica alterada +completion.workflow.event.check_suite=Verifique o conjunto alterado +completion.workflow.event.create=Ramificaรงรฃo ou tag criada +completion.workflow.event.delete=Filial ou tag excluรญda +completion.workflow.event.deployment=Implantaรงรฃo criada +completion.workflow.event.deployment_status=Status de implantaรงรฃo alterado +completion.workflow.event.discussion=Discussรฃo alterada +completion.workflow.event.discussion_comment=Comentรกrio da discussรฃo alterado +completion.workflow.event.fork=Repositรณrio bifurcado +completion.workflow.event.gollum=Pรกgina Wiki alterada +completion.workflow.event.image_version=Versรฃo da imagem do pacote alterada +completion.workflow.event.issue_comment=Problema ou comentรกrio PR alterado +completion.workflow.event.issues=Problema alterado +completion.workflow.event.label=Rรณtulo alterado +completion.workflow.event.merge_group=Verificaรงรฃo de fila de mesclagem solicitada +completion.workflow.event.milestone=Marco alterado +completion.workflow.event.page_build=A compilaรงรฃo das pรกginas foi executada +completion.workflow.event.project=Projeto clรกssico alterado +completion.workflow.event.project_card=Cartรฃo de projeto clรกssico alterado +completion.workflow.event.project_column=Coluna do projeto clรกssico alterada +completion.workflow.event.public=Repositรณrio tornou-se pรบblico +completion.workflow.event.pull_request=Solicitaรงรฃo pull alterada +completion.workflow.event.pull_request_review=Revisรฃo PR alterada +completion.workflow.event.pull_request_review_comment=Comentรกrio de revisรฃo PR alterado +completion.workflow.event.pull_request_target=Contexto de destino PR. Facas afiadas. +completion.workflow.event.push=Confirmaรงรฃo ou tag enviada +completion.workflow.event.registry_package=Pacote publicado ou atualizado +completion.workflow.event.release=Versรฃo alterada +completion.workflow.event.repository_dispatch=Evento de API personalizado +completion.workflow.event.schedule=Carrapato de Cron. Mecanismo de relรณgio. +completion.workflow.event.status=Status de confirmaรงรฃo alterado +completion.workflow.event.watch=Repositรณrio marcado com estrela +completion.workflow.event.workflow_call=Chamada de fluxo de trabalho reutilizรกvel +completion.workflow.event.workflow_dispatch=Botรฃo de execuรงรฃo manual +completion.workflow.event.workflow_run=Execuรงรฃo do fluxo de trabalho alterada +completion.workflow.eventFilter.types=Limitar tipos de atividades +completion.workflow.eventFilter.branches=Somente esses ramos +completion.workflow.eventFilter.branches-ignore=Pule esses ramos +completion.workflow.eventFilter.tags=Somente essas tags +completion.workflow.eventFilter.tags-ignore=Ignorar essas tags +completion.workflow.eventFilter.paths=Somente esses caminhos +completion.workflow.eventFilter.paths-ignore=Pule esses caminhos +completion.workflow.eventFilter.workflows=Nomes de fluxo de trabalho a serem observados +completion.workflow.eventFilter.cron=Cronograma Cron. Um pequeno relรณgio. +completion.workflow.permission.actions=Execuรงรตes de fluxo de trabalho e artefatos de aรงรฃo +completion.workflow.permission.artifact-metadata=Registros de metadados de artefato +completion.workflow.permission.attestations=Atestados de artefato +completion.workflow.permission.checks=Verifique execuรงรตes e suรญtes +completion.workflow.permission.code-quality=Relatรณrios de qualidade de cรณdigo +completion.workflow.permission.contents=Conteรบdo do repositรณrio +completion.workflow.permission.deployments=Implantaรงรตes +completion.workflow.permission.discussions=Discussรตes +completion.workflow.permission.id-token=Tokens OpenID Connect +completion.workflow.permission.issues=Problemas +completion.workflow.permission.models=Modelos GitHub +completion.workflow.permission.packages=Pacotes GitHub +completion.workflow.permission.pages=Pรกginas GitHub +completion.workflow.permission.pull-requests=Solicitaรงรตes pull +completion.workflow.permission.security-events=Verificaรงรฃo de cรณdigo e eventos de seguranรงa +completion.workflow.permission.statuses=Status de confirmaรงรฃo +completion.workflow.permission.vulnerability-alerts=Alertas Dependabot +completion.workflow.permission.value.read=Acesso de leitura +completion.workflow.permission.value.write=Acesso de gravaรงรฃo, leitura incluรญda +completion.workflow.permission.value.none=Sem acesso +completion.workflow.permission.shorthand.read-all=Todas as permissรตes lidas. Cobertor grande. +completion.workflow.permission.shorthand.write-all=Todas as permissรตes escrevem. Grande martelo. +completion.workflow.permission.shorthand.empty=Desabilitar permissรตes de token +completion.workflow.job.name=Nome de exibiรงรฃo do trabalho +completion.workflow.job.permissions=Permissรตes de token de trabalho +completion.workflow.job.needs=Empregos para esperar +completion.workflow.job.if=Condiรงรฃo de trabalho +completion.workflow.job.runs-on=Rรณtulo ou grupo do corredor +completion.workflow.job.snapshot=Instantรขneo do corredor +completion.workflow.job.environment=Ambiente de implantaรงรฃo +completion.workflow.job.concurrency=Bloqueio de simultaneidade de trabalho +completion.workflow.job.outputs=Saรญdas que outros trabalhos podem ler +completion.workflow.job.env=Variรกveis de ambiente de trabalho +completion.workflow.job.defaults=Configuraรงรตes padrรฃo do trabalho +completion.workflow.job.steps=Lista de etapas. O trabalho real. +completion.workflow.job.timeout-minutes=Tempo limite do trabalho em minutos +completion.workflow.job.strategy=Estratรฉgia matricial e de agendamento +completion.workflow.job.continue-on-error=Deixe este trabalho falhar suavemente +completion.workflow.job.container=Contรชiner para este trabalho +completion.workflow.job.services=Contรชineres de serviรงo sidecar +completion.workflow.job.uses=Fluxo de trabalho reutilizรกvel para ligar +completion.workflow.job.with=Entradas para fluxo de trabalho chamado +completion.workflow.job.secrets=Segredos para o fluxo de trabalho chamado +completion.workflow.defaultsRun.shell=Shell padrรฃo para etapas de execuรงรฃo +completion.workflow.defaultsRun.working-directory=Diretรณrio de trabalho padrรฃo +completion.workflow.concurrency.group=Bloquear nome para execuรงรตes na fila +completion.workflow.concurrency.cancel-in-progress=Cancelar execuรงรตes correspondentes mais antigas +completion.workflow.environment.name=Nome do ambiente +completion.workflow.environment.url=Ambiente URL +completion.workflow.strategy.matrix=Eixos matriciais e variantes +completion.workflow.strategy.fail-fast=Cancelar irmรฃos da matriz em caso de falha +completion.workflow.strategy.max-parallel=Limite de paralelismo de matriz +completion.workflow.matrix.include=Adicionar combinaรงรตes de matrizes +completion.workflow.matrix.exclude=Remover combinaรงรตes de matrizes +completion.workflow.step.id=ID da etapa para referรชncias +completion.workflow.step.if=Condiรงรฃo de etapa +completion.workflow.step.name=Nome de exibiรงรฃo da etapa +completion.workflow.step.uses=Aรงรฃo para executar +completion.workflow.step.run=Script de shell para executar +completion.workflow.step.shell=Shell para esta etapa de execuรงรฃo +completion.workflow.step.with=Entradas de aรงรฃo +completion.workflow.step.env=Variรกveis de ambiente de etapa +completion.workflow.step.continue-on-error=Deixe esta etapa falhar suavemente +completion.workflow.step.timeout-minutes=Tempo limite da etapa em minutos +completion.workflow.step.working-directory=Diretรณrio de trabalho da etapa +completion.workflow.container.image=Imagem do contรชiner +completion.workflow.container.credentials=Credenciais de registro +completion.workflow.container.env=Variรกveis de ambiente de contรชiner +completion.workflow.container.ports=Portas para expor +completion.workflow.container.volumes=Volumes para montar +completion.workflow.container.options=Opรงรตes de criaรงรฃo do Docker +completion.workflow.service.image=Imagem do contรชiner de serviรงo +completion.workflow.service.credentials=Credenciais de registro +completion.workflow.service.env=Variรกveis de ambiente de serviรงo +completion.workflow.service.ports=Portas de serviรงo +completion.workflow.service.volumes=Volumes de serviรงo +completion.workflow.service.options=Opรงรตes de criaรงรฃo do Docker +completion.workflow.credentials.username=Nome de usuรกrio do registro +completion.workflow.credentials.password=Senha ou token de registro +completion.workflow.inputType.string=Entrada de texto +completion.workflow.inputType.boolean=Entrada verdadeira ou falsa +completion.workflow.inputType.choice=Entrada de escolha suspensa +completion.workflow.inputType.number=Entrada de nรบmero +completion.workflow.inputType.environment=Entrada do seletor de ambiente +completion.workflow.boolean.true=Sim. Ligue-o. +completion.workflow.boolean.false=Nรฃo. Mantenha tudo escuro. +completion.workflow.runner.ubuntu-latest=Corredor Ubuntu mais recente +completion.workflow.runner.ubuntu-24.04=Corredor Ubuntu 24.04 +completion.workflow.runner.ubuntu-22.04=Corredor Ubuntu 22.04 +completion.workflow.runner.windows-latest=Corredor Windows mais recente +completion.workflow.runner.windows-2025=Executor do servidor Windows 2025 +completion.workflow.runner.windows-2022=Executor do servidor Windows 2022 +completion.workflow.runner.macos-latest=Corredor macOS mais recente +completion.workflow.runner.macos-15=Corredor macOS 15 +completion.workflow.runner.macos-14=Corredor macOS 14 +completion.workflow.runner.self-hosted=Seu prรณprio corredor. Seu circo. +completion.steps.outputs=O conjunto de saรญdas definidas para a etapa. +completion.steps.conclusion=O resultado de uma etapa concluรญda apรณs a aplicaรงรฃo de continuaรงรฃo em caso de erro. +completion.steps.outcome=O resultado de uma etapa concluรญda antes da aplicaรงรฃo do continue-on-error. +completion.jobs.outputs=O conjunto de saรญdas definidas para o trabalho. +completion.jobs.result=O resultado do trabalho. +settings.displayName=Fluxo de trabalho GitHub +settings.language.label=Idioma: +settings.language.system=IDE/padrรฃo do sistema +settings.cache.title=Cache de aรงรฃo +settings.cache.column.key=Chave de cache +settings.cache.column.name=Nome +settings.cache.column.kind=Gentil +settings.cache.column.state=Estado +settings.cache.column.expires=Expira +settings.cache.kind.local=locais +settings.cache.kind.remote=remoto +settings.cache.state.resolved=resolvido +settings.cache.state.pending=pendente +settings.cache.state.expired=obsoleto +settings.cache.state.suppressed=suprimido +settings.cache.refresh=Tabela Refresh +settings.cache.deleteSelected=Excluir selecionado +settings.cache.deleteAll=Excluir tudo +settings.cache.export=Exportar +settings.cache.import=Importar +settings.cache.summary=Cache: entradas {0}, {1} resolvido, {2} remoto, {3} obsoleto, {4} silenciado. Cache: {5} KB. +settings.cache.noneSelected=Selecione as linhas do cache primeiro. A vassoura recusa suposiรงรตes. +settings.cache.deleteSelected.done=Entradas de cache {0} excluรญdas. Pequena nuvem de poeira contida. +settings.cache.deleteAll.confirm=Excluir todas as entradas de cache do fluxo de trabalho GitHub? +settings.cache.deleteAll.done=Excluiu todas as entradas de cache. O cache agora estรก suspeitosamente silencioso. +settings.cache.export.done=Entradas de cache {0} exportadas. Pequena fera de arquivo enjaulada. +settings.cache.import.done=Entradas de cache importadas. A fera do arquivo se comportou. +settings.cache.import.unsupported=Arquivo de cache do fluxo de trabalho GitHub nรฃo suportado. +settings.cache.import.brokenLine=Linha de cache do fluxo de trabalho GitHub quebrada. +settings.cache.import.brokenKey=Chave de cache do fluxo de trabalho GitHub quebrada. +settings.support.button=Apoie este plugin +settings.support.tooltip=Abra a pรกgina de suporte +settings.support.line.0=Alimente o forno de construรงรฃo +settings.support.line.1=Compre o cafรฉ analisador +settings.support.line.2=Patrocine menos fluxos de trabalho assombrados +workflow.run.jobs.title=Trabalhos de fluxo de trabalho +workflow.run.jobs.root=Execuรงรฃo do fluxo de trabalho +workflow.run.jobs.description=รrvore de tarefas de aรงรตes GitHub e log de tarefas selecionado +workflow.run.tree.done=feito +workflow.run.tree.failed=falhou +workflow.run.tree.skipped=ignorado +workflow.run.tree.warn=avisar +workflow.run.tree.err=errar +workflow.run.delete.tooltip=Excluir execuรงรฃo +workflow.run.delete.noRun=Ainda nรฃo hรก ID de execuรงรฃo. +workflow.run.delete.requested=Excluindo a execuรงรฃo {0}. +workflow.run.delete.done=Execute {0} excluรญdo. +workflow.run.delete.http=Exclua HTTP {0}. Ufa. +workflow.run.delete.failed=Excluir fracassou: {0} +workflow.run.rerun.all.tooltip=Execute novamente o fluxo de trabalho +workflow.run.rerun.failed.tooltip=Execute novamente trabalhos com falha +workflow.run.rerun.noRun=Ainda nรฃo hรก ID de execuรงรฃo. +workflow.run.rerun.all.requested=Nova execuรงรฃo solicitada: {0}. +workflow.run.rerun.failed.requested=Trabalhos solicitados com falha: {0}. +workflow.run.rerun.all.done=Nova execuรงรฃo na fila: {0}. +workflow.run.rerun.failed.done=Trabalhos com falha na fila: {0}. +workflow.run.rerun.http=Execute novamente HTTP {0}. Ufa. +workflow.run.rerun.failed=Repetiรงรฃo fracassou: {0} +workflow.run.download.log.tooltip=Salvar registro de trabalho +workflow.run.download.artifacts.tooltip=Baixar artefatos +workflow.run.download.noRun=Ainda nรฃo hรก ID de execuรงรฃo. +workflow.run.download.log.requested=Buscando log para {0}. +workflow.run.download.log.done=Registro salvo: {0}. +workflow.run.download.artifacts.requested=Buscando artefatos. +workflow.run.download.artifacts.empty=Sem artefatos. Pequeno vazio. +workflow.run.download.artifact.expired=Artefato expirado: {0}. +workflow.run.download.artifact.done=Artefato salvo: {0} -> {1}. +workflow.run.download.failed=Download fracassou: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ru.properties b/src/main/resources/messages/GitHubWorkflowBundle_ru.properties new file mode 100644 index 0000000..4a4ca1d --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_ru.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub ะ ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +plugin.description=ะŸะพะดะดะตั€ะถะบะฐ ั„ะฐะนะปะพะฒ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ ะดะตะนัั‚ะฒะธะน GitHub. +group.GitHubWorkflow.Tools.text=GitHub ะ ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +group.GitHubWorkflow.Tools.description=GitHub ะ˜ะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะฟะปะฐะณะธะฝะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +action.GitHubWorkflow.RefreshActionCache.text=ะšััˆ ะดะตะนัั‚ะฒะธะน Refresh +action.GitHubWorkflow.RefreshActionCache.description=Refresh ั€ะฐะทั€ะตัˆะธะป ัƒะดะฐะปะตะฝะฝั‹ะต ะดะตะนัั‚ะฒะธั GitHub ะธ ะฟะพะฒั‚ะพั€ะฝะพ ะธัะฟะพะปัŒะทัƒะตะผั‹ะต ะผะตั‚ะฐะดะฐะฝะฝั‹ะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ. +action.GitHubWorkflow.RestoreActionWarnings.text=ะŸั€ะตะดัƒะฟั€ะตะถะดะตะฝะธั ะพ ะดะตะนัั‚ะฒะธัั… ะฟะพ ะฒะพััั‚ะฐะฝะพะฒะปะตะฝะธัŽ +action.GitHubWorkflow.RestoreActionWarnings.description=ะ’ะพััั‚ะฐะฝะพะฒะปะตะฝะธะต ะฟะพะดะฐะฒะปะตะฝะฝั‹ั… ะดะตะนัั‚ะฒะธะน, ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะน ะฟั€ะพะฒะตั€ะบะธ ะฒะฒะพะดะฐ ะธ ะฒั‹ะฒะพะดะฐ. +action.GitHubWorkflow.ClearActionCache.text=ะžั‡ะธัั‚ะธั‚ัŒ ะบััˆ ะดะตะนัั‚ะฒะธะน +action.GitHubWorkflow.ClearActionCache.description=ะžั‡ะธัั‚ะบะฐ ะบััˆะธั€ะพะฒะฐะฝะฝั‹ั… ะดะตะนัั‚ะฒะธะน GitHub ะธ ะฟะพะฒั‚ะพั€ะฝะพ ะธัะฟะพะปัŒะทัƒะตะผั‹ั… ะผะตั‚ะฐะดะฐะฝะฝั‹ั… ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ. +notification.cache.cleared=ะžั‡ะธั‰ะตะฝั‹ ะบััˆะธั€ะพะฒะฐะฝะฝั‹ะต ะทะฐะฟะธัะธ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ {0} GitHub. +notification.cache.refresh.started=Refreshing {0} ะบััˆะธั€ะพะฒะฐะป ัƒะดะฐะปะตะฝะฝั‹ะต ะทะฐะฟะธัะธ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ GitHub. +notification.warnings.restored=ะ’ะพััั‚ะฐะฝะพะฒะปะตะฝั‹ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธั ะดะปั ะทะฐะฟะธัะตะน ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ {0} GitHub. +workflow.run.configuration.display=GitHub ะ ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +workflow.run.configuration.description=ะžั‚ะฟั€ะฐะฒะบะฐ ะธ ะพั‚ัะปะตะถะธะฒะฐะฝะธะต ะฒั‹ะฟะพะปะฝะตะฝะธั ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ ะดะตะนัั‚ะฒะธะน GitHub. +workflow.run.configuration.name=GitHub ะ ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=ะ’ะปะฐะดะตะปะตั† +workflow.run.field.repo=ะ ะตะฟะพะทะธั‚ะพั€ะธะน +workflow.run.field.workflow=ะคะฐะนะป ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=ะ ะตะทะตั€ะฒะฝั‹ะน ะฒะฐั€ะธะฐะฝั‚ ั‚ะพะบะตะฝะฐ env var +workflow.run.inputs.title=ะ’ั…ะพะดั‹ workflow_dispatch (ะบะปัŽั‡=ะทะฝะฐั‡ะตะฝะธะต) +workflow.run.error.apiUrl=GitHub ะขั€ะตะฑัƒะตั‚ัั API URL. +workflow.run.error.repository=ะขั€ะตะฑัƒะตั‚ัั ะฒะปะฐะดะตะปะตั† ะธ ะธะผั ั€ะตะฟะพะทะธั‚ะพั€ะธั GitHub. +workflow.run.error.workflow=ะขั€ะตะฑัƒะตั‚ัั ั„ะฐะนะป ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ. +workflow.run.error.ref=ะขั€ะตะฑัƒะตั‚ัั ััั‹ะปะบะฐ ะฝะฐ ะฒะตั‚ะบัƒ ะธะปะธ ั‚ะตะณ. +workflow.run.error.inputs=GitHub workflow_dispatch ะฟะพะดะดะตั€ะถะธะฒะฐะตั‚ ะผะฐะบัะธะผัƒะผ 25 ะฒั…ะพะดะพะฒ. +workflow.run.gutter.stop=ะžัั‚ะฐะฝะพะฒะธั‚ัŒ ะฒั‹ะฟะพะปะฝะตะฝะธะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +workflow.run.gutter.stop.text=ะžัั‚ะฐะฝะพะฒะธั‚ัŒ ะฒั‹ะฟะพะปะฝะตะฝะธะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +workflow.run.gutter.stop.description=ะžั‚ะผะตะฝะธั‚ัŒ ัั‚ะพั‚ ะทะฐะฟัƒัะบ +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=ะทะฐะฟัƒัั‚ะธั‚ัŒ: +workflow.log.warning=ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต: +workflow.log.error=ะพัˆะธะฑะบะฐ: +workflow.run.cancel.requested=ะžั‚ะผะตะฝะธั‚ัŒ ะทะฐะฟั€ะพั: {0}. +workflow.run.stop.before.id=ะกั‚ะพะฟ ะฟั€ะพัะธะปะธ. ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบะฐ ะฟะพะบะฐ ะฝะตั‚. +workflow.run.cancel.http=ะžั‚ะผะตะฝะธั‚ัŒ HTTP {0}. ะฃั„. +workflow.run.cancel.failed=ะžั‚ะผะตะฝะฐ ะฝะต ัƒะดะฐะปะฐััŒ: {0} +workflow.run.interrupted=ะŸั€ะตั€ะฒะฐะฝะพ. +workflow.run.link=ะ—ะฐะฟัƒัะบ: {0} +workflow.run.discovery=ะŸั€ะพะฑะตะณ ะฟั€ะธะฝัั‚. ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะพั…ะพั‚ะฝะธั‡ัŒะตะณะพ ะทะฐะฑะตะณะฐ. +workflow.run.discovery.none=ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบะฐ ะฟะพะบะฐ ะฝะตั‚. ะ’ะบะปะฐะดะบะฐ ยซะ”ะตะนัั‚ะฒะธัยป ะทะฝะฐะตั‚ ะฑะพะปัŒัˆะต. +workflow.run.status=ะกั‚ะฐั‚ัƒั: {0}{1} +workflow.run.job.main=ะ”ะพะปะถะฝะพัั‚ัŒ: {0} {1} [{2}{3}{4}] +workflow.run.job.status=ะกั‚ะฐั‚ัƒั: {0} {1}{2}{3} +workflow.run.logs.later=ะ–ัƒั€ะฝะฐะปั‹ ะฟะพัะฒัั‚ัั, ะบะพะณะดะฐ GitHub ะพะฟัƒะฑะปะธะบัƒะตั‚ ะธั…. +workflow.run.job.logs.later=ะ’ะฐะบะฐะฝัะธั {0}: {1} +workflow.run.log.failed=ะะต ัƒะดะฐะปะพััŒ ะทะฐะณั€ัƒะทะธั‚ัŒ ะถัƒั€ะฝะฐะป: {0}. +workflow.run.log.failed.job=ะะต ัƒะดะฐะปะพััŒ ะทะฐะณั€ัƒะทะธั‚ัŒ ะถัƒั€ะฝะฐะป ะดะปั {0}: {1}. +workflow.run.job.url=URL: {0} +workflow.run.job.header=ะ ะฐะฑะพั‚ะฐ: {0} +workflow.run.job.fallbackName=ะ’ะฐะบะฐะฝัะธั {0} +workflow.run.overview=ะ—ะฐะฟัƒัะบ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ {0} {1}/{2} ะฒั‹ะฟะพะปะฝะตะฝ, {3} ะทะฐะฟัƒั‰ะตะฝ +workflow.run.state.ok=[ะžะš] +workflow.run.state.fail=[ะะ•ะฃะ”ะะงะ] +workflow.run.state.running=[ะ‘ะ•ะ“] +workflow.run.state.waiting=[ะ–ะ”ะะขะฌ] +workflow.run.dispatch.verbs=ะ—ะฐะณั€ัƒะทะบะฐ|ะŸะพัั‚ะฐะฝะพะฒะบะฐ ะฒ ะพั‡ะตั€ะตะดัŒ|ะ’ั‹ะทะพะฒ|ะ—ะฐะฟัƒัะบ|ะ—ะฐะณั€ัƒะทะบะฐ +workflow.run.dispatch.objects=ั€ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั|ะฐะฒั‚ะพะผะฐั‚ะธะทะฐั†ะธั|ะบะพะฝะฒะตะนะตั€|ะทะฐะฟัƒัะบ +workflow.run.dispatch={0} {1} {2}{3} ะดะปั {4} ะฝะฐ {5}. +workflow.run.notification.auth=ะ”ะปั ะพั‚ะฟั€ะฐะฒะบะธ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ GitHub ั‚ั€ะตะฑัƒะตั‚ัั ะฐัƒั‚ะตะฝั‚ะธั„ะธั†ะธั€ะพะฒะฐะฝะฝะฐั ัƒั‡ะตั‚ะฝะฐั ะทะฐะฟะธััŒ GitHub. ะ”ะพะฑะฐะฒัŒั‚ะต ะธะปะธ ะพะฑะฝะพะฒะธั‚ะต ัƒั‡ะตั‚ะฝั‹ะต ะทะฐะฟะธัะธ ะฒ {0}. +workflow.run.notification.openSettings=ะžั‚ะบั€ะพะนั‚ะต ะฝะฐัั‚ั€ะพะนะบะธ GitHub. +workflow.cache.progress.title=ะ ะฐะทั€ะตัˆะตะฝะธะต ะดะตะนัั‚ะฒะธะน GitHub +workflow.cache.progress.text=ะ ะฐะทั€ะตัˆะตะฝะธะต {0} {1} +workflow.cache.kind.action=ะดะตะนัั‚ะฒะธะต +workflow.cache.kind.workflow=ั€ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +inspection.parameter.input=ะฒะฒะพะด +inspection.parameter.secret=ัะตะบั€ะตั‚ +inspection.action.delete.invalid=ะฃะดะฐะปะธั‚ัŒ ะฝะตะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ะน {0} [{1}] +inspection.action.update.major=ะžะฑะฝะพะฒะธั‚ัŒ ะดะตะนัั‚ะฒะธะต [{0}] ะฝะฐ [{1}] +inspection.warning.toggle=ะŸะตั€ะตะบะปัŽั‡ะธั‚ัŒ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธั [{0}] ะดะปั [{1}] +inspection.warning.on=ะฝะฐ +inspection.warning.off=ะฒั‹ะบะป. +inspection.statement.incomplete=ะะตะฟะพะปะฝะพะต ะทะฐัะฒะปะตะฝะธะต [{0}] +inspection.invalid.suffix.remove=ะฃะดะฐะปะธั‚ัŒ ะฝะตะฒะตั€ะฝั‹ะน ััƒั„ั„ะธะบั [{0}] +inspection.replace.with=ะ—ะฐะผะตะฝะธั‚ัŒ ะฝะฐ [{0}] +inspection.invalid.remove=ะฃะดะฐะปะธั‚ัŒ ะฝะตะดะตะนัั‚ะฒะธั‚ะตะปัŒะฝั‹ะน [{0}] +inspection.workflow.syntax.unknownTopLevelKey=ะะตะธะทะฒะตัั‚ะฝั‹ะน ะบะปัŽั‡ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ [{0}] +inspection.workflow.syntax.unknownEventKey=ะะตะธะทะฒะตัั‚ะฝะพะต ัะพะฑั‹ั‚ะธะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ [{0}] +inspection.workflow.syntax.unknownTriggerKey=ะะตะธะทะฒะตัั‚ะฝะฐั ะบะปะฐะฒะธัˆะฐ ั‚ั€ะธะณะณะตั€ะฐ [{0}] +inspection.workflow.syntax.unknownTriggerFilter=ะะตะธะทะฒะตัั‚ะฝั‹ะน ั‚ั€ะธะณะณะตั€ะฝั‹ะน ั„ะธะปัŒั‚ั€ [{0}] +inspection.workflow.syntax.unknownTriggerValue=ะะตะธะทะฒะตัั‚ะฝะพะต ะทะฝะฐั‡ะตะฝะธะต ั‚ั€ะธะณะณะตั€ะฐ [{0}] +inspection.workflow.syntax.unknownPermission=ะะตะธะทะฒะตัั‚ะฝะพะต ั€ะฐะทั€ะตัˆะตะฝะธะต [{0}] +inspection.workflow.syntax.unknownPermissionValue=ะะตะธะทะฒะตัั‚ะฝะพะต ะทะฝะฐั‡ะตะฝะธะต ั€ะฐะทั€ะตัˆะตะฝะธั [{0}] +inspection.workflow.syntax.unknownJobKey=ะะตะธะทะฒะตัั‚ะฝั‹ะน ะบะปัŽั‡ ะทะฐะดะฐะฝะธั [{0}] +inspection.workflow.syntax.unknownStepKey=ะะตะธะทะฒะตัั‚ะฝะฐั ะบะปะฐะฒะธัˆะฐ ัˆะฐะณะฐ [{0}] +inspection.action.reload=ะŸะตั€ะตะทะฐะณั€ัƒะทะธั‚ัŒ [{0}] +inspection.action.unresolved=ะะตั€ะฐะทั€ะตัˆะตะฝะพ [{0}] โ€” ะฟั€ะพะฒะตั€ัŒั‚ะต ะดะพัั‚ัƒะฟ ะบ ัƒั‡ะตั‚ะฝะพะน ะทะฐะฟะธัะธ GitHub, ั€ะฐะทั€ะตัˆะตะฝะธั ั‡ะฐัั‚ะฝะพะณะพ ั€ะตะฟะพะทะธั‚ะพั€ะธั, ะพะณั€ะฐะฝะธั‡ะตะฝะธั ัะบะพั€ะพัั‚ะธ, ะพั‚ััƒั‚ัั‚ะฒัƒัŽั‰ะธะต ััั‹ะปะบะธ ะธะปะธ ะพั‚ััƒั‚ัั‚ะฒัƒัŽั‰ะธะต ะผะตั‚ะฐะดะฐะฝะฝั‹ะต ะดะตะนัั‚ะฒะธะน/ั€ะฐะฑะพั‡ะธั… ะฟั€ะพั†ะตััะพะฒ. +inspection.action.jump=ะŸะตั€ะตะนั‚ะธ ะบ ั„ะฐะนะปัƒ [{0}] +inspection.output.unused=ะะต ะธัะฟะพะปัŒะทัƒะตั‚ัั [{0}] +inspection.secret.invalid.if=ะฃะดะฐะปะธั‚ัŒ [{0}] โ€” ัะตะบั€ะตั‚ั‹ ะฝะตะดะพะฟัƒัั‚ะธะผั‹ ะฒ ะพะฟะตั€ะฐั‚ะพั€ะฐั… `if`. +inspection.secret.replace.runtime=ะ—ะฐะผะตะฝะธั‚ะต [{0}] ะฝะฐ [{1}] โ€” ะตัะปะธ ะพะฝ ะฝะต ัƒะบะฐะทะฐะฝ ะฒะพ ะฒั€ะตะผั ะฒั‹ะฟะพะปะฝะตะฝะธั. +inspection.needs.invalid.job=ะฃะดะฐะปะธั‚ัŒ ะฝะตะฒะตั€ะฝั‹ะน ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะทะฐะดะฐะฝะธั [{0}] โ€” ัั‚ะพั‚ ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะทะฐะดะฐะฝะธั ะฝะต ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒะตั‚ ะฝะธ ะพะดะฝะพะผัƒ ะฟั€ะตะดั‹ะดัƒั‰ะตะผัƒ ะทะฐะดะฐะฝะธัŽ. +documentation.description=ะžะฟะธัะฐะฝะธะต: {0} +documentation.type=ะขะธะฟ: {0} +documentation.required=ะขั€ะตะฑัƒะตั‚ัั: {0} +documentation.default=ะŸะพ ัƒะผะพะปั‡ะฐะฝะธัŽ: {0} +documentation.deprecated=ะฃัั‚ะฐั€ะตะปะพ: {0} +documentation.open.declaration=ะžั‚ะบั€ั‹ั‚ะฐั ะดะตะบะปะฐั€ะฐั†ะธั ({0}) +documentation.input.label=ะ’ะฒะพะด +documentation.secret.label=ะกะตะบั€ะตั‚ +documentation.env.label=ะŸะตั€ะตะผะตะฝะฝะฐั ัั€ะตะดั‹ +documentation.matrix.label=ะœะฐั‚ั€ะธั‡ะฝะพะต ัะฒะพะนัั‚ะฒะพ +documentation.need.label=ะัƒะถะฝะฐ ั€ะฐะฑะพั‚ะฐ +documentation.need.description=ะŸั€ัะผะฐั ะทะฐะฒะธัะธะผะพัั‚ัŒ ะพั‚ ั€ะฐะฑะพั‚ั‹. +documentation.needOutput.label=ะะตะพะฑั…ะพะดะธะผั‹ะน ั€ะตะทัƒะปัŒั‚ะฐั‚ ั€ะฐะฑะพั‚ั‹ +documentation.reusableJob.label=ะœะฝะพะณะพั€ะฐะทะพะฒะพะต ะทะฐะดะฐะฝะธะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +documentation.reusableJob.description=ะ—ะฐะดะฐะฝะธะต ะพะฑัŠัะฒะปะตะฝะพ ะฒ ัั‚ะพะผ ะผะฝะพะณะพะบั€ะฐั‚ะฝะพ ะธัะฟะพะปัŒะทัƒะตะผะพะผ ั€ะฐะฑะพั‡ะตะผ ะฟั€ะพั†ะตััะต. +documentation.reusableJobOutput.label=ะœะฝะพะณะพั€ะฐะทะพะฒั‹ะต ะฒั‹ั…ะพะดะฝั‹ะต ะดะฐะฝะฝั‹ะต ะทะฐะดะฐะฝะธั ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +documentation.service.label=ะกะตั€ะฒะธัะฝั‹ะน ะบะพะฝั‚ะตะนะฝะตั€ +documentation.servicePort.label=ะกะตั€ะฒะธัะฝั‹ะน ะฟะพั€ั‚ +documentation.container.label=ะšะพะฝั‚ะตะนะฝะตั€ ะทะฐะดะฐะฝะธะน +documentation.symbol.label=ะกะธะผะฒะพะป ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +documentation.symbol.description=ะ ะฐะทั€ะตัˆะตะฝะพ ะฒั‹ั€ะฐะถะตะฝะธะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ. +documentation.workflowOutput.label=ะ’ั‹ั…ะพะดะฝั‹ะต ะดะฐะฝะฝั‹ะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +documentation.jobOutput.label=ะ’ั‹ะฒะพะด ะทะฐะดะฐะฝะธั +documentation.action.label=ะ”ะตะนัั‚ะฒะธะต +documentation.externalAction.label=ะ’ะฝะตัˆะฝะตะต ะดะตะนัั‚ะฒะธะต +documentation.reusableWorkflow.label=ะœะฝะพะณะพั€ะฐะทะพะฒั‹ะน ั€ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +documentation.resolvedFrom=ั€ะตัˆะตะฝะพ ะธะท {0} +documentation.notResolved=ะตั‰ะต ะฝะต ั€ะตัˆะตะฝะพ +documentation.inputs.title=ะ’ั…ะพะดั‹ +documentation.outputs.title=ะ’ั‹ั…ะพะดั‹ +documentation.secrets.title=ะกะตะบั€ะตั‚ั‹ +documentation.value.label=ะ—ะฝะฐั‡ะตะฝะธะต +documentation.step.title=ะจะฐะณ {0} +documentation.name.label=ะ˜ะผั +documentation.uses.label=ะ˜ัะฟะพะปัŒะทะพะฒะฐะฝะธะต +documentation.run.label=ะ‘ะตะณะธ +documentation.description.label=ะžะฟะธัะฐะฝะธะต +documentation.step.label=ะจะฐะณ +documentation.source.label=ะ˜ัั‚ะพั‡ะฝะธะบ +documentation.stepOutput.label=ะจะฐะณ ะฒั‹ะฒะพะดะฐ +documentation.context.github=ะบะพะฝั‚ะตะบัั‚ GitHub +documentation.context.github.description=ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ั‚ะตะบัƒั‰ะตะผ ะฒั‹ะฟะพะปะฝะตะฝะธะธ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ ะธ ัะพะฑั‹ั‚ะธะธ. +documentation.context.gitea=ะบะพะฝั‚ะตะบัั‚ ะณะธั‚ะตะธ +documentation.context.gitea.description=ะกะพะฒะผะตัั‚ะธะผั‹ะน ั Gitea ะฟัะตะฒะดะพะฝะธะผ ะดะปั ะบะพะฝั‚ะตะบัั‚ะฐ ะดะตะนัั‚ะฒะธะน GitHub. +documentation.context.inputs=ะบะพะฝั‚ะตะบัั‚ ะฒะฒะพะดะฐ +documentation.context.inputs.description=ะ—ะดะตััŒ ะดะพัั‚ัƒะฟะฝั‹ ะฒั…ะพะดะฝั‹ะต ะดะฐะฝะฝั‹ะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ, ะดะธัะฟะตั‚ั‡ะตั€ะธะทะฐั†ะธะธ ะธะปะธ ะดะตะนัั‚ะฒะธั. +documentation.context.secrets=ะบะพะฝั‚ะตะบัั‚ ัะตะบั€ะตั‚ะพะฒ +documentation.context.secrets.description=ะกะตะบั€ะตั‚ะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั, ะดะพัั‚ัƒะฟะฝั‹ะต ะดะปั ัั‚ะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ ะธะปะธ ะผะฝะพะณะพะบั€ะฐั‚ะฝะพ ะธัะฟะพะปัŒะทัƒะตะผะพะณะพ ะฒั‹ะทะพะฒะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ. +documentation.context.env=ะบะพะฝั‚ะตะบัั‚ ะพะบั€ัƒะถะตะฝะธั +documentation.context.env.description=ะŸะตั€ะตะผะตะฝะฝั‹ะต ัั€ะตะดั‹, ะฒะธะดะธะผั‹ะต ะฒ ัั‚ะพะผ ะผะตัั‚ะต. +documentation.context.matrix=ะผะฐั‚ั€ะธั‡ะฝั‹ะน ะบะพะฝั‚ะตะบัั‚ +documentation.context.matrix.description=ะ—ะฝะฐั‡ะตะฝะธั ะผะฐั‚ั€ะธั†ั‹ ะดะปั ั‚ะตะบัƒั‰ะตะณะพ ะทะฐะดะฐะฝะธั. +documentation.context.steps=ะบะพะฝั‚ะตะบัั‚ ัˆะฐะณะพะฒ +documentation.context.steps.description=ะŸั€ะตะดั‹ะดัƒั‰ะธะต ัˆะฐะณะธ ั‚ะตะบัƒั‰ะตะณะพ ะทะฐะดะฐะฝะธั, ะฒะบะปัŽั‡ะฐั ั€ะตะทัƒะปัŒั‚ะฐั‚ั‹ ะธ ัั‚ะฐั‚ัƒั. +documentation.context.needs=ะฝัƒะถะตะฝ ะบะพะฝั‚ะตะบัั‚ +documentation.context.needs.description=ะŸั€ัะผั‹ะต ะทะฐะฒะธัะธะผะพัั‚ะธ ะพั‚ ั€ะฐะฑะพั‚ั‹ ะธ ะธั… ั€ะตะทัƒะปัŒั‚ะฐั‚ั‹/ั€ะตะทัƒะปัŒั‚ะฐั‚ั‹. +documentation.context.jobs=ะบะพะฝั‚ะตะบัั‚ ั€ะฐะฑะพั‚ั‹ +documentation.context.jobs.description=ะœะฝะพะณะพั€ะฐะทะพะฒั‹ะต ะทะฐะดะฐะฝะธั ะธ ั€ะตะทัƒะปัŒั‚ะฐั‚ั‹ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ. +documentation.context.outputs=ั€ะตะทัƒะปัŒั‚ะฐั‚ั‹ +documentation.context.outputs.description=ะ’ั‹ั…ะพะดะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั, ะฟั€ะตะดะพัั‚ะฐะฒะปัะตะผั‹ะต ัั‚ะธะผ ัˆะฐะณะพะผ ะธะปะธ ะทะฐะดะฐะฝะธะตะผ. +documentation.context.result=ั€ะตะทัƒะปัŒั‚ะฐั‚ +documentation.context.result.description=ะ ะตะทัƒะปัŒั‚ะฐั‚ ะทะฐะดะฐะฝะธั: ัƒัะฟะตัˆะฝะพะต, ะฝะตัƒะดะฐั‡ะฝะพะต, ะพั‚ะผะตะฝะตะฝะฝะพะต ะธะปะธ ะฟั€ะพะฟัƒั‰ะตะฝะฝะพะต. +documentation.context.outcome=ั€ะตะทัƒะปัŒั‚ะฐั‚ +documentation.context.outcome.description=ะ ะตะทัƒะปัŒั‚ะฐั‚ ัˆะฐะณะฐ ะดะพ ะฟั€ะธะผะตะฝะตะฝะธั ะฟั€ะพะดะพะปะถะตะฝะธั ะฟั€ะธ ะพัˆะธะฑะบะต. +documentation.context.conclusion=ะทะฐะบะปัŽั‡ะตะฝะธะต +documentation.context.conclusion.description=ะ ะตะทัƒะปัŒั‚ะฐั‚ ัˆะฐะณะฐ ะฟะพัะปะต ะฟั€ะธะผะตะฝะตะฝะธั ะฟั€ะพะดะพะปะถะตะฝะธั ะฟั€ะธ ะพัˆะธะฑะบะต. +error.report.action=ะกะพะพะฑั‰ะธั‚ัŒ ะพะฑ ะธัะบะปัŽั‡ะตะฝะธะธ +error.report.description=ะžะฟะธัะฐะฝะธะต +error.report.steps=ะจะฐะณะธ ะฟะพ ะฒะพัะฟั€ะพะธะทะฒะตะดะตะฝะธัŽ +error.report.sample=ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฟั€ะตะดะพัั‚ะฐะฒัŒั‚ะต ะพะฑั€ะฐะทะตั† ะบะพะดะฐ, ะตัะปะธ ะฟั€ะธะผะตะฝะธะผะพ +error.report.message=ะกะพะพะฑั‰ะตะฝะธะต +error.report.runtime=ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฒั€ะตะผะตะฝะธ ะฒั‹ะฟะพะปะฝะตะฝะธั +error.report.pluginVersion=ะ’ะตั€ัะธั ะฟะปะฐะณะธะฝะฐ: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=ะขั€ะฐััะธั€ะพะฒะบะฐ ัั‚ะตะบะฐ +completion.shell.bash=ะžะฑะพะปะพั‡ะบะฐ Bash. ะ˜ัะฟะพะปัŒะทัƒะตั‚ bash ะฝะฐ ะฑะตะณัƒะฝะฐั… Linux ะธ macOS ะธ Git ะดะปั Windows bash ะฝะฐ ะฑะตะณัƒะฝะฐั… Windows. +completion.shell.sh=ะ ะตะทะตั€ะฒะฝั‹ะน ะฒะฐั€ะธะฐะฝั‚ ะพะฑะพะปะพั‡ะบะธ POSIX. +completion.shell.pwsh=ะฏะดั€ะพ PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=ะšะพะผะฐะฝะดะฝะฐั ัั‚ั€ะพะบะฐ Windows. +completion.shell.python=ะ’ั‹ะฟะพะปะฝะธั‚ะตะปัŒ ะบะพะผะฐะฝะด Python. +completion.runner.name=ะ˜ะผั ะฑะตะณัƒะฝะฐ, ะฒั‹ะฟะพะปะฝััŽั‰ะตะณะพ ะทะฐะดะฐะฝะธะต. +completion.runner.os=ะžะฟะตั€ะฐั†ะธะพะฝะฝะฐั ัะธัั‚ะตะผะฐ ะฑะตะณัƒะฝะฐ, ะฒั‹ะฟะพะปะฝััŽั‰ะตะณะพ ะทะฐะดะฐะฝะธะต. ะ’ะพะทะผะพะถะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั: Linux, Windows ะธะปะธ macOS. +completion.runner.arch=ะั€ั…ะธั‚ะตะบั‚ัƒั€ะฐ ะฑะตะณัƒะฝะฐ, ะฒั‹ะฟะพะปะฝััŽั‰ะตะณะพ ะทะฐะดะฐะฝะธะต. ะ’ะพะทะผะพะถะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั: X86, X64, ARM ะธะปะธ ARM64. +completion.runner.temp=ะŸัƒั‚ัŒ ะบ ะฒั€ะตะผะตะฝะฝะพะผัƒ ะบะฐั‚ะฐะปะพะณัƒ ะฝะฐ ะฑะตะณัƒะฝะต. ะญั‚ะพั‚ ะบะฐั‚ะฐะปะพะณ ะพั‡ะธั‰ะฐะตั‚ัั ะฒ ะฝะฐั‡ะฐะปะต ะธ ะฒ ะบะพะฝั†ะต ะบะฐะถะดะพะณะพ ะทะฐะดะฐะฝะธั. ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต, ั‡ั‚ะพ ั„ะฐะนะปั‹ ะฝะต ะฑัƒะดัƒั‚ ัƒะดะฐะปะตะฝั‹, ะตัะปะธ ัƒ ัƒั‡ะตั‚ะฝะพะน ะทะฐะฟะธัะธ ะฑะตะณัƒะฝะฐ ะฝะตั‚ ั€ะฐะทั€ะตัˆะตะฝะธั ะฝะฐ ะธั… ัƒะดะฐะปะตะฝะธะต. +completion.runner.toolCache=ะŸัƒั‚ัŒ ะบ ะบะฐั‚ะฐะปะพะณัƒ, ัะพะดะตั€ะถะฐั‰ะตะผัƒ ะฟั€ะตะดัƒัั‚ะฐะฝะพะฒะปะตะฝะฝั‹ะต ะธะฝัั‚ั€ัƒะผะตะฝั‚ั‹ ะดะปั ะฑะตะณัƒะฝะพะฒ, ั€ะฐะทะผะตั‰ะตะฝะฝั‹ั… ะฝะฐ GitHub. +completion.runner.debug=ะญั‚ะพ ะทะฝะฐั‡ะตะฝะธะต ัƒัั‚ะฐะฝะฐะฒะปะธะฒะฐะตั‚ัั ั‚ะพะปัŒะบะพ ะฒ ั‚ะพะผ ัะปัƒั‡ะฐะต, ะตัะปะธ ะฒะบะปัŽั‡ะตะฝะพ ะฒะตะดะตะฝะธะต ะถัƒั€ะฝะฐะปะฐ ะพั‚ะปะฐะดะบะธ, ะธ ะฒัะตะณะดะฐ ะธะผะตะตั‚ ะทะฝะฐั‡ะตะฝะธะต 1. +completion.runner.environment=ะกั€ะตะดะฐ ะฑะตะณัƒะฝะฐ, ะฒั‹ะฟะพะปะฝััŽั‰ะตะณะพ ะทะฐะดะฐะฝะธะต. ะ’ะพะทะผะพะถะฝั‹ะต ะทะฝะฐั‡ะตะฝะธั: ั€ะฐะทะผะตั‰ะตะฝะธะต ะฝะฐ GitHub ะธะปะธ ัะฐะผะพัั‚ะพัั‚ะตะปัŒะฝะพะต ั€ะฐะทะผะตั‰ะตะฝะธะต. +completion.job.status=ะขะตะบัƒั‰ะธะน ัั‚ะฐั‚ัƒั ั€ะฐะฑะพั‚ั‹. +completion.job.checkRunId=ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะฟั€ะพะฒะตั€ะบะธ ั‚ะตะบัƒั‰ะตะณะพ ะทะฐะดะฐะฝะธั. +completion.job.container=ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะบะพะฝั‚ะตะนะฝะตั€ะต ะทะฐะดะฐะฝะธั. +completion.job.services=ะšะพะฝั‚ะตะนะฝะตั€ั‹ ัะปัƒะถะฑ, ัะพะทะดะฐะฝะฝั‹ะต ะดะปั ะทะฐะดะฐะฝะธั. +completion.job.workflowRef=ะŸะพะปะฝะฐั ััั‹ะปะบะฐ ะฝะฐ ั„ะฐะนะป ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ, ะพะฟั€ะตะดะตะปััŽั‰ะธะน ั‚ะตะบัƒั‰ะตะต ะทะฐะดะฐะฝะธะต. +completion.job.workflowSha=ะคะธะบัะฐั†ะธั SHA ั„ะฐะนะปะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ, ะพะฟั€ะตะดะตะปััŽั‰ะตะณะพ ั‚ะตะบัƒั‰ะตะต ะทะฐะดะฐะฝะธะต. +completion.job.workflowRepository=ะ’ะปะฐะดะตะปะตั†/ั€ะตะฟะพะทะธั‚ะพั€ะธะน ั€ะตะฟะพะทะธั‚ะพั€ะธั, ัะพะดะตั€ะถะฐั‰ะตะณะพ ั„ะฐะนะป ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ, ะพะฟั€ะตะดะตะปััŽั‰ะธะน ั‚ะตะบัƒั‰ะตะต ะทะฐะดะฐะฝะธะต. +completion.job.workflowFilePath=ะŸัƒั‚ัŒ ะบ ั„ะฐะนะปัƒ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ ะพั‚ะฝะพัะธั‚ะตะปัŒะฝะพ ะบะพั€ะฝั ั€ะตะฟะพะทะธั‚ะพั€ะธั. +completion.job.containerField=ะŸะพะปะต ะบะพะฝั‚ะตะนะฝะตั€ะฐ ะทะฐะดะฐะฝะธั +completion.job.service=ะกะปัƒะถะฑะฐ ั€ะฐะฑะพั‚ั‹ +completion.job.serviceField=ะกั„ะตั€ะฐ ัƒัะปัƒะณ ะฟะพ ั‚ั€ัƒะดะพัƒัั‚ั€ะพะนัั‚ะฒัƒ +completion.job.mappedServicePort=ะกะพะฟะพัั‚ะฐะฒะปะตะฝะฝั‹ะน ัะตั€ะฒะธัะฝั‹ะน ะฟะพั€ั‚ +completion.strategy.failFast=ะ‘ัƒะดัƒั‚ ะปะธ ะพั‚ะผะตะฝะตะฝั‹ ะฒัะต ะฒั‹ะฟะพะปะฝััŽั‰ะธะตัั ะทะฐะดะฐะฝะธั ะฒ ัะปัƒั‡ะฐะต ัะฑะพั ะบะฐะบะพะณะพ-ะปะธะฑะพ ะผะฐั‚ั€ะธั‡ะฝะพะณะพ ะทะฐะดะฐะฝะธั. +completion.strategy.jobIndex=ะžั‚ัั‡ะธั‚ั‹ะฒะฐะตะผั‹ะน ะพั‚ ะฝัƒะปั ะธะฝะดะตะบั ั‚ะตะบัƒั‰ะตะณะพ ะทะฐะดะฐะฝะธั ะฒ ะผะฐั‚ั€ะธั†ะต. +completion.strategy.jobTotal=ะžะฑั‰ะตะต ะบะพะปะธั‡ะตัั‚ะฒะพ ั€ะฐะฑะพั‡ะธั… ะผะตัั‚ ะฒ ะผะฐั‚ั€ะธั†ะต. +completion.strategy.maxParallel=ะœะฐะบัะธะผะฐะปัŒะฝะพะต ะบะพะปะธั‡ะตัั‚ะฒะพ ะผะฐั‚ั€ะธั‡ะฝั‹ั… ะทะฐะดะฐะฝะธะน, ะบะพั‚ะพั€ั‹ะต ะผะพะณัƒั‚ ะฒั‹ะฟะพะปะฝัั‚ัŒัั ะพะดะฝะพะฒั€ะตะผะตะฝะฝะพ. +completion.context.inputs=ะ’ั…ะพะดะฝั‹ะต ะดะฐะฝะฝั‹ะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ, ั‚ะฐะบะธะต ะบะฐะบ workflow_dispatch ะธะปะธ workflow_call. +completion.context.secrets=ะกะตะบั€ะตั‚ั‹ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ. +completion.context.job=ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ั‚ะตะบัƒั‰ะตะผ ะฒั‹ะฟะพะปะฝัะตะผะพะผ ะทะฐะดะฐะฝะธะธ. +completion.context.jobs=ะ ะฐะฑะพั‡ะธะต ะผะตัั‚ะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ. +completion.context.matrix=ะกะฒะพะนัั‚ะฒะฐ ะผะฐั‚ั€ะธั†ั‹, ะพะฟั€ะตะดะตะปะตะฝะฝั‹ะต ะดะปั ั‚ะตะบัƒั‰ะตะณะพ ะทะฐะดะฐะฝะธั ะผะฐั‚ั€ะธั†ั‹. +completion.context.strategy=ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ัั‚ั€ะฐั‚ะตะณะธะธ ะฒั‹ะฟะพะปะฝะตะฝะธั ะผะฐั‚ั€ะธั†ั‹ ะดะปั ั‚ะตะบัƒั‰ะตะณะพ ะทะฐะดะฐะฝะธั. +completion.context.steps=ะจะฐะณะธ ั ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะพะผ ะฒ ั‚ะตะบัƒั‰ะตะผ ะทะฐะดะฐะฝะธะธ. +completion.context.env=ะŸะตั€ะตะผะตะฝะฝั‹ะต ัั€ะตะดั‹ ะธะท ะทะฐะดะฐะฝะธะน ะธ ัˆะฐะณะพะฒ. +completion.context.vars=ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะธะต ะฟะตั€ะตะผะตะฝะฝั‹ะต ะบะพะฝั„ะธะณัƒั€ะฐั†ะธะธ ะธะท ะพะฑะปะฐัั‚ะตะน ะพั€ะณะฐะฝะธะทะฐั†ะธะธ, ั€ะตะฟะพะทะธั‚ะพั€ะธั ะธ ัั€ะตะดั‹. +completion.context.needs=ะ—ะฐะดะฐะฝะธั, ะบะพั‚ะพั€ั‹ะต ะดะพะปะถะฝั‹ ะฑั‹ั‚ัŒ ะฒั‹ะฟะพะปะฝะตะฝั‹ ะฟะตั€ะตะด ะฒั‹ะฟะพะปะฝะตะฝะธะตะผ ัั‚ะพะณะพ ะทะฐะดะฐะฝะธั, ะฐ ั‚ะฐะบะถะต ะธั… ะฒั‹ั…ะพะดะฝั‹ะต ะดะฐะฝะฝั‹ะต ะธ ั€ะตะทัƒะปัŒั‚ะฐั‚ั‹. +completion.context.github=ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะทะฐะฟัƒัะบะต ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ ะธ ัะพะฑั‹ั‚ะธัั… ะธะท ะบะพะฝั‚ะตะบัั‚ะฐ GitHub. +completion.context.gitea=ะกะพะฒะผะตัั‚ะธะผั‹ะน ั Gitea ะฟัะตะฒะดะพะฝะธะผ ะดะปั ะบะพะฝั‚ะตะบัั‚ะฐ ะดะตะนัั‚ะฒะธะน GitHub. +completion.context.runner=ะ˜ะฝั„ะพั€ะผะฐั†ะธั ะพ ะฑะตะณัƒะฝะต, ะฒั‹ะฟะพะปะฝััŽั‰ะตะผ ั‚ะตะบัƒั‰ะตะต ะทะฐะดะฐะฝะธะต. +completion.secret.githubToken=ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ ัะพะทะดะฐะตั‚ัั ั‚ะพะบะตะฝ ะดะปั ะบะฐะถะดะพะณะพ ะทะฐะฟัƒัะบะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ. +completion.remote.repository=ะฃะดะฐะปะตะฝะฝั‹ะน ั€ะตะฟะพะทะธั‚ะพั€ะธะน +completion.uses.local.workflow=ะ›ะพะบะฐะปัŒะฝั‹ะน ะผะฝะพะณะพั€ะฐะทะพะฒั‹ะน ั€ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +completion.uses.local.action=ะœะตัั‚ะฝะพะต ะดะตะนัั‚ะฒะธะต +completion.uses.ref.known=ะ˜ะทะฒะตัั‚ะฝะฐั ััั‹ะปะบะฐ ะฝะฐ ั€ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +completion.uses.ref.remote=ะกะฟั€ะฐะฒะพั‡ะฝะธะบ ะฟะพ ัƒะดะฐะปะตะฝะฝะพะผัƒ ั€ะฐะฑะพั‡ะตะผัƒ ะฟั€ะพั†ะตัััƒ +completion.uses.remote.known=ะ˜ะทะฒะตัั‚ะฝะพะต ัƒะดะฐะปะตะฝะฝะพะต ะดะตะนัั‚ะฒะธะต ะธะปะธ ะผะฝะพะณะพะบั€ะฐั‚ะฝะพ ะธัะฟะพะปัŒะทัƒะตะผั‹ะน ั€ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +completion.workflow.syntax=GitHub ะกะธะฝั‚ะฐะบัะธั ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ ะดะตะนัั‚ะฒะธะน +completion.workflow.top.name=ะžั‚ะพะฑั€ะฐะถะฐะตะผะพะต ะธะผั ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +completion.workflow.top.run-name=ะ”ะธะฝะฐะผะธั‡ะตัะบะพะต ะธะผั ะทะฐะฟัƒัะบะฐ +completion.workflow.top.on=ะกะพะฑั‹ั‚ะธั, ะทะฐะฟัƒัะบะฐัŽั‰ะธะต ั€ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +completion.workflow.top.permissions=ะ ะฐะทั€ะตัˆะตะฝะธั GITHUB_TOKEN ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ +completion.workflow.top.env=ะŸะตั€ะตะผะตะฝะฝั‹ะต ัั€ะตะดั‹ ะฒัะตะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +completion.workflow.top.defaults=ะะฐัั‚ั€ะพะนะบะธ ะทะฐะดะฐะฝะธั ะธ ัˆะฐะณะฐ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ +completion.workflow.top.concurrency=ะ“ั€ัƒะฟะฟะฐ ะฟะฐั€ะฐะปะปะตะปะธะทะผะฐ ะธ ะพั‚ะผะตะฝะฐ +completion.workflow.top.jobs=ะ—ะฐะดะฐะฝะธั, ะฒั‹ะฟะพะปะฝัะตะผั‹ะต ะฒ ัั‚ะพะผ ั€ะฐะฑะพั‡ะตะผ ะฟั€ะพั†ะตััะต +completion.workflow.event.branch_protection_rule=ะ˜ะทะผะตะฝะตะฝะฐ ะทะฐั‰ะธั‚ะฐ ะฒะตั‚ะบะธ +completion.workflow.event.check_run=ะžะดะธะฝะพั‡ะฝั‹ะน ะบะพะฝั‚ั€ะพะปัŒะฝั‹ะน ะฟั€ะพะณะพะฝ ะธะทะผะตะฝะตะฝ +completion.workflow.event.check_suite=ะŸั€ะพะฒะตั€ะธั‚ัŒ ะฝะฐะฑะพั€ ะธะทะผะตะฝะตะฝ +completion.workflow.event.create=ะ’ะตั‚ะบะฐ ะธะปะธ ั‚ะตะณ ัะพะทะดะฐะฝั‹. +completion.workflow.event.delete=ะ’ะตั‚ะบะฐ ะธะปะธ ั‚ะตะณ ัƒะดะฐะปะตะฝั‹. +completion.workflow.event.deployment=ะ ะฐะทะฒะตั€ั‚ั‹ะฒะฐะฝะธะต ัะพะทะดะฐะฝะพ +completion.workflow.event.deployment_status=ะกั‚ะฐั‚ัƒั ั€ะฐะทะฒะตั€ั‚ั‹ะฒะฐะฝะธั ะธะทะผะตะฝะตะฝ +completion.workflow.event.discussion=ะžะฑััƒะถะดะตะฝะธะต ะธะทะผะตะฝะตะฝะพ +completion.workflow.event.discussion_comment=ะšะพะผะผะตะฝั‚ะฐั€ะธะน ะฒ ะพะฑััƒะถะดะตะฝะธะธ ะธะทะผะตะฝะตะฝ +completion.workflow.event.fork=ะ ะตะฟะพะทะธั‚ะพั€ะธะน ั€ะฐะทะดะฒะพะตะฝ +completion.workflow.event.gollum=ะ’ะธะบะธ-ัั‚ั€ะฐะฝะธั†ะฐ ะธะทะผะตะฝะตะฝะฐ +completion.workflow.event.image_version=ะ’ะตั€ัะธั ะพะฑั€ะฐะทะฐ ะฟะฐะบะตั‚ะฐ ะธะทะผะตะฝะตะฝะฐ +completion.workflow.event.issue_comment=ะŸั€ะพะฑะปะตะผะฐ ะธะปะธ ะบะพะผะผะตะฝั‚ะฐั€ะธะน PR ะธะทะผะตะฝะตะฝั‹. +completion.workflow.event.issues=ะŸั€ะพะฑะปะตะผะฐ ะธะทะผะตะฝะตะฝะฐ +completion.workflow.event.label=ะฏั€ะปั‹ะบ ะธะทะผะตะฝะตะฝ +completion.workflow.event.merge_group=ะ—ะฐะฟั€ะพัˆะตะฝะฐ ะฟั€ะพะฒะตั€ะบะฐ ะพั‡ะตั€ะตะดะธ ัะปะธัะฝะธั +completion.workflow.event.milestone=ะญั‚ะฐะฟ ะธะทะผะตะฝะตะฝ +completion.workflow.event.page_build=ะกะฑะพั€ะบะฐ ัั‚ั€ะฐะฝะธั† ะทะฐะฟัƒั‰ะตะฝะฐ +completion.workflow.event.project=ะšะปะฐััะธั‡ะตัะบะธะน ะฟั€ะพะตะบั‚ ะธะทะผะตะฝะตะฝ +completion.workflow.event.project_card=ะšะปะฐััะธั‡ะตัะบะฐั ะบะฐั€ั‚ะพั‡ะบะฐ ะฟั€ะพะตะบั‚ะฐ ะธะทะผะตะฝะตะฝะฐ. +completion.workflow.event.project_column=ะกั‚ะพะปะฑะตั† ะบะปะฐััะธั‡ะตัะบะพะณะพ ะฟั€ะพะตะบั‚ะฐ ะธะทะผะตะฝะตะฝ. +completion.workflow.event.public=ะ ะตะฟะพะทะธั‚ะพั€ะธะน ัั‚ะฐะป ะพะฑั‰ะตะดะพัั‚ัƒะฟะฝั‹ะผ +completion.workflow.event.pull_request=ะ—ะฐะฟั€ะพั ะฝะฐ ะฒะบะปัŽั‡ะตะฝะธะต ะธะทะผะตะฝะตะฝ +completion.workflow.event.pull_request_review=ะžะฑะทะพั€ PR ะธะทะผะตะฝะตะฝ +completion.workflow.event.pull_request_review_comment=ะšะพะผะผะตะฝั‚ะฐั€ะธะน ะบ ะพะฑะทะพั€ัƒ PR ะธะทะผะตะฝะตะฝ +completion.workflow.event.pull_request_target=ะฆะตะปะตะฒะพะน ะบะพะฝั‚ะตะบัั‚ PR. ะžัั‚ั€ั‹ะต ะฝะพะถะธ. +completion.workflow.event.push=ะ—ะฐั„ะธะบัะธั€ะพะฒะฐั‚ัŒ ะธะปะธ ะพั‚ะฟั€ะฐะฒะธั‚ัŒ ั‚ะตะณ +completion.workflow.event.registry_package=ะŸะฐะบะตั‚ ะพะฟัƒะฑะปะธะบะพะฒะฐะฝ ะธะปะธ ะพะฑะฝะพะฒะปะตะฝ +completion.workflow.event.release=ะ’ะตั€ัะธั ะธะทะผะตะฝะตะฝะฐ +completion.workflow.event.repository_dispatch=ะŸะพะปัŒะทะพะฒะฐั‚ะตะปัŒัะบะพะต ัะพะฑั‹ั‚ะธะต API +completion.workflow.event.schedule=ะšั€ะพะฝ ั‚ะธะบ. ะงะฐัะพะฒะพะน ะผะตั…ะฐะฝะธะทะผ. +completion.workflow.event.status=ะกั‚ะฐั‚ัƒั ั„ะธะบัะฐั†ะธะธ ะธะทะผะตะฝะตะฝ +completion.workflow.event.watch=ะ ะตะฟะพะทะธั‚ะพั€ะธะน ะฟะพะผะตั‡ะตะฝ +completion.workflow.event.workflow_call=ะœะฝะพะณะพั€ะฐะทะพะฒั‹ะน ะฒั‹ะทะพะฒ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +completion.workflow.event.workflow_dispatch=ะšะฝะพะฟะบะฐ ั€ัƒั‡ะฝะพะณะพ ะทะฐะฟัƒัะบะฐ +completion.workflow.event.workflow_run=ะ—ะฐะฟัƒัะบ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ ะธะทะผะตะฝะตะฝ +completion.workflow.eventFilter.types=ะžะณั€ะฐะฝะธั‡ะธั‚ัŒ ะฒะธะดั‹ ะดะตัั‚ะตะปัŒะฝะพัั‚ะธ +completion.workflow.eventFilter.branches=ะขะพะปัŒะบะพ ัั‚ะธ ะฒะตั‚ะบะธ +completion.workflow.eventFilter.branches-ignore=ะŸั€ะพะฟัƒัั‚ะธั‚ัŒ ัั‚ะธ ะฒะตั‚ะบะธ +completion.workflow.eventFilter.tags=ะขะพะปัŒะบะพ ัั‚ะธ ั‚ะตะณะธ +completion.workflow.eventFilter.tags-ignore=ะŸั€ะพะฟัƒัั‚ะธั‚ัŒ ัั‚ะธ ั‚ะตะณะธ +completion.workflow.eventFilter.paths=ะขะพะปัŒะบะพ ัั‚ะธ ะฟัƒั‚ะธ +completion.workflow.eventFilter.paths-ignore=ะŸั€ะพะฟัƒัั‚ะธั‚ัŒ ัั‚ะธ ะฟัƒั‚ะธ +completion.workflow.eventFilter.workflows=ะะฐะทะฒะฐะฝะธั ั€ะฐะฑะพั‡ะธั… ะฟั€ะพั†ะตััะพะฒ, ะทะฐ ะบะพั‚ะพั€ั‹ะผะธ ัั‚ะพะธั‚ ัะปะตะดะธั‚ัŒ +completion.workflow.eventFilter.cron=ะ ะฐัะฟะธัะฐะฝะธะต ะšั€ะพะฝ. ะœะฐะปะตะฝัŒะบะธะน ั‡ะฐัะพะฒะพะน ะผะตั…ะฐะฝะธะทะผ. +completion.workflow.permission.actions=ะ—ะฐะฟัƒัะบะธ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ ะธ ะฐั€ั‚ะตั„ะฐะบั‚ั‹ ะดะตะนัั‚ะฒะธะน +completion.workflow.permission.artifact-metadata=ะ—ะฐะฟะธัะธ ะผะตั‚ะฐะดะฐะฝะฝั‹ั… ะฐั€ั‚ะตั„ะฐะบั‚ะฐ +completion.workflow.permission.attestations=ะั‚ั‚ะตัั‚ะฐั†ะธะธ ะฐั€ั‚ะตั„ะฐะบั‚ะพะฒ +completion.workflow.permission.checks=ะŸั€ะพะฒะตั€ัŒั‚ะต ะฟั€ะพะฑะตะณะธ ะธ ะปัŽะบัั‹ +completion.workflow.permission.code-quality=ะžั‚ั‡ะตั‚ั‹ ะพ ะบะฐั‡ะตัั‚ะฒะต ะบะพะดะฐ +completion.workflow.permission.contents=ะกะพะดะตั€ะถะธะผะพะต ั€ะตะฟะพะทะธั‚ะพั€ะธั +completion.workflow.permission.deployments=ะ ะฐะทะฒะตั€ั‚ั‹ะฒะฐะฝะธั +completion.workflow.permission.discussions=ะžะฑััƒะถะดะตะฝะธั +completion.workflow.permission.id-token=ั‚ะพะบะตะฝั‹ OpenID Connect +completion.workflow.permission.issues=ะŸั€ะพะฑะปะตะผั‹ +completion.workflow.permission.models=ะœะพะดะตะปะธ GitHub +completion.workflow.permission.packages=ะŸะฐะบะตั‚ั‹ GitHub +completion.workflow.permission.pages=ะกั‚ั€ะฐะฝะธั†ั‹ GitHub +completion.workflow.permission.pull-requests=ะ—ะฐะฟั€ะพัั‹ ะฝะฐ ะฒั‹ั‚ัะณะธะฒะฐะฝะธะต +completion.workflow.permission.security-events=ะกะบะฐะฝะธั€ะพะฒะฐะฝะธะต ะบะพะดะฐ ะธ ัะพะฑั‹ั‚ะธั ะฑะตะทะพะฟะฐัะฝะพัั‚ะธ +completion.workflow.permission.statuses=ะกั‚ะฐั‚ัƒัั‹ ะบะพะผะผะธั‚ะพะฒ +completion.workflow.permission.vulnerability-alerts=ะžะฟะพะฒะตั‰ะตะฝะธั Dependabot +completion.workflow.permission.value.read=ะ”ะพัั‚ัƒะฟ ะดะปั ั‡ั‚ะตะฝะธั +completion.workflow.permission.value.write=ะ”ะพัั‚ัƒะฟ ะฝะฐ ะทะฐะฟะธััŒ, ั‡ั‚ะตะฝะธะต ะฒะบะปัŽั‡ะตะฝะพ +completion.workflow.permission.value.none=ะะตั‚ ะดะพัั‚ัƒะฟะฐ +completion.workflow.permission.shorthand.read-all=ะ’ัะต ั€ะฐะทั€ะตัˆะตะฝะธั ะฟั€ะพั‡ะธั‚ะฐะฝั‹. ะ‘ะพะปัŒัˆะพะต ะพะดะตัะปะพ. +completion.workflow.permission.shorthand.write-all=ะ’ัะต ั€ะฐะทั€ะตัˆะตะฝะธั ะฟะธัˆัƒั‚. ะ‘ะพะปัŒัˆะพะน ะผะพะปะพั‚ะพะบ. +completion.workflow.permission.shorthand.empty=ะžั‚ะบะปัŽั‡ะธั‚ัŒ ั€ะฐะทั€ะตัˆะตะฝะธั ั‚ะพะบะตะฝะฐ +completion.workflow.job.name=ะžั‚ะพะฑั€ะฐะถะฐะตะผะพะต ะธะผั ะทะฐะดะฐะฝะธั +completion.workflow.job.permissions=ะ ะฐะทั€ะตัˆะตะฝะธั ั‚ะพะบะตะฝะฐ ะทะฐะดะฐะฝะธั +completion.workflow.job.needs=ะ’ะฐะบะฐะฝัะธะธ, ะบะพั‚ะพั€ั‹ั… ัั‚ะพะธั‚ ะถะดะฐั‚ัŒ +completion.workflow.job.if=ะฃัะปะพะฒะธั ั€ะฐะฑะพั‚ั‹ +completion.workflow.job.runs-on=ะœะตั‚ะบะฐ ะธะปะธ ะณั€ัƒะฟะฟะฐ ะฑะตะณัƒะฝะฐ +completion.workflow.job.snapshot=ะกะฝะธะผะพะบ ะฑะตะณัƒะฝะฐ +completion.workflow.job.environment=ะกั€ะตะดะฐ ั€ะฐะทะฒะตั€ั‚ั‹ะฒะฐะฝะธั +completion.workflow.job.concurrency=ะ‘ะปะพะบะธั€ะพะฒะบะฐ ะฟะฐั€ะฐะปะปะตะปัŒะฝะพะณะพ ะฒั‹ะฟะพะปะฝะตะฝะธั ะทะฐะดะฐะฝะธะน +completion.workflow.job.outputs=ะ’ั‹ะฒะพะดั‹, ะบะพั‚ะพั€ั‹ะต ะผะพะณัƒั‚ ั‡ะธั‚ะฐั‚ัŒ ะดั€ัƒะณะธะต ะทะฐะดะฐะฝะธั +completion.workflow.job.env=ะŸะตั€ะตะผะตะฝะฝั‹ะต ัั€ะตะดั‹ ะทะฐะดะฐะฝะธั +completion.workflow.job.defaults=ะะฐัั‚ั€ะพะนะบะธ ะทะฐะดะฐะฝะธั ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ +completion.workflow.job.steps=ะกะฟะธัะพะบ ัˆะฐะณะพะฒ. ะ ะตะฐะปัŒะฝะฐั ั€ะฐะฑะพั‚ะฐ. +completion.workflow.job.timeout-minutes=ะขะฐะนะผ-ะฐัƒั‚ ะทะฐะดะฐะฝะธั ะฒ ะผะธะฝัƒั‚ะฐั… +completion.workflow.job.strategy=ะœะฐั‚ั€ะธั†ะฐ ะธ ัั‚ั€ะฐั‚ะตะณะธั ะฟะปะฐะฝะธั€ะพะฒะฐะฝะธั +completion.workflow.job.continue-on-error=ะŸัƒัั‚ัŒ ัั‚ะฐ ั€ะฐะฑะพั‚ะฐ ะผัะณะบะพ ะฟั€ะพะฒะฐะปะธั‚ัั +completion.workflow.job.container=ะšะพะฝั‚ะตะนะฝะตั€ ะดะปั ัั‚ะพะน ั€ะฐะฑะพั‚ั‹ +completion.workflow.job.services=ะกะตั€ะฒะธัะฝั‹ะต ะบะพะฝั‚ะตะนะฝะตั€ั‹ ั ะบะพะปััะบะพะน +completion.workflow.job.uses=ะœะฝะพะณะพั€ะฐะทะพะฒั‹ะน ั€ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั ะดะปั ะฒั‹ะทะพะฒะฐ +completion.workflow.job.with=ะ’ั…ะพะดะฝั‹ะต ะดะฐะฝะฝั‹ะต ะดะปั ะฒั‹ะทะฒะฐะฝะฝะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +completion.workflow.job.secrets=ะกะตะบั€ะตั‚ั‹ ะฒั‹ะทั‹ะฒะฐะตะผะพะณะพ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +completion.workflow.defaultsRun.shell=ะžะฑะพะปะพั‡ะบะฐ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ ะดะปั ัˆะฐะณะพะฒ ะฒั‹ะฟะพะปะฝะตะฝะธั +completion.workflow.defaultsRun.working-directory=ะ ะฐะฑะพั‡ะธะน ะบะฐั‚ะฐะปะพะณ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ +completion.workflow.concurrency.group=ะ—ะฐะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ะธะผั ะดะปั ะทะฐะฟัƒัะบะพะฒ ะฒ ะพั‡ะตั€ะตะดะธ +completion.workflow.concurrency.cancel-in-progress=ะžั‚ะผะตะฝะธั‚ัŒ ะฟั€ะตะดั‹ะดัƒั‰ะธะต ัะพะพั‚ะฒะตั‚ัั‚ะฒัƒัŽั‰ะธะต ะฟั€ะพะณะพะฝั‹ +completion.workflow.environment.name=ะ˜ะผั ัั€ะตะดั‹ +completion.workflow.environment.url=ะžะบั€ัƒะถะฐัŽั‰ะฐั ัั€ะตะดะฐ URL +completion.workflow.strategy.matrix=ะœะฐั‚ั€ะธั‡ะฝั‹ะต ะพัะธ ะธ ะฒะฐั€ะธะฐะฝั‚ั‹ +completion.workflow.strategy.fail-fast=ะžั‚ะผะตะฝะฐ ะพะดะฝะพัƒั€ะพะฒะฝะตะฒั‹ั… ัะปะตะผะตะฝั‚ะพะฒ ะผะฐั‚ั€ะธั†ั‹ ะฒ ัะปัƒั‡ะฐะต ัะฑะพั +completion.workflow.strategy.max-parallel=ะžะณั€ะฐะฝะธั‡ะตะฝะธะต ะฟะฐั€ะฐะปะปะตะปะธะทะผะฐ ะผะฐั‚ั€ะธั† +completion.workflow.matrix.include=ะ”ะพะฑะฐะฒะธั‚ัŒ ะบะพะผะฑะธะฝะฐั†ะธะธ ะผะฐั‚ั€ะธั† +completion.workflow.matrix.exclude=ะฃะดะฐะปะธั‚ัŒ ะบะพะผะฑะธะฝะฐั†ะธะธ ะผะฐั‚ั€ะธั† +completion.workflow.step.id=ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ัˆะฐะณะฐ ะดะปั ััั‹ะปะพะบ +completion.workflow.step.if=ะกะพัั‚ะพัะฝะธะต ัˆะฐะณะฐ +completion.workflow.step.name=ะžั‚ะพะฑั€ะฐะถะฐะตะผะพะต ะธะผั ัˆะฐะณะฐ +completion.workflow.step.uses=ะ”ะตะนัั‚ะฒะธะต ะดะปั ะทะฐะฟัƒัะบะฐ +completion.workflow.step.run=ะกะบั€ะธะฟั‚ ะดะปั ะทะฐะฟัƒัะบะฐ +completion.workflow.step.shell=ะžะฑะพะปะพั‡ะบะฐ ะดะปั ัั‚ะพะณะพ ัˆะฐะณะฐ ะฒั‹ะฟะพะปะฝะตะฝะธั +completion.workflow.step.with=ะ’ั…ะพะดะฝั‹ะต ะดะตะนัั‚ะฒะธั +completion.workflow.step.env=ะŸะตั€ะตะผะตะฝะฝั‹ะต ัั€ะตะดั‹ ัˆะฐะณะฐ +completion.workflow.step.continue-on-error=ะŸัƒัั‚ัŒ ัั‚ะพั‚ ัˆะฐะณ ะผัะณะบะพ ะฟั€ะพะฒะฐะปะธั‚ัั +completion.workflow.step.timeout-minutes=ะขะฐะนะผ-ะฐัƒั‚ ัˆะฐะณะฐ ะฒ ะผะธะฝัƒั‚ะฐั… +completion.workflow.step.working-directory=ะจะฐะณ ั€ะฐะฑะพั‡ะตะณะพ ะบะฐั‚ะฐะปะพะณะฐ +completion.workflow.container.image=ะ˜ะทะพะฑั€ะฐะถะตะฝะธะต ะบะพะฝั‚ะตะนะฝะตั€ะฐ +completion.workflow.container.credentials=ะฃั‡ะตั‚ะฝั‹ะต ะดะฐะฝะฝั‹ะต ั€ะตะตัั‚ั€ะฐ +completion.workflow.container.env=ะŸะตั€ะตะผะตะฝะฝั‹ะต ัั€ะตะดั‹ ะบะพะฝั‚ะตะนะฝะตั€ะฐ +completion.workflow.container.ports=ะŸะพั€ั‚ั‹ ะดะปั ั€ะฐัะบั€ั‹ั‚ะธั +completion.workflow.container.volumes=ะขะพะผะฐ ะดะปั ะผะพะฝั‚ะธั€ะพะฒะฐะฝะธั +completion.workflow.container.options=Docker ัะพะทะดะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ั‹ +completion.workflow.service.image=ะžะฑั€ะฐะท ัะตั€ะฒะธัะฝะพะณะพ ะบะพะฝั‚ะตะนะฝะตั€ะฐ +completion.workflow.service.credentials=ะฃั‡ะตั‚ะฝั‹ะต ะดะฐะฝะฝั‹ะต ั€ะตะตัั‚ั€ะฐ +completion.workflow.service.env=ะŸะตั€ะตะผะตะฝะฝั‹ะต ัั€ะตะดั‹ ัะปัƒะถะฑั‹ +completion.workflow.service.ports=ะกะตั€ะฒะธัะฝั‹ะต ะฟะพั€ั‚ั‹ +completion.workflow.service.volumes=ะžะฑัŠะตะผั‹ ัƒัะปัƒะณ +completion.workflow.service.options=Docker ัะพะทะดะฐั‚ัŒ ะฟะฐั€ะฐะผะตั‚ั€ั‹ +completion.workflow.credentials.username=ะ˜ะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ั€ะตะตัั‚ั€ะฐ +completion.workflow.credentials.password=ะŸะฐั€ะพะปัŒ ะธะปะธ ั‚ะพะบะตะฝ ั€ะตะตัั‚ั€ะฐ +completion.workflow.inputType.string=ะ’ะฒะพะด ั‚ะตะบัั‚ะฐ +completion.workflow.inputType.boolean=ะ’ะตั€ะฝั‹ะน ะธะปะธ ะปะพะถะฝั‹ะน ะฒะฒะพะด +completion.workflow.inputType.choice=ะ’ะฒะพะด ะฒั‹ะฟะฐะดะฐัŽั‰ะตะณะพ ะฒั‹ะฑะพั€ะฐ +completion.workflow.inputType.number=ะ’ะฒะพะด ะฝะพะผะตั€ะฐ +completion.workflow.inputType.environment=ะ’ะฒะพะด ัั€ะตะดัั‚ะฒะฐ ะฒั‹ะฑะพั€ะฐ ัั€ะตะดั‹ +completion.workflow.boolean.true=ะ”ะฐ. ะ’ะบะปัŽั‡ะธั‚ะต ะตะณะพ. +completion.workflow.boolean.false=ะะตั‚. ะ”ะตั€ะถะธั‚ะต ัั‚ะพ ะฒ ั‚ะตะผะฝะพั‚ะต. +completion.workflow.runner.ubuntu-latest=ะŸะพัะปะตะดะฝะธะน ั€ะฐะฝะฝะตั€ Ubuntu +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 ะฑะตะณัƒะฝ +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 ะฑะตะณัƒะฝ +completion.workflow.runner.windows-latest=ะŸะพัะปะตะดะฝะธะน ั€ะฐะฝะฝะตั€ Windows +completion.workflow.runner.windows-2025=Windows ะ‘ะตะณัƒะฝ ัะตั€ะฒะตั€ะฐ 2025 +completion.workflow.runner.windows-2022=Windows ะ‘ะตะณัƒะฝ ัะตั€ะฒะตั€ะฐ 2022 +completion.workflow.runner.macos-latest=ะŸะพัะปะตะดะฝะธะน ั€ะฐะฝะฝะตั€ macOS +completion.workflow.runner.macos-15=macOS 15 ะฑะตะณัƒะฝะพะบ +completion.workflow.runner.macos-14=macOS 14 ะฑะตะณัƒะฝะพะบ +completion.workflow.runner.self-hosted=ะ’ะฐัˆ ัะพะฑัั‚ะฒะตะฝะฝั‹ะน ะฑะตะณัƒะฝ. ะ’ะฐัˆ ั†ะธั€ะบ. +completion.steps.outputs=ะะฐะฑะพั€ ะฒั‹ั…ะพะดะพะฒ, ะพะฟั€ะตะดะตะปะตะฝะฝั‹ะน ะดะปั ัˆะฐะณะฐ. +completion.steps.conclusion=ะ ะตะทัƒะปัŒั‚ะฐั‚ ะทะฐะฒะตั€ัˆะตะฝะฝะพะณะพ ัˆะฐะณะฐ ะฟะพัะปะต ะฟั€ะธะผะตะฝะตะฝะธั ะฟั€ะพะดะพะปะถะตะฝะธั ะฟั€ะธ ะพัˆะธะฑะบะต. +completion.steps.outcome=ะ ะตะทัƒะปัŒั‚ะฐั‚ ะทะฐะฒะตั€ัˆะตะฝะฝะพะณะพ ัˆะฐะณะฐ ะดะพ ะฟั€ะธะผะตะฝะตะฝะธั ะฟั€ะพะดะพะปะถะตะฝะธั ะฟั€ะธ ะพัˆะธะฑะบะต. +completion.jobs.outputs=ะะฐะฑะพั€ ะฒั‹ั…ะพะดะฝั‹ั… ะดะฐะฝะฝั‹ั…, ะพะฟั€ะตะดะตะปะตะฝะฝั‹ั… ะดะปั ะทะฐะดะฐะฝะธั. +completion.jobs.result=ะ ะตะทัƒะปัŒั‚ะฐั‚ ั€ะฐะฑะพั‚ั‹. +settings.displayName=GitHub ะ ะฐะฑะพั‡ะธะน ะฟั€ะพั†ะตัั +settings.language.label=ะฏะทั‹ะบ: +settings.language.system=IDE/ัะธัั‚ะตะผะฝะพะต ะทะฝะฐั‡ะตะฝะธะต ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ +settings.cache.title=ะšััˆ ะดะตะนัั‚ะฒะธะน +settings.cache.column.key=ะšะปัŽั‡ ะบััˆะฐ +settings.cache.column.name=ะ˜ะผั +settings.cache.column.kind=ะ”ะพะฑั€ั‹ะน +settings.cache.column.state=ะ“ะพััƒะดะฐั€ัั‚ะฒะพ +settings.cache.column.expires=ะกั€ะพะบ ะดะตะนัั‚ะฒะธั ะธัั‚ะตะบะฐะตั‚ +settings.cache.kind.local=ะผะตัั‚ะฝั‹ะน +settings.cache.kind.remote=ัƒะดะฐะปะตะฝะฝั‹ะน +settings.cache.state.resolved=ั€ะตัˆะตะฝะพ +settings.cache.state.pending=ะฒ ะพะถะธะดะฐะฝะธะธ +settings.cache.state.expired=ะฝะตัะฒะตะถะธะน +settings.cache.state.suppressed=ะฟะพะดะฐะฒะปะตะฝะฝั‹ะน +settings.cache.refresh=ะขะฐะฑะปะธั†ะฐ Refresh +settings.cache.deleteSelected=ะฃะดะฐะปะธั‚ัŒ ะฒั‹ะฑั€ะฐะฝะฝะพะต +settings.cache.deleteAll=ะฃะดะฐะปะธั‚ัŒ ะฒัะต +settings.cache.export=ะญะบัะฟะพั€ั‚ +settings.cache.import=ะ˜ะผะฟะพั€ั‚ +settings.cache.summary=ะšััˆ: ะทะฐะฟะธัะธ {0}, {1} ั€ะฐะทั€ะตัˆะตะฝะพ, {2} ัƒะดะฐะปะตะฝะพ, {3} ัƒัั‚ะฐั€ะตะฒัˆะตะต, {4} ะพั‚ะบะปัŽั‡ะตะฝะพ. ะšััˆ: {5} KB. +settings.cache.noneSelected=ะกะฝะฐั‡ะฐะปะฐ ะฒั‹ะฑะตั€ะธั‚ะต ัั‚ั€ะพะบะธ ะบััˆะฐ. ะœะตั‚ะปะฐ ะพั‚ะบะฐะทั‹ะฒะฐะตั‚ัั ะพั‚ ะดะพะณะฐะดะพะบ. +settings.cache.deleteSelected.done=ะฃะดะฐะปะตะฝั‹ ะทะฐะฟะธัะธ ะบััˆะฐ {0}. ะšั€ะพัˆะตั‡ะฝะพะต ะพะฑะปะฐะบะพ ะฟั‹ะปะธ ัะดะตั€ะถะฐะฝะพ. +settings.cache.deleteAll.confirm=ะฃะดะฐะปะธั‚ัŒ ะฒัะต ะทะฐะฟะธัะธ ะบััˆะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ GitHub? +settings.cache.deleteAll.done=ะฃะดะฐะปะธะป ะฒัะต ะทะฐะฟะธัะธ ะบััˆะฐ. ะ’ ะบััˆะต ั‚ะตะฟะตั€ัŒ ะฟะพะดะพะทั€ะธั‚ะตะปัŒะฝะพ ั‚ะธั…ะพ. +settings.cache.export.done=ะญะบัะฟะพั€ั‚ะธั€ะพะฒะฐะฝั‹ ะทะฐะฟะธัะธ ะบััˆะฐ {0}. ะœะฐะปะตะฝัŒะบะธะน ะฐั€ั…ะธะฒะฝั‹ะน ะทะฒะตั€ัŒ ะฒ ะบะปะตั‚ะบะต. +settings.cache.import.done=ะ˜ะผะฟะพั€ั‚ะธั€ะพะฒะฐะฝะฝั‹ะต ะทะฐะฟะธัะธ ะบััˆะฐ. ะั€ั…ะธะฒะฝั‹ะน ะทะฒะตั€ัŒ ะฟะพะฒะตะป ัะตะฑั. +settings.cache.import.unsupported=ะะตะฟะพะดะดะตั€ะถะธะฒะฐะตะผั‹ะน ั„ะฐะนะป ะบััˆะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ GitHub. +settings.cache.import.brokenLine=ะะตั€ะฐะฑะพั‚ะฐัŽั‰ะฐั ัั‚ั€ะพะบะฐ ะบััˆะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ GitHub. +settings.cache.import.brokenKey=ะะตั€ะฐะฑะพั‚ะฐัŽั‰ะธะน ะบะปัŽั‡ ะบััˆะฐ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ GitHub. +settings.support.button=ะŸะพะดะดะตั€ะถะธั‚ะต ัั‚ะพั‚ ะฟะปะฐะณะธะฝ +settings.support.tooltip=ะžั‚ะบั€ะพะนั‚ะต ัั‚ั€ะฐะฝะธั†ัƒ ะฟะพะดะดะตั€ะถะบะธ +settings.support.line.0=ะŸะพะดะฐั‡ะฐ ัั‚ั€ะพะธั‚ะตะปัŒะฝะพะน ะฟะตั‡ะธ +settings.support.line.1=ะšัƒะฟะธั‚ัŒ ะฟะฐั€ัะตั€ ะบะพั„ะต +settings.support.line.2=ะกะฟะพะฝัะธั€ัƒะนั‚ะต ะผะตะฝัŒัˆะต ะทะฐะฟัƒั‚ะฐะฝะฝั‹ั… ั€ะฐะฑะพั‡ะธั… ะฟั€ะพั†ะตััะพะฒ +workflow.run.jobs.title=ะ—ะฐะดะฐะฝะธั ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +workflow.run.jobs.root=ะ—ะฐะฟัƒัะบ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +workflow.run.jobs.description=GitHub ะ”ะตั€ะตะฒะพ ะทะฐะดะฐะฝะธะน ยซะ”ะตะนัั‚ะฒะธัยป ะธ ะฒั‹ะฑั€ะฐะฝะฝั‹ะน ะถัƒั€ะฝะฐะป ะทะฐะดะฐะฝะธะน +workflow.run.tree.done=ัะดะตะปะฐะฝะพ +workflow.run.tree.failed=ะฝะต ัƒะดะฐะปะพััŒ +workflow.run.tree.skipped=ะฟั€ะพะฟัƒั‰ะตะฝ +workflow.run.tree.warn=ะฟั€ะตะดัƒะฟั€ะตะถะดะฐั‚ัŒ +workflow.run.tree.err=ะพัˆะธะฑะฐั‚ัŒัั +workflow.run.delete.tooltip=ะฃะดะฐะปะธั‚ัŒ ะทะฐะฟัƒัะบ +workflow.run.delete.noRun=ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบะฐ ะฟะพะบะฐ ะฝะตั‚. +workflow.run.delete.requested=ะฃะดะฐะปะตะฝะธะต ะทะฐะฟัƒัะบะฐ {0}. +workflow.run.delete.done=ะ—ะฐะฟัƒัะบ {0} ัƒะดะฐะปะตะฝ. +workflow.run.delete.http=ะฃะดะฐะปะธั‚ัŒ HTTP {0}. ะฃั„. +workflow.run.delete.failed=ะฃะดะฐะปะตะฝะธะต ะฝะต ัƒะดะฐะปะพััŒ: {0} +workflow.run.rerun.all.tooltip=ะŸะพะฒั‚ะพั€ะฝั‹ะน ะทะฐะฟัƒัะบ ั€ะฐะฑะพั‡ะตะณะพ ะฟั€ะพั†ะตััะฐ +workflow.run.rerun.failed.tooltip=ะŸะตั€ะตะทะฐะฟัƒัั‚ะธั‚ัŒ ะฝะตัƒะดะฐั‡ะฝั‹ะต ะทะฐะดะฐะฝะธั +workflow.run.rerun.noRun=ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบะฐ ะฟะพะบะฐ ะฝะตั‚. +workflow.run.rerun.all.requested=ะ—ะฐะฟั€ะพัˆะตะฝ ะฟะพะฒั‚ะพั€ะฝั‹ะน ะทะฐะฟัƒัะบ: {0}. +workflow.run.rerun.failed.requested=ะ—ะฐะฟั€ะพัˆะตะฝั‹ ะฝะตัƒะดะฐะฒัˆะธะตัั ะทะฐะดะฐะฝะธั: {0}. +workflow.run.rerun.all.done=ะŸะพะฒั‚ะพั€ะฝั‹ะน ะทะฐะฟัƒัะบ ะฒ ะพั‡ะตั€ะตะดะธ: {0}. +workflow.run.rerun.failed.done=ะะตัƒะดะฐั‡ะฝั‹ะต ะทะฐะดะฐะฝะธั ะฒ ะพั‡ะตั€ะตะดะธ: {0}. +workflow.run.rerun.http=ะŸะตั€ะตะทะฐะฟัƒัั‚ะธั‚ะต HTTP {0}. ะฃั„. +workflow.run.rerun.failed=ะŸะพะฒั‚ะพั€ ะฝะต ัƒะดะฐะปัั: {0} +workflow.run.download.log.tooltip=ะกะพั…ั€ะฐะฝะธั‚ัŒ ะถัƒั€ะฝะฐะป ะทะฐะดะฐะฝะธะน +workflow.run.download.artifacts.tooltip=ะกะบะฐั‡ะฐั‚ัŒ ะฐั€ั‚ะตั„ะฐะบั‚ั‹ +workflow.run.download.noRun=ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบะฐ ะฟะพะบะฐ ะฝะตั‚. +workflow.run.download.log.requested=ะŸะพะปัƒั‡ะตะฝะธะต ะถัƒั€ะฝะฐะปะฐ ะดะปั {0}. +workflow.run.download.log.done=ะ–ัƒั€ะฝะฐะป ัะพั…ั€ะฐะฝะตะฝ: {0}. +workflow.run.download.artifacts.requested=ะ˜ะทะฒะปะตั‡ะตะฝะธะต ะฐั€ั‚ะตั„ะฐะบั‚ะพะฒ. +workflow.run.download.artifacts.empty=ะะธะบะฐะบะธั… ะฐั€ั‚ะตั„ะฐะบั‚ะพะฒ. ะšั€ะพัˆะตั‡ะฝะฐั ะฟัƒัั‚ะพั‚ะฐ. +workflow.run.download.artifact.expired=ะกั€ะพะบ ะดะตะนัั‚ะฒะธั ะฐั€ั‚ะตั„ะฐะบั‚ะฐ ะธัั‚ะตะบ: {0}. +workflow.run.download.artifact.done=ะั€ั‚ะตั„ะฐะบั‚ ัะพั…ั€ะฐะฝะตะฝ: {0} -> {1}. +workflow.run.download.failed=ะ—ะฐะณั€ัƒะทะบะฐ ะฝะต ัƒะดะฐะปะฐััŒ: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_sv.properties b/src/main/resources/messages/GitHubWorkflowBundle_sv.properties new file mode 100644 index 0000000..8949d30 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_sv.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub arbetsflรถde +plugin.description=Stรถd fรถr GitHub Actions arbetsflรถdesfiler +group.GitHubWorkflow.Tools.text=GitHub arbetsflรถde +group.GitHubWorkflow.Tools.description=GitHub Workflow plugin-verktyg +action.GitHubWorkflow.RefreshActionCache.text=Uppdatera รฅtgรคrdscache +action.GitHubWorkflow.RefreshActionCache.description=Refresh lรถste fjรคrr GitHub-รฅtgรคrder och รฅteranvรคndbar arbetsflรถdesmetadata +action.GitHubWorkflow.RestoreActionWarnings.text=ร…terstรคll รฅtgรคrdsvarningar +action.GitHubWorkflow.RestoreActionWarnings.description=ร…terstรคll varningar fรถr undertryckta รฅtgรคrder, inmatning och utdatavalidering +action.GitHubWorkflow.ClearActionCache.text=Rensa Action Cache +action.GitHubWorkflow.ClearActionCache.description=Rensa cachade GitHub-รฅtgรคrder och รฅteranvรคndbar arbetsflรถdesmetadata +notification.cache.cleared=Rensade {0} cachade GitHub arbetsflรถdesposter. +notification.cache.refresh.started=Refreshing {0} cachade fjรคrr GitHub arbetsflรถdesposter. +notification.warnings.restored=ร…terstรคllda varningar fรถr {0} GitHub Workflow-poster. +workflow.run.configuration.display=GitHub arbetsflรถde +workflow.run.configuration.description=Skicka och fรถlj GitHub Actions-arbetsflรถdeskรถrningar +workflow.run.configuration.name=GitHub Arbetsflรถde: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=ร„gare +workflow.run.field.repo=Fรถrvar +workflow.run.field.workflow=Arbetsflรถdesfil +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Reservvariabel fรถr token +workflow.run.inputs.title=workflow_dispatch-ingรฅngar (nyckel=vรคrde) +workflow.run.error.apiUrl=GitHub API URL krรคvs. +workflow.run.error.repository=GitHub-fรถrvarets รคgare och namn krรคvs. +workflow.run.error.workflow=Arbetsflรถdesfil krรคvs. +workflow.run.error.ref=Filial eller tagreferens krรคvs. +workflow.run.error.inputs=GitHub workflow_dispatch stรถder hรถgst 25 ingรฅngar. +workflow.run.gutter.stop=Stoppa kรถrningen av arbetsflรถdet +workflow.run.gutter.stop.text=Stoppa kรถrningen av arbetsflรถdet +workflow.run.gutter.stop.description=Avbryt denna kรถrning +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=kรถr: +workflow.log.warning=varning: +workflow.log.error=fel: +workflow.run.cancel.requested=Avbryt begรคrd: {0}. +workflow.run.stop.before.id=Stopp begรคrts. Inget kรถr-id รคnnu. +workflow.run.cancel.http=Avbryt HTTP {0}. Oj. +workflow.run.cancel.failed=Avbryt lรถst: {0} +workflow.run.interrupted=Avbruten. +workflow.run.link=Kรถr: {0} +workflow.run.discovery=Kรถr accepterat. Jaktkรถrnings-id. +workflow.run.discovery.none=Inget kรถr-id รคnnu. Fliken ร…tgรคrder vet mer. +workflow.run.status=Status: {0}{1} +workflow.run.job.main=Jobb: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Status: {0} {1}{2}{3} +workflow.run.logs.later=Loggar visas nรคr GitHub publicerar dem. +workflow.run.job.logs.later=Jobb {0}: {1} +workflow.run.log.failed=Loggnedladdning misslyckades: {0} +workflow.run.log.failed.job=Loggnedladdning misslyckades fรถr {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Jobb: {0} +workflow.run.job.fallbackName=Jobb {0} +workflow.run.overview=Arbetsflรถdeskรถrning {0} {1}/{2} klar, {3} kรถrs +workflow.run.state.ok=[OK] +workflow.run.state.fail=[FEL] +workflow.run.state.running=[Kร–R] +workflow.run.state.waiting=[Vร„NTA] +workflow.run.dispatch.verbs=Priming|Kรถ|Kallelse|Lasering|Uppstart +workflow.run.dispatch.objects=arbetsflรถde|automation|pipeline|kรถrning +workflow.run.dispatch={0} {1} {2}{3} fรถr {4} pรฅ {5}. +workflow.run.notification.auth=GitHub-arbetsflรถdesutskick krรคver ett autentiserat GitHub-konto. Lรคgg till eller uppdatera konton i {0}. +workflow.run.notification.openSettings=ร–ppna GitHub Instรคllningar +workflow.cache.progress.title=Lรถser GitHub-รฅtgรคrder +workflow.cache.progress.text=Lรถser {0} {1} +workflow.cache.kind.action=handling +workflow.cache.kind.workflow=arbetsflรถde +inspection.parameter.input=ingรฅng +inspection.parameter.secret=hemlighet +inspection.action.delete.invalid=Ta bort ogiltiga {0} [{1}] +inspection.action.update.major=Uppdatera รฅtgรคrd [{0}] till [{1}] +inspection.warning.toggle=Vรคxla varningar [{0}] fรถr [{1}] +inspection.warning.on=pรฅ +inspection.warning.off=av +inspection.statement.incomplete=Ofullstรคndig uttalande [{0}] +inspection.invalid.suffix.remove=Ta bort ogiltigt suffix [{0}] +inspection.replace.with=Ersรคtt med [{0}] +inspection.invalid.remove=Ta bort ogiltig [{0}] +inspection.workflow.syntax.unknownTopLevelKey=Okรคnd arbetsflรถdesnyckel [{0}] +inspection.workflow.syntax.unknownEventKey=Okรคnd arbetsflรถdeshรคndelse [{0}] +inspection.workflow.syntax.unknownTriggerKey=Okรคnd triggernyckel [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Okรคnt triggerfilter [{0}] +inspection.workflow.syntax.unknownTriggerValue=Okรคnt triggervรคrde [{0}] +inspection.workflow.syntax.unknownPermission=Okรคnd behรถrighet [{0}] +inspection.workflow.syntax.unknownPermissionValue=Okรคnt behรถrighetsvรคrde [{0}] +inspection.workflow.syntax.unknownJobKey=Okรคnd jobbnyckel [{0}] +inspection.workflow.syntax.unknownStepKey=Okรคnd stegnyckel [{0}] +inspection.action.reload=Ladda om [{0}] +inspection.action.unresolved=Olรถst [{0}] - kontrollera GitHub-kontoรฅtkomst, behรถrigheter fรถr privata arkiv, hastighetsgrรคnser, saknade refs eller saknade รฅtgรคrds-/arbetsflรถdesmetadata +inspection.action.jump=Hoppa till fil [{0}] +inspection.output.unused=Oanvรคnd [{0}] +inspection.secret.invalid.if=Ta bort [{0}] - Hemligheter รคr inte giltiga i `if`-satser +inspection.secret.replace.runtime=Ersรคtt [{0}] med [{1}] - om det inte tillhandahรฅlls under kรถrning +inspection.needs.invalid.job=Ta bort ogiltigt jobb-ID [{0}] - detta jobb-ID matchar inte nรฅgot tidigare jobb +documentation.description=Beskrivning: {0} +documentation.type=Typ: {0} +documentation.required=Krรคvs: {0} +documentation.default=Standard: {0} +documentation.deprecated=Utfasad: {0} +documentation.open.declaration=ร–ppen deklaration ({0}) +documentation.input.label=Ingรฅng +documentation.secret.label=Hemligt +documentation.env.label=Miljรถvariabel +documentation.matrix.label=Matrisegenskap +documentation.need.label=Behรถvde jobb +documentation.need.description=Direkt jobbberoende. +documentation.needOutput.label=Behรถvde jobbutdata +documentation.reusableJob.label=ร…teranvรคndbart arbetsflรถdesjobb +documentation.reusableJob.description=Jobb deklarerat i detta รฅteranvรคndbara arbetsflรถde. +documentation.reusableJobOutput.label=ร…teranvรคndbar arbetsflรถdesjobb +documentation.service.label=Servicecontainer +documentation.servicePort.label=Serviceport +documentation.container.label=Jobbcontainer +documentation.symbol.label=Arbetsflรถdessymbol +documentation.symbol.description=Lรถst arbetsflรถdesuttryck. +documentation.workflowOutput.label=Arbetsflรถdesutgรฅng +documentation.jobOutput.label=Jobbutgรฅng +documentation.action.label=ร…tgรคrd +documentation.externalAction.label=Extern รฅtgรคrd +documentation.reusableWorkflow.label=ร…teranvรคndbart arbetsflรถde +documentation.resolvedFrom=lรถst frรฅn {0} +documentation.notResolved=inte lรถst รคnnu +documentation.inputs.title=Ingรฅngar +documentation.outputs.title=Utgรฅngar +documentation.secrets.title=Hemligheter +documentation.value.label=Vรคrde +documentation.step.title=Steg {0} +documentation.name.label=Namn +documentation.uses.label=Anvรคnds +documentation.run.label=Kรถr +documentation.description.label=Beskrivning +documentation.step.label=Steg +documentation.source.label=Kรคlla +documentation.stepOutput.label=Steg utgรฅng +documentation.context.github=github sammanhang +documentation.context.github.description=Information om aktuell arbetsflรถdeskรถrning och hรคndelse. +documentation.context.gitea=gitea sammanhang +documentation.context.gitea.description=Gitea-kompatibelt alias fรถr GitHub Actions-kontexten. +documentation.context.inputs=inmatningskontext +documentation.context.inputs.description=Ingรฅngar fรถr arbetsflรถde, utskick eller รฅtgรคrd finns hรคr. +documentation.context.secrets=hemligheter sammanhang +documentation.context.secrets.description=Hemliga vรคrden tillgรคngliga fรถr detta arbetsflรถde eller รฅteranvรคndbara arbetsflรถdesanrop. +documentation.context.env=env sammanhang +documentation.context.env.description=Miljรถvariabler synliga pรฅ den hรคr platsen. +documentation.context.matrix=matriskontext +documentation.context.matrix.description=Matrisvรคrden fรถr det aktuella jobbet. +documentation.context.steps=steg sammanhang +documentation.context.steps.description=Tidigare steg i det aktuella jobbet, inklusive utgรฅngar och status. +documentation.context.needs=behรถver sammanhang +documentation.context.needs.description=Direkta jobbberoenden och deras resultat/resultat. +documentation.context.jobs=jobbsammanhang +documentation.context.jobs.description=ร…teranvรคndbara arbetsflรถdesjobb och utgรฅngar. +documentation.context.outputs=utgรฅngar +documentation.context.outputs.description=Utdatavรคrden exponerade av detta steg eller jobb. +documentation.context.result=resultat +documentation.context.result.description=Jobbresultat: framgรฅng, misslyckande, avbruten eller รถverhoppad. +documentation.context.outcome=resultat +documentation.context.outcome.description=Stegresultat innan fortsรคtt-pรฅ-fel tillรคmpas. +documentation.context.conclusion=slutsats +documentation.context.conclusion.description=Stegresultat efter att fortsรคtta-pรฅ-fel tillรคmpas. +error.report.action=Anmรคl undantag +error.report.description=Beskrivning +error.report.steps=Steg fรถr att reproducera +error.report.sample=Ange kodexempel om tillรคmpligt +error.report.message=Meddelande +error.report.runtime=Kรถrtidsinfo +error.report.pluginVersion=Pluginversion: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=Stacktrace +completion.shell.bash=Bash skal. Anvรคnder bash pรฅ Linux och macOS lรถpare, och Git fรถr Windows bash pรฅ Windows lรถpare. +completion.shell.sh=POSIX-skal reserv. +completion.shell.pwsh=PowerShell kรคrna. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Windows kommandotolk. +completion.shell.python=Python kommando lรถpare. +completion.runner.name=Namnet pรฅ lรถparen som utfรถr jobbet. +completion.runner.os=Operativsystemet fรถr lรถparen som utfรถr jobbet. Mรถjliga vรคrden รคr Linux, Windows eller macOS. +completion.runner.arch=Arkitekturen fรถr lรถparen som utfรถr jobbet. Mรถjliga vรคrden รคr X86, X64, ARM eller ARM64. +completion.runner.temp=Sรถkvรคgen till en tillfรคllig katalog pรฅ lรถparen. Denna katalog tรถms i bรถrjan och slutet av varje jobb. Observera att filer inte kommer att tas bort om lรถparens anvรคndarkonto inte har behรถrighet att radera dem. +completion.runner.toolCache=Sรถkvรคgen till katalogen som innehรฅller fรถrinstallerade verktyg fรถr lรถpare med GitHub-vรคrd. +completion.runner.debug=Detta stรคlls bara in om felsรถkningsloggning รคr aktiverat och har alltid vรคrdet 1. +completion.runner.environment=Miljรถn fรถr lรถparen som utfรถr jobbet. Mรถjliga vรคrden รคr github-vรคrd eller egenvรคrd. +completion.job.status=Aktuell status fรถr jobbet. +completion.job.checkRunId=Kontrollkรถrnings-ID fรถr det aktuella jobbet. +completion.job.container=Information om jobbets container. +completion.job.services=Tjรคnstecontainrarna som skapats fรถr ett jobb. +completion.job.workflowRef=Den fullstรคndiga referensen fรถr arbetsflรถdesfilen som definierar det aktuella jobbet. +completion.job.workflowSha=commit SHA fรถr arbetsflรถdesfilen som definierar det aktuella jobbet. +completion.job.workflowRepository=ร„garen/repo fรถr arkivet som innehรฅller arbetsflรถdesfilen som definierar det aktuella jobbet. +completion.job.workflowFilePath=Arbetsflรถdesfilens sรถkvรคg, i fรถrhรฅllande till arkivroten. +completion.job.containerField=Jobcontainerfรคlt +completion.job.service=Jobbservice +completion.job.serviceField=Jobbservicefรคlt +completion.job.mappedServicePort=Kartlagd servicehamn +completion.strategy.failFast=Om alla pรฅgรฅende jobb avbryts om nรฅgot matrisjobb misslyckas. +completion.strategy.jobIndex=Det nollbaserade indexet fรถr det aktuella jobbet i matrisen. +completion.strategy.jobTotal=Det totala antalet jobb i matrisen. +completion.strategy.maxParallel=Det maximala antalet matrisjobb som kan kรถras samtidigt. +completion.context.inputs=Arbetsflรถdesingรฅngar, som workflow_dispatch eller workflow_call. +completion.context.secrets=Arbetsflรถdeshemligheter. +completion.context.job=Information om det aktuella jobbet. +completion.context.jobs=Arbetsflรถdesjobb. +completion.context.matrix=Matrisegenskaper definierade fรถr det aktuella matrisjobbet. +completion.context.strategy=Matrisexekveringsstrategiinformation fรถr det aktuella jobbet. +completion.context.steps=Steg med ett id i det aktuella jobbet. +completion.context.env=Miljรถvariabler frรฅn jobb och steg. +completion.context.vars=Anpassade konfigurationsvariabler frรฅn organisation, arkiv och miljรถomfรฅng. +completion.context.needs=Jobb som mรฅste slutfรถras innan det hรคr jobbet kan kรถras, plus deras utdata och resultat. +completion.context.github=Arbetsflรถdeskรถrning och hรคndelseinformation frรฅn GitHub-kontexten. +completion.context.gitea=Gitea-kompatibelt alias fรถr GitHub Actions-kontexten. +completion.context.runner=Information om lรถparen som utfรถr det aktuella jobbet. +completion.secret.githubToken=Automatiskt skapad token fรถr varje arbetsflรถdeskรถrning. +completion.remote.repository=Fjรคrrfรถrvar +completion.uses.local.workflow=Lokalt รฅteranvรคndbart arbetsflรถde +completion.uses.local.action=Lokal handling +completion.uses.ref.known=Kรคnd arbetsflรถdesreferens +completion.uses.ref.remote=Referens fรถr fjรคrrarbetsflรถde +completion.uses.remote.known=Kรคnd fjรคrrรฅtgรคrd eller รฅteranvรคndbart arbetsflรถde +completion.workflow.syntax=GitHub Actions arbetsflรถdessyntax +completion.workflow.top.name=Visningsnamn fรถr arbetsflรถde +completion.workflow.top.run-name=Dynamiskt kรถrnamn +completion.workflow.top.on=Hรคndelser som startar arbetsflรถdet +completion.workflow.top.permissions=Standardbehรถrigheter fรถr GITHUB_TOKEN +completion.workflow.top.env=Arbetsflรถdesomfattande miljรถvariabler +completion.workflow.top.defaults=Standardinstรคllningar fรถr jobb och steg +completion.workflow.top.concurrency=Samtidighetsgrupp och avbokning +completion.workflow.top.jobs=Jobb som kรถrs i detta arbetsflรถde +completion.workflow.event.branch_protection_rule=Grenskyddet รคndrat +completion.workflow.event.check_run=Enstaka kontrollkรถrning รคndrad +completion.workflow.event.check_suite=Check svit รคndras +completion.workflow.event.create=Gren eller tagg skapad +completion.workflow.event.delete=Gren eller tagg raderad +completion.workflow.event.deployment=Implementering skapad +completion.workflow.event.deployment_status=Implementeringsstatus รคndrad +completion.workflow.event.discussion=Diskussionen รคndrades +completion.workflow.event.discussion_comment=Diskussionskommentaren รคndrad +completion.workflow.event.fork=Fรถrvaret kluven +completion.workflow.event.gollum=Wikisidan รคndrad +completion.workflow.event.image_version=Paketbildversionen har รคndrats +completion.workflow.event.issue_comment=Problemet eller PR-kommentaren har รคndrats +completion.workflow.event.issues=Problemet รคndrat +completion.workflow.event.label=Etiketten har รคndrats +completion.workflow.event.merge_group=Kontroll av sammanslagning av kรถ begรคrd +completion.workflow.event.milestone=Milstolpen รคndrades +completion.workflow.event.page_build=Sidbyggd kรถrdes +completion.workflow.event.project=Klassiskt projekt รคndrat +completion.workflow.event.project_card=Klassiskt projektkort รคndrat +completion.workflow.event.project_column=Klassisk projektkolumn รคndrad +completion.workflow.event.public=Fรถrvaret blev offentligt +completion.workflow.event.pull_request=Pull-begรคran har รคndrats +completion.workflow.event.pull_request_review=PR recension รคndrad +completion.workflow.event.pull_request_review_comment=PR recensionskommentar รคndrad +completion.workflow.event.pull_request_target=PR mรฅlkontext. Vassa knivar. +completion.workflow.event.push=Commit eller tagg tryckt +completion.workflow.event.registry_package=Paketet publicerat eller uppdaterat +completion.workflow.event.release=Utgivningen รคndrad +completion.workflow.event.repository_dispatch=Anpassad API-hรคndelse +completion.workflow.event.schedule=Cron tick. Urverk. +completion.workflow.event.status=Bekrรคftelsestatus รคndrad +completion.workflow.event.watch=Lagret stjรคrnmรคrkt +completion.workflow.event.workflow_call=ร…teranvรคndbart arbetsflรถdesanrop +completion.workflow.event.workflow_dispatch=Knapp fรถr manuell kรถrning +completion.workflow.event.workflow_run=Arbetsflรถdeskรถrningen har รคndrats +completion.workflow.eventFilter.types=Begrรคnsa aktivitetstyper +completion.workflow.eventFilter.branches=Endast dessa grenar +completion.workflow.eventFilter.branches-ignore=Hoppa รถver dessa grenar +completion.workflow.eventFilter.tags=Endast dessa taggar +completion.workflow.eventFilter.tags-ignore=Hoppa รถver dessa taggar +completion.workflow.eventFilter.paths=Bara dessa vรคgar +completion.workflow.eventFilter.paths-ignore=Hoppa รถver dessa vรคgar +completion.workflow.eventFilter.workflows=Arbetsflรถdesnamn att titta pรฅ +completion.workflow.eventFilter.cron=Cron schema. Litet urverk. +completion.workflow.permission.actions=Arbetsflรถdeskรถrningar och handlingsartefakter +completion.workflow.permission.artifact-metadata=Artefaktmetadataposter +completion.workflow.permission.attestations=Artefaktintyg +completion.workflow.permission.checks=Kontrollera kรถrningar och sviter +completion.workflow.permission.code-quality=Kodkvalitetsrapporter +completion.workflow.permission.contents=Fรถrvarets innehรฅll +completion.workflow.permission.deployments=Utplaceringar +completion.workflow.permission.discussions=Diskussioner +completion.workflow.permission.id-token=OpenID Connect-tokens +completion.workflow.permission.issues=frรฅgor +completion.workflow.permission.models=GitHub-modeller +completion.workflow.permission.packages=GitHub-paket +completion.workflow.permission.pages=GitHub sidor +completion.workflow.permission.pull-requests=Dra fรถrfrรฅgningar +completion.workflow.permission.security-events=Kodskanning och sรคkerhetshรคndelser +completion.workflow.permission.statuses=Commit statuser +completion.workflow.permission.vulnerability-alerts=Dependabot-varningar +completion.workflow.permission.value.read=Lรคsรฅtkomst +completion.workflow.permission.value.write=Skrivรฅtkomst, lรคs ingรฅr +completion.workflow.permission.value.none=Ingen tillgรฅng +completion.workflow.permission.shorthand.read-all=Alla behรถrigheter รคr lรคsta. Stor filt. +completion.workflow.permission.shorthand.write-all=Alla behรถrigheter skriver. Stor hammare. +completion.workflow.permission.shorthand.empty=Inaktivera tokenbehรถrigheter +completion.workflow.job.name=Visningsnamn fรถr jobb +completion.workflow.job.permissions=Behรถrigheter fรถr jobbtoken +completion.workflow.job.needs=Jobb att vรคnta pรฅ +completion.workflow.job.if=Arbetsvillkor +completion.workflow.job.runs-on=Lรถparetikett eller grupp +completion.workflow.job.snapshot=Lรถpare รถgonblicksbild +completion.workflow.job.environment=Implementeringsmiljรถ +completion.workflow.job.concurrency=Jobb samtidighetslรฅs +completion.workflow.job.outputs=Utgรฅngar som andra jobb kan lรคsa +completion.workflow.job.env=Arbetsmiljรถvariabler +completion.workflow.job.defaults=Standardinstรคllningar fรถr jobb +completion.workflow.job.steps=Steglista. Sjรคlva arbetet. +completion.workflow.job.timeout-minutes=Jobbet timeout pรฅ minuter +completion.workflow.job.strategy=Matris och schemalรคggningsstrategi +completion.workflow.job.continue-on-error=Lรฅt det hรคr jobbet misslyckas mjukt +completion.workflow.job.container=Container fรถr det hรคr jobbet +completion.workflow.job.services=Sidovagnsservicecontainrar +completion.workflow.job.uses=ร…teranvรคndbart arbetsflรถde att ringa +completion.workflow.job.with=Ingรฅngar fรถr anropat arbetsflรถde +completion.workflow.job.secrets=Hemligheter fรถr kallat arbetsflรถde +completion.workflow.defaultsRun.shell=Standardskal fรถr kรถrsteg +completion.workflow.defaultsRun.working-directory=Standard arbetskatalog +completion.workflow.concurrency.group=Lรฅsnamn fรถr kรถrningar i kรถ +completion.workflow.concurrency.cancel-in-progress=Avbryt รคldre matchande kรถrningar +completion.workflow.environment.name=Miljรถnamn +completion.workflow.environment.url=Miljรถ URL +completion.workflow.strategy.matrix=Matrisaxlar och varianter +completion.workflow.strategy.fail-fast=Avbryt matris syskon vid misslyckande +completion.workflow.strategy.max-parallel=Matrix parallellitet lock +completion.workflow.matrix.include=Lรคgg till matriskombinationer +completion.workflow.matrix.exclude=Ta bort matriskombinationer +completion.workflow.step.id=Steg-id fรถr referenser +completion.workflow.step.if=Stegskick +completion.workflow.step.name=Steg visningsnamn +completion.workflow.step.uses=ร…tgรคrd att kรถra +completion.workflow.step.run=Skalskript som ska kรถras +completion.workflow.step.shell=Skal fรถr detta kรถrsteg +completion.workflow.step.with=ร…tgรคrdsingรฅngar +completion.workflow.step.env=Steg miljรถvariabler +completion.workflow.step.continue-on-error=Lรฅt detta steg misslyckas mjukt +completion.workflow.step.timeout-minutes=Steg timeout pรฅ minuter +completion.workflow.step.working-directory=Steg arbetskatalog +completion.workflow.container.image=Behรฅllarbild +completion.workflow.container.credentials=Registeruppgifter +completion.workflow.container.env=Containermiljรถvariabler +completion.workflow.container.ports=Portar att exponera +completion.workflow.container.volumes=Volymer att montera +completion.workflow.container.options=Docker skapa alternativ +completion.workflow.service.image=Servicebehรฅllarebild +completion.workflow.service.credentials=Registeruppgifter +completion.workflow.service.env=Tjรคnstemiljรถvariabler +completion.workflow.service.ports=Servicehamnar +completion.workflow.service.volumes=Servicevolymer +completion.workflow.service.options=Docker skapa alternativ +completion.workflow.credentials.username=Anvรคndarnamn fรถr registret +completion.workflow.credentials.password=Registerlรถsenord eller token +completion.workflow.inputType.string=Textinmatning +completion.workflow.inputType.boolean=Sant eller falskt inmatning +completion.workflow.inputType.choice=Val i rullgardinsmenyn +completion.workflow.inputType.number=Nummerinmatning +completion.workflow.inputType.environment=Indata fรถr miljรถvรคljare +completion.workflow.boolean.true=Ja. Vรคnd pรฅ den. +completion.workflow.boolean.false=Nej. Hรฅll det mรถrkt. +completion.workflow.runner.ubuntu-latest=Senaste Ubuntu lรถparen +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 lรถpare +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 lรถpare +completion.workflow.runner.windows-latest=Senaste Windows lรถparen +completion.workflow.runner.windows-2025=Windows Server 2025 lรถpare +completion.workflow.runner.windows-2022=Windows Server 2022 lรถpare +completion.workflow.runner.macos-latest=Senaste macOS lรถparen +completion.workflow.runner.macos-15=macOS 15 lรถpare +completion.workflow.runner.macos-14=macOS 14 lรถpare +completion.workflow.runner.self-hosted=Din egen lรถpare. Din cirkus. +completion.steps.outputs=Uppsรคttningen utgรฅngar definierade fรถr steget. +completion.steps.conclusion=Resultatet av ett slutfรถrt steg efter att fortsรคtta-pรฅ-fel tillรคmpas. +completion.steps.outcome=Resultatet av ett avslutat steg innan fortsรคtt-pรฅ-fel tillรคmpas. +completion.jobs.outputs=Uppsรคttningen utgรฅngar som definierats fรถr jobbet. +completion.jobs.result=Resultatet av jobbet. +settings.displayName=GitHub arbetsflรถde +settings.language.label=Sprรฅk: +settings.language.system=IDE/systemstandard +settings.cache.title=ร…tgรคrdscache +settings.cache.column.key=Cache-nyckel +settings.cache.column.name=Namn +settings.cache.column.kind=Snรคll +settings.cache.column.state=staten +settings.cache.column.expires=Upphรถr att gรคlla +settings.cache.kind.local=lokalt +settings.cache.kind.remote=fjรคrrkontroll +settings.cache.state.resolved=lรถst +settings.cache.state.pending=vรคntande +settings.cache.state.expired=inaktuella +settings.cache.state.suppressed=undertryckt +settings.cache.refresh=Refresh tabell +settings.cache.deleteSelected=Ta bort markerade +settings.cache.deleteAll=Ta bort alla +settings.cache.export=Exportera +settings.cache.import=Importera +settings.cache.summary=Cache: {0}-poster, {1} lรถst, {2} fjรคrrkontroll, {3} inaktuell, {4} avstรคngd. Cache: {5} KB. +settings.cache.noneSelected=Vรคlj cache-rader fรถrst. Kvasten vรคgrar gissningar. +settings.cache.deleteSelected.done=Raderade {0}-cacheposter. Litet dammmoln innehรถll. +settings.cache.deleteAll.confirm=Ta bort alla GitHub Workflow-cacheposter? +settings.cache.deleteAll.done=Raderade alla cacheposter. Cachen รคr nu misstรคnkt tyst. +settings.cache.export.done=Exporterade {0}-cacheposter. Litet arkivdjur i bur. +settings.cache.import.done=Importerade cacheposter. Arkivodjuret betedde sig. +settings.cache.import.unsupported=GitHub Workflow-cachefil som inte stรถds. +settings.cache.import.brokenLine=Trasig GitHub Workflow cache-linje. +settings.cache.import.brokenKey=Trasig GitHub Workflow cache-nyckel. +settings.support.button=Stรถd detta plugin +settings.support.tooltip=ร–ppna supportsidan +settings.support.line.0=Mata byggugnen +settings.support.line.1=Kรถp parserkaffe +settings.support.line.2=Sponsra fรคrre hemsรถkta arbetsflรถden +workflow.run.jobs.title=Arbetsflรถdesjobb +workflow.run.jobs.root=Arbetsflรถdeskรถrning +workflow.run.jobs.description=GitHub Actions jobbtrรคd och vald jobblogg +workflow.run.tree.done=gjort +workflow.run.tree.failed=misslyckades +workflow.run.tree.skipped=hoppade รถver +workflow.run.tree.warn=varna +workflow.run.tree.err=fel +workflow.run.delete.tooltip=Ta bort kรถrning +workflow.run.delete.noRun=Inget kรถr-id รคnnu. +workflow.run.delete.requested=Raderar kรถrningen {0}. +workflow.run.delete.done=Kรถr {0} raderad. +workflow.run.delete.http=Ta bort HTTP {0}. Oj. +workflow.run.delete.failed=Ta bort fizzled: {0} +workflow.run.rerun.all.tooltip=Kรถr arbetsflรถdet igen +workflow.run.rerun.failed.tooltip=Kรถr misslyckade jobb igen +workflow.run.rerun.noRun=Inget kรถr-id รคnnu. +workflow.run.rerun.all.requested=Omkรถrning begรคrd: {0}. +workflow.run.rerun.failed.requested=Misslyckade jobb begรคrda: {0}. +workflow.run.rerun.all.done=Kรถr igen: {0}. +workflow.run.rerun.failed.done=Misslyckade jobb i kรถ: {0}. +workflow.run.rerun.http=Kรถr HTTP {0} igen. Oj. +workflow.run.rerun.failed=ร…terkรถrning: {0} +workflow.run.download.log.tooltip=Spara jobblogg +workflow.run.download.artifacts.tooltip=Ladda ner artefakter +workflow.run.download.noRun=Inget kรถr-id รคnnu. +workflow.run.download.log.requested=Hรคmtar logg fรถr {0}. +workflow.run.download.log.done=Loggen sparad: {0}. +workflow.run.download.artifacts.requested=Hรคmtar artefakter. +workflow.run.download.artifacts.empty=Inga artefakter. Litet tomrum. +workflow.run.download.artifact.expired=Artefakt har lรถpt ut: {0}. +workflow.run.download.artifact.done=Artefakt sparad: {0} -> {1}. +workflow.run.download.failed=Ladda ner fizzled: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_th.properties b/src/main/resources/messages/GitHubWorkflowBundle_th.properties new file mode 100644 index 0000000..73c798e --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_th.properties @@ -0,0 +1,442 @@ +plugin.name=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub +plugin.description=เธฃเธญเธ‡เธฃเธฑเธšเน„เธŸเธฅเนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub Actions +group.GitHubWorkflow.Tools.text=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub +group.GitHubWorkflow.Tools.description=เน€เธ„เธฃเธทเนˆเธญเธ‡เธกเธทเธญเธ›เธฅเธฑเนŠเธเธญเธดเธ™ GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=เนเธ„เธŠเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ Refresh +action.GitHubWorkflow.RefreshActionCache.description=Refresh เนเธเน‰เน„เธ‚เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ GitHub เธฃเธฐเธขเธฐเน„เธเธฅเนเธฅเธฐเธ‚เน‰เธญเธกเธนเธฅเน€เธกเธ•เธฒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธ™เธณเธกเธฒเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰ +action.GitHubWorkflow.RestoreActionWarnings.text=เธ„เธทเธ™เธ„เนˆเธฒเธ„เธณเน€เธ•เธทเธญเธ™เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ +action.GitHubWorkflow.RestoreActionWarnings.description=เธ„เธทเธ™เธ„เนˆเธฒเธ„เธณเน€เธ•เธทเธญเธ™เธเธฒเธฃเธ•เธฃเธงเธˆเธชเธญเธšเธ„เธงเธฒเธกเธ–เธนเธเธ•เน‰เธญเธ‡เธ‚เธญเธ‡เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ เธญเธดเธ™เธžเธธเธ• เนเธฅเธฐเน€เธญเธฒเธ•เนŒเธžเธธเธ•เธ—เธตเนˆเธ–เธนเธเธฃเธฐเธ‡เธฑเธš +action.GitHubWorkflow.ClearActionCache.text=เธฅเน‰เธฒเธ‡เนเธ„เธŠเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ +action.GitHubWorkflow.ClearActionCache.description=เธฅเน‰เธฒเธ‡เนเธ„เธŠเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ GitHub เนเธฅเธฐเธ‚เน‰เธญเธกเธนเธฅเน€เธกเธ•เธฒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธ™เธณเธกเธฒเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰ +notification.cache.cleared=เธฅเน‰เธฒเธ‡เธฃเธฒเธขเธเธฒเธฃเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ {0} เธ—เธตเนˆเนเธ„เธŠเน„เธงเน‰ GitHub +notification.cache.refresh.started=Refreshing {0} เนเธ„เธŠเธฃเธฒเธขเธเธฒเธฃเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub เธฃเธฐเธขเธฐเน„เธเธฅ +notification.warnings.restored=เธเธนเน‰เธ„เธทเธ™เธ„เธณเน€เธ•เธทเธญเธ™เธชเธณเธซเธฃเธฑเธšเธฃเธฒเธขเธเธฒเธฃเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ {0} GitHub +workflow.run.configuration.display=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub +workflow.run.configuration.description=เธˆเธฑเธ”เธชเนˆเธ‡เนเธฅเธฐเธ•เธดเธ”เธ•เธฒเธกเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ GitHub +workflow.run.configuration.name=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=เน€เธˆเน‰เธฒเธ‚เธญเธ‡ +workflow.run.field.repo=เธžเธทเน‰เธ™เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅ +workflow.run.field.workflow=เน„เธŸเธฅเนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Token env เน€เธ›เน‡เธ™เธ—เธฒเธ‡เน€เธฅเธทเธญเธเธชเธณเธฃเธญเธ‡ +workflow.run.inputs.title=เธญเธดเธ™เธžเธธเธ• workflow_dispatch (เธ„เธตเธขเนŒ=เธ„เนˆเธฒ) +workflow.run.error.apiUrl=เธ•เน‰เธญเธ‡เนƒเธŠเน‰ GitHub API URL +workflow.run.error.repository=เธ•เน‰เธญเธ‡เธฃเธฐเธšเธธเน€เธˆเน‰เธฒเธ‚เธญเธ‡เธ—เธตเนˆเน€เธเน‡เธš GitHub เนเธฅเธฐเธŠเธทเนˆเธญ +workflow.run.error.workflow=เธˆเธณเน€เธ›เน‡เธ™เธ•เน‰เธญเธ‡เธกเธตเน„เธŸเธฅเนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +workflow.run.error.ref=เธˆเธณเน€เธ›เน‡เธ™เธ•เน‰เธญเธ‡เธกเธตเธเธฒเธฃเธญเน‰เธฒเธ‡เธญเธดเธ‡เธชเธฒเธ‚เธฒเธซเธฃเธทเธญเนเธ—เน‡เธ +workflow.run.error.inputs=GitHub workflow_dispatch เธฃเธญเธ‡เธฃเธฑเธšเธญเธดเธ™เธžเธธเธ•เน„เธ”เน‰เธชเธนเธ‡เธชเธธเธ” 25 เธญเธดเธ™เธžเธธเธ• +workflow.run.gutter.stop=เธซเธขเธธเธ”เธเธฒเธฃเธฃเธฑเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +workflow.run.gutter.stop.text=เธซเธขเธธเธ”เธเธฒเธฃเธฃเธฑเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +workflow.run.gutter.stop.description=เธขเธเน€เธฅเธดเธเธเธฒเธฃเธงเธดเนˆเธ‡เธ„เธฃเธฑเน‰เธ‡เธ™เธตเน‰ +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=เธงเธดเนˆเธ‡: +workflow.log.warning=เธ„เธณเน€เธ•เธทเธญเธ™: +workflow.log.error=เธ‚เน‰เธญเธœเธดเธ”เธžเธฅเธฒเธ”: +workflow.run.cancel.requested=เธขเธเน€เธฅเธดเธเธ—เธตเนˆเธฃเน‰เธญเธ‡เธ‚เธญ: {0} +workflow.run.stop.before.id=เธซเธขเธธเธ”เธฃเน‰เธญเธ‡เธ‚เธญเนเธฅเน‰เธง เธขเธฑเธ‡เน„เธกเนˆเธกเธตเธฃเธซเธฑเธชเธฃเธฑเธ™ +workflow.run.cancel.http=เธขเธเน€เธฅเธดเธ HTTP {0} เธญเนŠเธญเธŸ. +workflow.run.cancel.failed=เธขเธเน€เธฅเธดเธเธกเธญเธ”: {0} +workflow.run.interrupted=เธ‚เธฑเธ”เธˆเธฑเธ‡เธซเธงเธฐ. +workflow.run.link=เธฃเธฑเธ™: {0} +workflow.run.discovery=เธขเธญเธกเธฃเธฑเธšเธเธฒเธฃเธฃเธฑเธ™เนเธฅเน‰เธง เธฃเธซเธฑเธชเธงเธดเนˆเธ‡เธฅเนˆเธฒเธชเธฑเธ•เธงเนŒ +workflow.run.discovery.none=เธขเธฑเธ‡เน„เธกเนˆเธกเธตเธฃเธซเธฑเธชเธฃเธฑเธ™ เนเธ—เน‡เธšเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃเธฃเธนเน‰เธกเธฒเธเธ‚เธถเน‰เธ™ +workflow.run.status=เธชเธ–เธฒเธ™เธฐ: {0}{1} +workflow.run.job.main=เธ‡เธฒเธ™: {0} {1} [{2}{3}{4}] +workflow.run.job.status=เธชเธ–เธฒเธ™เธฐ: {0} {1}{2}{3} +workflow.run.logs.later=เธšเธฑเธ™เธ—เธถเธเธˆเธฐเธ›เธฃเธฒเธเธเธ‚เธถเน‰เธ™เน€เธกเธทเนˆเธญ GitHub เน€เธœเธขเนเธžเธฃเนˆ +workflow.run.job.logs.later=เธ‡เธฒเธ™ {0}: {1} +workflow.run.log.failed=เธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ”เธšเธฑเธ™เธ—เธถเธเธฅเน‰เธกเน€เธซเธฅเธง: {0} +workflow.run.log.failed.job=เธเธฒเธฃเธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ”เธšเธฑเธ™เธ—เธถเธเธฅเน‰เธกเน€เธซเธฅเธงเธชเธณเธซเธฃเธฑเธš {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=เธ‡เธฒเธ™: {0} +workflow.run.job.fallbackName=เธ‡เธฒเธ™ {0} +workflow.run.overview=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธฃเธฑเธ™ {0} {1}/{2} เน€เธชเธฃเน‡เธˆเนเธฅเน‰เธง {3} เธเธณเธฅเธฑเธ‡เธ—เธณเธ‡เธฒเธ™เธญเธขเธนเนˆ +workflow.run.state.ok=[เธ•เธเธฅเธ‡] +workflow.run.state.fail=[เธฅเน‰เธกเน€เธซเธฅเธง] +workflow.run.state.running=[เธงเธดเนˆเธ‡] +workflow.run.state.waiting=[เธฃเธญ] +workflow.run.dispatch.verbs=เธเธฒเธฃเธฃเธญเธ‡เธžเธทเน‰เธ™|เธเธฒเธฃเน€เธ‚เน‰เธฒเธ„เธดเธง|เธเธฒเธฃเน€เธฃเธตเธขเธ|เธเธฒเธฃเน€เธ›เธดเธ”เธ•เธฑเธง|เธเธฒเธฃเธšเธนเธ— +workflow.run.dispatch.objects=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ | เธฃเธฐเธšเธšเธญเธฑเธ•เน‚เธ™เธกเธฑเธ•เธด | เน„เธ›เธ›เนŒเน„เธฅเธ™เนŒ | เน€เธฃเธตเธขเธเนƒเธŠเน‰ +workflow.run.dispatch={0} {1} {2}{3} เธชเธณเธซเธฃเธฑเธš {4} เธšเธ™ {5} +workflow.run.notification.auth=เธเธฒเธฃเธˆเธฑเธ”เธชเนˆเธ‡เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub เธˆเธณเน€เธ›เน‡เธ™เธ•เน‰เธญเธ‡เธกเธตเธšเธฑเธเธŠเธต GitHub เธ—เธตเนˆเน„เธ”เน‰เธฃเธฑเธšเธเธฒเธฃเธ•เธฃเธงเธˆเธชเธญเธšเธชเธดเธ—เธ˜เธดเนŒ เน€เธžเธดเนˆเธกเธซเธฃเธทเธญเธฃเธตเน€เธŸเธฃเธŠเธšเธฑเธเธŠเธตเนƒเธ™ {0} +workflow.run.notification.openSettings=เน€เธ›เธดเธ”เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒ GitHub +workflow.cache.progress.title=เธเธฒเธฃเนเธเน‰เน„เธ‚เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ GitHub +workflow.cache.progress.text=เธเธณเธฅเธฑเธ‡เนเธเน‰เน„เธ‚ {0} {1} +workflow.cache.kind.action=เธเธฒเธฃเธเธฃเธฐเธ—เธณ +workflow.cache.kind.workflow=เธ‚เธฑเน‰เธ™เธ•เธญเธ™เธเธฒเธฃเธ—เธณเธ‡เธฒเธ™ +inspection.parameter.input=เธญเธดเธ™เธžเธธเธ• +inspection.parameter.secret=เธ„เธงเธฒเธกเธฅเธฑเธš +inspection.action.delete.invalid=เธฅเธš {0} เธ—เธตเนˆเน„เธกเนˆเธ–เธนเธเธ•เน‰เธญเธ‡ [{1}] +inspection.action.update.major=เธญเธฑเธ›เน€เธ”เธ•เธเธฒเธฃเธเธฃเธฐเธ—เธณ [{0}] เน€เธ›เน‡เธ™ [{1}] +inspection.warning.toggle=เธชเธฅเธฑเธšเธ„เธณเน€เธ•เธทเธญเธ™ [{0}] เธชเธณเธซเธฃเธฑเธš [{1}] +inspection.warning.on=เธšเธ™ +inspection.warning.off=เธ›เธดเธ” +inspection.statement.incomplete=เธ„เธณเธชเธฑเนˆเธ‡เธ—เธตเนˆเน„เธกเนˆเธชเธกเธšเธนเธฃเธ“เนŒ [{0}] +inspection.invalid.suffix.remove=เธฅเธšเธชเนˆเธงเธ™เธ•เนˆเธญเธ—เน‰เธฒเธขเธ—เธตเนˆเน„เธกเนˆเธ–เธนเธเธ•เน‰เธญเธ‡ [{0}] +inspection.replace.with=เนเธ—เธ™เธ—เธตเนˆเธ”เน‰เธงเธข [{0}] +inspection.invalid.remove=เธฅเธšเธ—เธตเนˆเน„เธกเนˆเธ–เธนเธเธ•เน‰เธญเธ‡ [{0}] +inspection.workflow.syntax.unknownTopLevelKey=เธ„เธตเธขเนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเน„เธกเนˆเธฃเธนเน‰เธˆเธฑเธ [{0}] +inspection.workflow.syntax.unknownEventKey=เน€เธซเธ•เธธเธเธฒเธฃเธ“เนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเน„เธกเนˆเธฃเธนเน‰เธˆเธฑเธ [{0}] +inspection.workflow.syntax.unknownTriggerKey=เธ„เธตเธขเนŒเธ—เธฃเธดเธเน€เธเธญเธฃเนŒเธ—เธตเนˆเน„เธกเนˆเธฃเธนเน‰เธˆเธฑเธ [{0}] +inspection.workflow.syntax.unknownTriggerFilter=เธ•เธฑเธงเธเธฃเธญเธ‡เธ—เธฃเธดเธเน€เธเธญเธฃเนŒเธ—เธตเนˆเน„เธกเนˆเธฃเธนเน‰เธˆเธฑเธ [{0}] +inspection.workflow.syntax.unknownTriggerValue=เธ„เนˆเธฒเธ—เธฃเธดเธเน€เธเธญเธฃเนŒเธ—เธตเนˆเน„เธกเนˆเธฃเธนเน‰เธˆเธฑเธ [{0}] +inspection.workflow.syntax.unknownPermission=เธชเธดเธ—เธ˜เธดเนŒเธ—เธตเนˆเน„เธกเนˆเธฃเธนเน‰เธˆเธฑเธ [{0}] +inspection.workflow.syntax.unknownPermissionValue=เธ„เนˆเธฒเธชเธดเธ—เธ˜เธดเนŒเธ—เธตเนˆเน„เธกเนˆเธฃเธนเน‰เธˆเธฑเธ [{0}] +inspection.workflow.syntax.unknownJobKey=เธฃเธซเธฑเธชเธ‡เธฒเธ™เธ—เธตเนˆเน„เธกเนˆเธฃเธนเน‰เธˆเธฑเธ [{0}] +inspection.workflow.syntax.unknownStepKey=เธฃเธซเธฑเธชเธ‚เธฑเน‰เธ™เธ•เธญเธ™เธ—เธตเนˆเน„เธกเนˆเธฃเธนเน‰เธˆเธฑเธ [{0}] +inspection.action.reload=เน‚เธซเธฅเธ”เธ‹เน‰เธณ [{0}] +inspection.action.unresolved=เธขเธฑเธ‡เน„เธกเนˆเน„เธ”เน‰เธฃเธฑเธšเธเธฒเธฃเนเธเน‰เน„เธ‚ [{0}] - เธ•เธฃเธงเธˆเธชเธญเธšเธเธฒเธฃเน€เธ‚เน‰เธฒเธ–เธถเธ‡เธšเธฑเธเธŠเธต GitHub, เธชเธดเธ—เธ˜เธดเนŒเธ‚เธญเธ‡เธžเธทเน‰เธ™เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅเธชเนˆเธงเธ™เธ•เธฑเธง, เธ‚เธตเธ”เธˆเธณเธเธฑเธ”เธญเธฑเธ•เธฃเธฒ, เธ‚เน‰เธญเธกเธนเธฅเธญเน‰เธฒเธ‡เธญเธดเธ‡เธซเธฒเธขเน„เธ› เธซเธฃเธทเธญเธ‚เน‰เธญเธกเธนเธฅเน€เธกเธ•เธฒเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ/เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธซเธฒเธขเน„เธ› +inspection.action.jump=เธ‚เน‰เธฒเธกเน„เธ›เธ—เธตเนˆเน„เธŸเธฅเนŒ [{0}] +inspection.output.unused=เน„เธกเนˆเน„เธ”เน‰เนƒเธŠเน‰ [{0}] +inspection.secret.invalid.if=เธฅเธš [{0}] - เธ‚เน‰เธญเธกเธนเธฅเธฅเธฑเธšเน„เธกเนˆเธ–เธนเธเธ•เน‰เธญเธ‡เนƒเธ™เธ„เธณเธชเธฑเนˆเธ‡ "if" +inspection.secret.replace.runtime=เนเธ—เธ™เธ—เธตเนˆ [{0}] เธ”เน‰เธงเธข [{1}] - เธซเธฒเธเน„เธกเนˆเน„เธ”เน‰เธฃเธฐเธšเธธเน„เธงเน‰เธ‚เธ“เธฐเธฃเธฑเธ™เน„เธ—เธกเนŒ +inspection.needs.invalid.job=เธฅเธš jobId เธ—เธตเนˆเน„เธกเนˆเธ–เธนเธเธ•เน‰เธญเธ‡ [{0}] - jobId เธ™เธตเน‰เน„เธกเนˆเธ•เธฃเธ‡เธเธฑเธšเธ‡เธฒเธ™เธเนˆเธญเธ™เธซเธ™เน‰เธฒเนƒเธ”เน† +documentation.description=เธ„เธณเธญเธ˜เธดเธšเธฒเธข: {0} +documentation.type=เธ›เธฃเธฐเน€เธ เธ—: {0} +documentation.required=เธˆเธณเน€เธ›เน‡เธ™: {0} +documentation.default=เธ”เธตเธŸเธญเธฅเธ•เนŒ: {0} +documentation.deprecated=เน€เธฅเธดเธเนƒเธŠเน‰เนเธฅเน‰เธง: {0} +documentation.open.declaration=เน€เธ›เธดเธ”เธเธฒเธฃเธ›เธฃเธฐเธเธฒเธจ ({0}) +documentation.input.label=เธญเธดเธ™เธžเธธเธ• +documentation.secret.label=เธ„เธงเธฒเธกเธฅเธฑเธš +documentation.env.label=เธ•เธฑเธงเนเธ›เธฃเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธก +documentation.matrix.label=เธ„เธธเธ“เธชเธกเธšเธฑเธ•เธดเน€เธกเธ—เธฃเธดเธเธ‹เนŒ +documentation.need.label=เธ‡เธฒเธ™เธ—เธตเนˆเธ•เน‰เธญเธ‡เธเธฒเธฃ +documentation.need.description=เธเธฒเธฃเธžเธถเนˆเธ‡เธžเธฒเธ‡เธฒเธ™เน‚เธ”เธขเธ•เธฃเธ‡ +documentation.needOutput.label=เธœเธฅเธœเธฅเธดเธ•เธ‡เธฒเธ™เธ—เธตเนˆเธ•เน‰เธญเธ‡เธเธฒเธฃ +documentation.reusableJob.label=เธ‡เธฒเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰ +documentation.reusableJob.description=เธ›เธฃเธฐเธเธฒเธจเธ‡เธฒเธ™เนƒเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰เธ™เธตเน‰ +documentation.reusableJobOutput.label=เน€เธญเธฒเธ•เนŒเธžเธธเธ•เธ‡เธฒเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰ +documentation.service.label=เธ เธฒเธŠเธ™เธฐเธšเธฃเธดเธเธฒเธฃ +documentation.servicePort.label=เธžเธญเธฃเนŒเธ•เธšเธฃเธดเธเธฒเธฃ +documentation.container.label=เธ•เธนเน‰เนƒเธชเนˆเธ‡เธฒเธ™ +documentation.symbol.label=เธชเธฑเธเธฅเธฑเธเธฉเธ“เนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +documentation.symbol.description=เธ™เธดเธžเธˆเธ™เนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเน„เธ”เน‰เธฃเธฑเธšเธเธฒเธฃเนเธเน‰เน„เธ‚เนเธฅเน‰เธง +documentation.workflowOutput.label=เน€เธญเธฒเธ—เนŒเธžเธธเธ•เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +documentation.jobOutput.label=เธœเธฅเธœเธฅเธดเธ•เธ‡เธฒเธ™ +documentation.action.label=เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ +documentation.externalAction.label=เธเธฒเธฃเธเธฃเธฐเธ—เธณเธ เธฒเธขเธ™เธญเธ +documentation.reusableWorkflow.label=เธ‚เธฑเน‰เธ™เธ•เธญเธ™เธเธฒเธฃเธ—เธณเธ‡เธฒเธ™เนเธšเธšเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰ +documentation.resolvedFrom=เนเธเน‰เน„เธ‚เธˆเธฒเธ {0} +documentation.notResolved=เธขเธฑเธ‡เน„เธกเนˆเน„เธ”เน‰เธฃเธฑเธšเธเธฒเธฃเนเธเน‰เน„เธ‚ +documentation.inputs.title=เธญเธดเธ™เธžเธธเธ• +documentation.outputs.title=เน€เธญเธฒเธ—เนŒเธžเธธเธ• +documentation.secrets.title=เธ„เธงเธฒเธกเธฅเธฑเธš +documentation.value.label=เธ„เธงเธฒเธกเธ„เธธเน‰เธกเธ„เนˆเธฒ +documentation.step.title=เธ‚เธฑเน‰เธ™เธ•เธญเธ™เธ—เธตเนˆ {0} +documentation.name.label=เธŠเธทเนˆเธญ +documentation.uses.label=เธเธฒเธฃเนƒเธŠเน‰เธ‡เธฒเธ™ +documentation.run.label=เธงเธดเนˆเธ‡ +documentation.description.label=เธ„เธณเธญเธ˜เธดเธšเธฒเธข +documentation.step.label=เธ‚เธฑเน‰เธ™เธ•เธญเธ™ +documentation.source.label=เนเธซเธฅเนˆเธ‡เธ—เธตเนˆเธกเธฒ +documentation.stepOutput.label=เธ‚เธฑเน‰เธ™เธ•เธญเธ™เธเธฒเธฃเธชเนˆเธ‡เธญเธญเธ +documentation.context.github=เธšเธฃเธดเธšเธ— GitHub +documentation.context.github.description=เธ‚เน‰เธญเธกเธนเธฅเน€เธเธตเนˆเธขเธงเธเธฑเธšเธเธฒเธฃเธฃเธฑเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ›เธฑเธˆเธˆเธธเธšเธฑเธ™เนเธฅเธฐเน€เธซเธ•เธธเธเธฒเธฃเธ“เนŒ +documentation.context.gitea=เธšเธฃเธดเธšเธ—เธ‚เธญเธ‡เธเธดเน€เธ—เธตเธข +documentation.context.gitea.description=เธ™เธฒเธกเนเธเธ‡เธ—เธตเนˆเน€เธ‚เน‰เธฒเธเธฑเธ™เน„เธ”เน‰เธเธฑเธš Gitea เธชเธณเธซเธฃเธฑเธšเธšเธฃเธดเธšเธ—เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ GitHub +documentation.context.inputs=เธšเธฃเธดเธšเธ—เธญเธดเธ™เธžเธธเธ• +documentation.context.inputs.description=เธ‚เน‰เธญเธกเธนเธฅเธ‚เธฑเน‰เธ™เธ•เธญเธ™เธเธฒเธฃเธ—เธณเธ‡เธฒเธ™ เธเธฒเธฃเธˆเธฑเธ”เธชเนˆเธ‡ เธซเธฃเธทเธญเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃเธกเธตเธญเธขเธนเนˆเธ—เธตเนˆเธ™เธตเนˆ +documentation.context.secrets=เธšเธฃเธดเธšเธ—เธ„เธงเธฒเธกเธฅเธฑเธš +documentation.context.secrets.description=เธ„เนˆเธฒเธฅเธฑเธšเธ—เธตเนˆเนƒเธŠเน‰เน„เธ”เน‰เธเธฑเธšเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ™เธตเน‰เธซเธฃเธทเธญเธเธฒเธฃเน€เธฃเธตเธขเธเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธ™เธณเธกเธฒเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰ +documentation.context.env=เธšเธฃเธดเธšเธ—เธชเธดเนˆเธ‡เนเธงเธ”เธฅเน‰เธญเธก +documentation.context.env.description=เธ•เธฑเธงเนเธ›เธฃเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธกเธ—เธตเนˆเธกเธญเธ‡เน€เธซเน‡เธ™เน„เธ”เน‰เนƒเธ™เธ•เธณเนเธซเธ™เนˆเธ‡เธ™เธตเน‰ +documentation.context.matrix=เธšเธฃเธดเธšเธ—เน€เธกเธ—เธฃเธดเธเธ‹เนŒ +documentation.context.matrix.description=เธ„เนˆเธฒเน€เธกเธ—เธฃเธดเธเธ‹เนŒเธชเธณเธซเธฃเธฑเธšเธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +documentation.context.steps=เธšเธฃเธดเธšเธ—เธ‚เธญเธ‡เธ‚เธฑเน‰เธ™เธ•เธญเธ™ +documentation.context.steps.description=เธ‚เธฑเน‰เธ™เธ•เธญเธ™เธเนˆเธญเธ™เธซเธ™เน‰เธฒเนƒเธ™เธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ เธฃเธงเธกเธ–เธถเธ‡เน€เธญเธฒเธ•เนŒเธžเธธเธ•เนเธฅเธฐเธชเธ–เธฒเธ™เธฐ +documentation.context.needs=เธ•เน‰เธญเธ‡เธเธฒเธฃเธšเธฃเธดเธšเธ— +documentation.context.needs.description=เธเธฒเธฃเธžเธถเนˆเธ‡เธžเธฒเธ‡เธฒเธ™เน‚เธ”เธขเธ•เธฃเธ‡เนเธฅเธฐเธœเธฅเธฅเธฑเธžเธ˜เนŒ/เธœเธฅเธฅเธฑเธžเธ˜เนŒ +documentation.context.jobs=เธšเธฃเธดเธšเธ—เธ‡เธฒเธ™ +documentation.context.jobs.description=เธ‡เธฒเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเนเธฅเธฐเน€เธญเธฒเธ•เนŒเธžเธธเธ•เธ—เธตเนˆเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰ +documentation.context.outputs=เน€เธญเธฒเธ—เนŒเธžเธธเธ— +documentation.context.outputs.description=เธ„เนˆเธฒเน€เธญเธฒเธ•เนŒเธžเธธเธ•เธ—เธตเนˆเนเธชเธ”เธ‡เน‚เธ”เธขเธ‚เธฑเน‰เธ™เธ•เธญเธ™เธซเธฃเธทเธญเธ‡เธฒเธ™เธ™เธตเน‰ +documentation.context.result=เธœเธฅเธฅเธฑเธžเธ˜เนŒ +documentation.context.result.description=เธœเธฅเธฅเธฑเธžเธ˜เนŒเธ‚เธญเธ‡เธ‡เธฒเธ™: เธชเธณเน€เธฃเน‡เธˆ เธฅเน‰เธกเน€เธซเธฅเธง เธขเธเน€เธฅเธดเธ เธซเธฃเธทเธญเธ‚เน‰เธฒเธกเน„เธ› +documentation.context.outcome=เธœเธฅเธฅเธฑเธžเธ˜เนŒ +documentation.context.outcome.description=เธœเธฅเธฅเธฑเธžเธ˜เนŒเธ‚เธฑเน‰เธ™เธ•เธญเธ™เธเนˆเธญเธ™เธ—เธตเนˆเธˆเธฐเนƒเธŠเน‰เธ‚เน‰เธญเธœเธดเธ”เธžเธฅเธฒเธ”เธ•เนˆเธญเน„เธ› +documentation.context.conclusion=เธ‚เน‰เธญเธชเธฃเธธเธ› +documentation.context.conclusion.description=เธœเธฅเธฅเธฑเธžเธ˜เนŒเธ‚เธฑเน‰เธ™เธ•เธญเธ™เธซเธฅเธฑเธ‡เธˆเธฒเธเนƒเธŠเน‰เธ‚เน‰เธญเธœเธดเธ”เธžเธฅเธฒเธ”เธ•เนˆเธญเน„เธ› +error.report.action=เธฃเธฒเธขเธ‡เธฒเธ™เธ‚เน‰เธญเธขเธเน€เธงเน‰เธ™ +error.report.description=เธ„เธณเธญเธ˜เธดเธšเธฒเธข +error.report.steps=เธ‚เธฑเน‰เธ™เธ•เธญเธ™เนƒเธ™เธเธฒเธฃเธชเธทเธšเธžเธฑเธ™เธ˜เธธเนŒ +error.report.sample=เน‚เธ›เธฃเธ”เธฃเธฐเธšเธธเธ•เธฑเธงเธญเธขเนˆเธฒเธ‡เน‚เธ„เน‰เธ”เธซเธฒเธเธกเธต +error.report.message=เธ‚เน‰เธญเธ„เธงเธฒเธก +error.report.runtime=เธ‚เน‰เธญเธกเธนเธฅเธฃเธฑเธ™เน„เธ—เธกเนŒ +error.report.pluginVersion=เน€เธงเธญเธฃเนŒเธŠเธฑเธ™เธ›เธฅเธฑเนŠเธเธญเธดเธ™: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=เธชเนเธ•เน‡เธ„เน€เธ—เธฃเธ‹ +completion.shell.bash=เน€เธŠเธฅเธฅเนŒ Bash เนƒเธŠเน‰ bash เธšเธ™เธ™เธฑเธเธงเธดเนˆเธ‡ Linux เนเธฅเธฐ macOS เนเธฅเธฐ Git เธชเธณเธซเธฃเธฑเธš Windows bash เธšเธ™เธ™เธฑเธเธงเธดเนˆเธ‡ Windows +completion.shell.sh=เธ—เธฒเธ‡เน€เธฅเธทเธญเธเน€เธŠเธฅเธฅเนŒ POSIX +completion.shell.pwsh=เนเธเธ™ PowerShell +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=เธžเธฃเธญเธกเธ•เนŒเธ„เธณเธชเธฑเนˆเธ‡ Windows +completion.shell.python=เธ•เธฑเธงเธฃเธฑเธ™เธ„เธณเธชเธฑเนˆเธ‡ Python +completion.runner.name=เธŠเธทเนˆเธญเธ™เธฑเธเธงเธดเนˆเธ‡เธ—เธตเนˆเธ›เธเธดเธšเธฑเธ•เธดเธ‡เธฒเธ™ +completion.runner.os=เธฃเธฐเธšเธšเธ›เธเธดเธšเธฑเธ•เธดเธเธฒเธฃเธ‚เธญเธ‡เธ™เธฑเธเธงเธดเนˆเธ‡เธ—เธตเนˆเธ›เธเธดเธšเธฑเธ•เธดเธ‡เธฒเธ™ เธ„เนˆเธฒเธ—เธตเนˆเน€เธ›เน‡เธ™เน„เธ›เน„เธ”เน‰เธ„เธทเธญ Linux, Windows เธซเธฃเธทเธญ macOS +completion.runner.arch=เธชเธ–เธฒเธ›เธฑเธ•เธขเธเธฃเธฃเธกเธ‚เธญเธ‡เธ™เธฑเธเธงเธดเนˆเธ‡เธ—เธตเนˆเธ›เธเธดเธšเธฑเธ•เธดเธ‡เธฒเธ™ เธ„เนˆเธฒเธ—เธตเนˆเน€เธ›เน‡เธ™เน„เธ›เน„เธ”เน‰เธ„เธทเธญ X86, X64, ARM เธซเธฃเธทเธญ ARM64 +completion.runner.temp=เน€เธชเน‰เธ™เธ—เธฒเธ‡เน„เธ›เธขเธฑเธ‡เน„เธ”เน€เธฃเน‡เธเธ—เธญเธฃเธตเธŠเธฑเนˆเธงเธ„เธฃเธฒเธงเธšเธ™เธฃเธฑเธ™เน€เธ™เธญเธฃเนŒ เน„เธ”เน€เธฃเน‡เธเธ—เธญเธฃเธตเธ™เธตเน‰เธงเนˆเธฒเธ‡เน€เธ›เธฅเนˆเธฒเธ—เธตเนˆเธˆเธธเธ”เน€เธฃเธดเนˆเธกเธ•เน‰เธ™เนเธฅเธฐเธˆเธธเธ”เธชเธดเน‰เธ™เธชเธธเธ”เธ‚เธญเธ‡เนเธ•เนˆเธฅเธฐเธ‡เธฒเธ™ เน‚เธ›เธฃเธ”เธ—เธฃเธฒเธšเธงเนˆเธฒเน„เธŸเธฅเนŒเธˆเธฐเน„เธกเนˆเธ–เธนเธเธฅเธšเธญเธญเธเธซเธฒเธเธšเธฑเธเธŠเธตเธœเธนเน‰เนƒเธŠเน‰เธ‚เธญเธ‡เธ™เธฑเธเธงเธดเนˆเธ‡เน„เธกเนˆเน„เธ”เน‰เธฃเธฑเธšเธญเธ™เธธเธเธฒเธ•เนƒเธซเน‰เธฅเธšเธญเธญเธ +completion.runner.toolCache=เน€เธชเน‰เธ™เธ—เธฒเธ‡เน„เธ›เธขเธฑเธ‡เน„เธ”เน€เธฃเน‡เธเธ—เธญเธฃเธตเธ—เธตเนˆเธกเธตเน€เธ„เธฃเธทเนˆเธญเธ‡เธกเธทเธญเธ—เธตเนˆเธ•เธดเธ”เธ•เธฑเน‰เธ‡เน„เธงเน‰เธฅเนˆเธงเธ‡เธซเธ™เน‰เธฒเธชเธณเธซเธฃเธฑเธšเธฃเธฑเธ™เน€เธ™เธญเธฃเนŒเธ—เธตเนˆเน‚เธฎเธชเธ•เนŒ GitHub +completion.runner.debug=เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒเธ™เธตเน‰เธˆเธฐเธ–เธนเธเธ•เธฑเน‰เธ‡เธ„เนˆเธฒเน€เธ‰เธžเธฒเธฐเน€เธกเธทเนˆเธญเธกเธตเธเธฒเธฃเน€เธ›เธดเธ”เนƒเธŠเน‰เธ‡เธฒเธ™เธเธฒเธฃเธšเธฑเธ™เธ—เธถเธเธเธฒเธฃเนเธเน‰เน„เธ‚เธ‚เน‰เธญเธšเธเธžเธฃเนˆเธญเธ‡ เนเธฅเธฐเธกเธตเธ„เนˆเธฒเน€เธ›เน‡เธ™ 1 เน€เธชเธกเธญ +completion.runner.environment=เธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธกเธ‚เธญเธ‡เธ™เธฑเธเธงเธดเนˆเธ‡เธ—เธตเนˆเธ›เธเธดเธšเธฑเธ•เธดเธ‡เธฒเธ™ เธ„เนˆเธฒเธ—เธตเนˆเน€เธ›เน‡เธ™เน„เธ›เน„เธ”เน‰เธ„เธทเธญเน‚เธฎเธชเธ•เนŒเนเธšเธš GitHub เธซเธฃเธทเธญเน‚เธฎเธชเธ•เนŒเธ”เน‰เธงเธขเธ•เธ™เน€เธญเธ‡ +completion.job.status=เธชเธ–เธฒเธ™เธฐเธ›เธฑเธˆเธˆเธธเธšเธฑเธ™เธ‚เธญเธ‡เธ‡เธฒเธ™ +completion.job.checkRunId=เธ•เธฃเธงเธˆเธชเธญเธš ID เธฃเธฑเธ™เธ‚เธญเธ‡เธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +completion.job.container=เธ‚เน‰เธญเธกเธนเธฅเน€เธเธตเนˆเธขเธงเธเธฑเธšเธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒเธ‚เธญเธ‡เธ‡เธฒเธ™ +completion.job.services=เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒเธšเธฃเธดเธเธฒเธฃเธ—เธตเนˆเธชเธฃเน‰เธฒเธ‡เธ‚เธถเน‰เธ™เธชเธณเธซเธฃเธฑเธšเธ‡เธฒเธ™ +completion.job.workflowRef=เธเธฒเธฃเธญเน‰เธฒเธ‡เธญเธดเธ‡เนเธšเธšเน€เธ•เน‡เธกเธ‚เธญเธ‡เน„เธŸเธฅเนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธเธณเธซเธ™เธ”เธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +completion.job.workflowSha=เธ„เธญเธกเธกเธดเธ• SHA เธ‚เธญเธ‡เน„เธŸเธฅเนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธเธณเธซเธ™เธ”เธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +completion.job.workflowRepository=เน€เธˆเน‰เธฒเธ‚เธญเธ‡/repo เธ‚เธญเธ‡เธ—เธตเนˆเน€เธเน‡เธšเธ—เธตเนˆเธกเธตเน„เธŸเธฅเนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธเธณเธซเธ™เธ”เธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +completion.job.workflowFilePath=เธžเธฒเธ˜เธ‚เธญเธ‡เน„เธŸเธฅเนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ เธชเธฑเธกเธžเธฑเธ™เธ˜เนŒเธเธฑเธšเธฃเธนเธ—เธ‚เธญเธ‡เธ—เธตเนˆเน€เธเน‡เธš +completion.job.containerField=เธŸเธดเธฅเธ”เนŒเธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒเธ‡เธฒเธ™ +completion.job.service=เธšเธฃเธดเธเธฒเธฃเธ‡เธฒเธ™ +completion.job.serviceField=เธชเธฒเธ‚เธฒเธšเธฃเธดเธเธฒเธฃเธ‡เธฒเธ™ +completion.job.mappedServicePort=เธžเธญเธฃเนŒเธ•เธšเธฃเธดเธเธฒเธฃเธ—เธตเนˆเนเธกเธ› +completion.strategy.failFast=เน„เธกเนˆเธงเนˆเธฒเธ‡เธฒเธ™เธ—เธตเนˆเธญเธขเธนเนˆเธฃเธฐเธซเธงเนˆเธฒเธ‡เธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃเธ—เธฑเน‰เธ‡เธซเธกเธ”เธˆเธฐเธ–เธนเธเธขเธเน€เธฅเธดเธเธซเธฃเธทเธญเน„เธกเนˆเธซเธฒเธเธ‡เธฒเธ™เน€เธกเธ—เธฃเธดเธเธ‹เนŒเธฅเน‰เธกเน€เธซเธฅเธง +completion.strategy.jobIndex=เธ”เธฑเธŠเธ™เธตเนเธšเธšเธจเธนเธ™เธขเนŒเธ‚เธญเธ‡เธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™เนƒเธ™เน€เธกเธ—เธฃเธดเธเธ‹เนŒ +completion.strategy.jobTotal=เธˆเธณเธ™เธงเธ™เธ‡เธฒเธ™เธ—เธฑเน‰เธ‡เธซเธกเธ”เนƒเธ™เน€เธกเธ—เธฃเธดเธเธ‹เนŒ +completion.strategy.maxParallel=เธˆเธณเธ™เธงเธ™เธ‡เธฒเธ™เน€เธกเธ—เธฃเธดเธเธ‹เนŒเธชเธนเธ‡เธชเธธเธ”เธ—เธตเนˆเธชเธฒเธกเธฒเธฃเธ–เธฃเธฑเธ™เธžเธฃเน‰เธญเธกเธเธฑเธ™เน„เธ”เน‰ +completion.context.inputs=เธญเธดเธ™เธžเธธเธ•เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ เน€เธŠเนˆเธ™ workflow_dispatch เธซเธฃเธทเธญ workflow_call +completion.context.secrets=เธ„เธงเธฒเธกเธฅเธฑเธšเธ‚เธญเธ‡เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +completion.context.job=เธ‚เน‰เธญเธกเธนเธฅเน€เธเธตเนˆเธขเธงเธเธฑเธšเธ‡เธฒเธ™เธ—เธตเนˆเธ”เธณเน€เธ™เธดเธ™เธญเธขเธนเนˆเนƒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +completion.context.jobs=เธ‡เธฒเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +completion.context.matrix=เธ„เธธเธ“เธชเธกเธšเธฑเธ•เธดเน€เธกเธ—เธฃเธดเธเธ‹เนŒเธ—เธตเนˆเธเธณเธซเธ™เธ”เน„เธงเน‰เธชเธณเธซเธฃเธฑเธšเธ‡เธฒเธ™เน€เธกเธ—เธฃเธดเธเธ‹เนŒเธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +completion.context.strategy=เธ‚เน‰เธญเธกเธนเธฅเธเธฅเธขเธธเธ—เธ˜เนŒเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃเน€เธกเธ—เธฃเธดเธเธ‹เนŒเธชเธณเธซเธฃเธฑเธšเธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +completion.context.steps=เธ‚เธฑเน‰เธ™เธ•เธญเธ™เธ”เน‰เธงเธข ID เนƒเธ™เธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +completion.context.env=เธ•เธฑเธงเนเธ›เธฃเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธกเธˆเธฒเธเธ‡เธฒเธ™เนเธฅเธฐเธ‚เธฑเน‰เธ™เธ•เธญเธ™ +completion.context.vars=เธ•เธฑเธงเนเธ›เธฃเธเธฒเธฃเธเธณเธซเธ™เธ”เธ„เนˆเธฒเนเธšเธšเธเธณเธซเธ™เธ”เน€เธญเธ‡เธˆเธฒเธเธ‚เธญเธšเน€เธ‚เธ•เธญเธ‡เธ„เนŒเธเธฃ เธžเธทเน‰เธ™เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅ เนเธฅเธฐเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธก +completion.context.needs=เธ‡เธฒเธ™เธ—เธตเนˆเธ•เน‰เธญเธ‡เธ—เธณเธเนˆเธญเธ™เธ—เธตเนˆเธ‡เธฒเธ™เธ™เธตเน‰เธˆเธฐเธชเธฒเธกเธฒเธฃเธ–เธฃเธฑเธ™เน„เธ”เน‰ เธฃเธงเธกเธ–เธถเธ‡เธœเธฅเธฅเธฑเธžเธ˜เนŒเนเธฅเธฐเธœเธฅเธฅเธฑเธžเธ˜เนŒ +completion.context.github=เธ‚เน‰เธญเธกเธนเธฅเธเธฒเธฃเธฃเธฑเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเนเธฅเธฐเน€เธซเธ•เธธเธเธฒเธฃเธ“เนŒเธˆเธฒเธเธšเธฃเธดเธšเธ— GitHub +completion.context.gitea=เธ™เธฒเธกเนเธเธ‡เธ—เธตเนˆเน€เธ‚เน‰เธฒเธเธฑเธ™เน„เธ”เน‰เธเธฑเธš Gitea เธชเธณเธซเธฃเธฑเธšเธšเธฃเธดเธšเธ—เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ GitHub +completion.context.runner=เธ‚เน‰เธญเธกเธนเธฅเน€เธเธตเนˆเธขเธงเธเธฑเธšเธ™เธฑเธเธงเธดเนˆเธ‡เธ—เธตเนˆเธ”เธณเน€เธ™เธดเธ™เธ‡เธฒเธ™เธ›เธฑเธˆเธˆเธธเธšเธฑเธ™ +completion.secret.githubToken=เน‚เธ—เน€เธ„เน‡เธ™เธ—เธตเนˆเธชเธฃเน‰เธฒเธ‡เธ‚เธถเน‰เธ™เน‚เธ”เธขเธญเธฑเธ•เน‚เธ™เธกเธฑเธ•เธดเธชเธณเธซเธฃเธฑเธšเธเธฒเธฃเธฃเธฑเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเนเธ•เนˆเธฅเธฐเธ„เธฃเธฑเน‰เธ‡ +completion.remote.repository=เธžเธทเน‰เธ™เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅเธฃเธฐเธขเธฐเน„เธเธฅ +completion.uses.local.workflow=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰เนƒเธ™เธ—เน‰เธญเธ‡เธ–เธดเนˆเธ™ +completion.uses.local.action=เธเธฒเธฃเธเธฃเธฐเธ—เธณเนƒเธ™เธ—เน‰เธญเธ‡เธ–เธดเนˆเธ™ +completion.uses.ref.known=เธเธฒเธฃเธญเน‰เธฒเธ‡เธญเธดเธ‡เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธฃเธนเน‰เธˆเธฑเธ +completion.uses.ref.remote=เธเธฒเธฃเธญเน‰เธฒเธ‡เธญเธดเธ‡เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธฃเธฐเธขเธฐเน„เธเธฅ +completion.uses.remote.known=เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃเธฃเธฐเธขเธฐเน„เธเธฅเธ—เธตเนˆเธฃเธนเน‰เธˆเธฑเธเธซเธฃเธทเธญเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธ™เธณเธกเธฒเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰ +completion.workflow.syntax=เน„เธงเธขเธฒเธเธฃเธ“เนŒเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ GitHub +completion.workflow.top.name=เธŠเธทเนˆเธญเธ—เธตเนˆเนเธชเธ”เธ‡เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +completion.workflow.top.run-name=เธŠเธทเนˆเธญเธเธฒเธฃเธฃเธฑเธ™เนเธšเธšเน„เธ”เธ™เธฒเธกเธดเธ +completion.workflow.top.on=เน€เธซเธ•เธธเธเธฒเธฃเธ“เนŒเธ—เธตเนˆเน€เธฃเธดเนˆเธกเธ•เน‰เธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +completion.workflow.top.permissions=เธชเธดเธ—เธ˜เธดเนŒ GITHUB_TOKEN เน€เธฃเธดเนˆเธกเธ•เน‰เธ™ +completion.workflow.top.env=เธ•เธฑเธงเนเธ›เธฃเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธกเธ—เธฑเนˆเธงเธ—เธฑเน‰เธ‡เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +completion.workflow.top.defaults=เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒเธ‡เธฒเธ™เนเธฅเธฐเธ‚เธฑเน‰เธ™เธ•เธญเธ™เน€เธฃเธดเนˆเธกเธ•เน‰เธ™ +completion.workflow.top.concurrency=เธเธฅเธธเนˆเธกเธเธฒเธฃเธ—เธณเธ‡เธฒเธ™เธžเธฃเน‰เธญเธกเธเธฑเธ™เนเธฅเธฐเธเธฒเธฃเธขเธเน€เธฅเธดเธ +completion.workflow.top.jobs=เธ‡เธฒเธ™เธ—เธตเนˆเธฃเธฑเธ™เนƒเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ™เธตเน‰ +completion.workflow.event.branch_protection_rule=เธเธฒเธฃเธ›เน‰เธญเธ‡เธเธฑเธ™เธชเธฒเธ‚เธฒเน€เธ›เธฅเธตเนˆเธขเธ™เน„เธ› +completion.workflow.event.check_run=เธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡เธเธฒเธฃเธ•เธฃเธงเธˆเธชเธญเธšเธ„เธฃเธฑเน‰เธ‡เน€เธ”เธตเธขเธง +completion.workflow.event.check_suite=เธŠเธธเธ”เธ•เธฃเธงเธˆเธชเธญเธšเธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.create=เธชเธฃเน‰เธฒเธ‡เธชเธฒเธ‚เธฒเธซเธฃเธทเธญเนเธ—เน‡เธเนเธฅเน‰เธง +completion.workflow.event.delete=เธฅเธšเธชเธฒเธ‚เธฒเธซเธฃเธทเธญเนเธ—เน‡เธเนเธฅเน‰เธง +completion.workflow.event.deployment=เธชเธฃเน‰เธฒเธ‡เธเธฒเธฃเธ—เธณเนƒเธซเน‰เนƒเธŠเน‰เธ‡เธฒเธ™เน„เธ”เน‰เนเธฅเน‰เธง +completion.workflow.event.deployment_status=เธชเธ–เธฒเธ™เธฐเธเธฒเธฃเธ—เธณเนƒเธซเน‰เนƒเธŠเน‰เธ‡เธฒเธ™เน„เธ”เน‰เน€เธ›เธฅเธตเนˆเธขเธ™เน„เธ› +completion.workflow.event.discussion=เธเธฒเธฃเธชเธ™เธ—เธ™เธฒเน€เธ›เธฅเธตเนˆเธขเธ™เน„เธ› +completion.workflow.event.discussion_comment=เธ„เธงเธฒเธกเธ„เธดเธ”เน€เธซเน‡เธ™เนƒเธ™เธเธฒเธฃเธชเธ™เธ—เธ™เธฒเธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.fork=เธžเธทเน‰เธ™เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅเนเธขเธเธญเธญเธ +completion.workflow.event.gollum=เธซเธ™เน‰เธฒเธงเธดเธเธดเธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.image_version=เน€เธงเธญเธฃเนŒเธŠเธฑเธ™เธญเธดเธกเน€เธกเธˆเนเธžเน‡เธเน€เธเธˆเธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.issue_comment=เธ›เธฑเธเธซเธฒเธซเธฃเธทเธญเธ„เธงเธฒเธกเธ„เธดเธ”เน€เธซเน‡เธ™ PR เน€เธ›เธฅเธตเนˆเธขเธ™เน„เธ› +completion.workflow.event.issues=เธ›เธฑเธเธซเธฒเธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.label=เธ›เน‰เธฒเธขเธเธณเธเธฑเธšเธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.merge_group=เธฃเน‰เธญเธ‡เธ‚เธญเธเธฒเธฃเธ•เธฃเธงเธˆเธชเธญเธšเธ„เธดเธงเธฃเธงเธก +completion.workflow.event.milestone=เน€เธซเธ•เธธเธเธฒเธฃเธ“เนŒเธชเธณเธ„เธฑเธเน€เธ›เธฅเธตเนˆเธขเธ™เน„เธ› +completion.workflow.event.page_build=เธšเธดเธฅเธ”เนŒเน€เธžเธˆเธ—เธณเธ‡เธฒเธ™เนเธฅเน‰เธง +completion.workflow.event.project=เน‚เธ›เธฃเน€เธˆเน‡เธเธ•เนŒเธ„เธฅเธฒเธชเธชเธดเธเน€เธ›เธฅเธตเนˆเธขเธ™เน„เธ› +completion.workflow.event.project_card=เธเธฒเธฃเนŒเธ”เน‚เธ›เธฃเน€เธˆเน‡เธเธ•เนŒเธ„เธฅเธฒเธชเธชเธดเธเธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.project_column=เธ„เธญเธฅเธฑเธกเธ™เนŒเน‚เธ„เธฃเธ‡เธเธฒเธฃเธ„เธฅเธฒเธชเธชเธดเธเธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.public=เธžเธทเน‰เธ™เธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅเธเธฅเธฒเธขเน€เธ›เน‡เธ™เธชเธฒเธ˜เธฒเธฃเธ“เธฐ +completion.workflow.event.pull_request=เธ„เธณเธ‚เธญเธ”เธถเธ‡เธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.pull_request_review=เธเธฒเธฃเธ•เธฃเธงเธˆเธชเธญเธš PR เธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.pull_request_review_comment=เธ„เธงเธฒเธกเธ„เธดเธ”เน€เธซเน‡เธ™เธเธฒเธฃเธ•เธฃเธงเธˆเธชเธญเธš PR เธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.pull_request_target=เธšเธฃเธดเธšเธ—เน€เธ›เน‰เธฒเธซเธกเธฒเธข PR เธกเธตเธ”เธ„เธก +completion.workflow.event.push=เธ„เธญเธกเธกเธดเธ•เธซเธฃเธทเธญเนเธ—เน‡เธเธ—เธตเนˆเธžเธธเธŠ +completion.workflow.event.registry_package=เนเธžเน‡เธ„เน€เธเธˆเน€เธœเธขเนเธžเธฃเนˆเธซเธฃเธทเธญเธญเธฑเธ›เน€เธ”เธ• +completion.workflow.event.release=เธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡เธเธฒเธฃเน€เธœเธขเนเธžเธฃเนˆ +completion.workflow.event.repository_dispatch=เน€เธซเธ•เธธเธเธฒเธฃเธ“เนŒ API เธ—เธตเนˆเธเธณเธซเธ™เธ”เน€เธญเธ‡ +completion.workflow.event.schedule=เธ„เธฃเธญเธ™เธ•เธดเนŠเธ เน€เธ„เธฃเธทเนˆเธญเธ‡เธˆเธฑเธเธฃ. +completion.workflow.event.status=เธชเธ–เธฒเธ™เธฐเธ„เธงเธฒเธกเธกเธธเนˆเธ‡เธกเธฑเนˆเธ™เธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.event.watch=เธ•เธดเธ”เธ”เธฒเธงเธ—เธตเนˆเน€เธเน‡เธšเนเธฅเน‰เธง +completion.workflow.event.workflow_call=เธเธฒเธฃเน€เธฃเธตเธขเธเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰ +completion.workflow.event.workflow_dispatch=เธ›เธธเนˆเธกเน€เธฃเธตเธขเธเนƒเธŠเน‰เธ”เน‰เธงเธขเธ•เธ™เน€เธญเธ‡ +completion.workflow.event.workflow_run=เธเธฒเธฃเน€เธฃเธตเธขเธเนƒเธŠเน‰เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธกเธตเธเธฒเธฃเน€เธ›เธฅเธตเนˆเธขเธ™เนเธ›เธฅเธ‡ +completion.workflow.eventFilter.types=เธˆเธณเธเธฑเธ”เธ›เธฃเธฐเน€เธ เธ—เธเธดเธˆเธเธฃเธฃเธก +completion.workflow.eventFilter.branches=เน€เธ‰เธžเธฒเธฐเธชเธฒเธ‚เธฒเน€เธซเธฅเนˆเธฒเธ™เธตเน‰เน€เธ—เนˆเธฒเธ™เธฑเน‰เธ™ +completion.workflow.eventFilter.branches-ignore=เธ‚เน‰เธฒเธกเธชเธฒเธ‚เธฒเน€เธซเธฅเนˆเธฒเธ™เธตเน‰ +completion.workflow.eventFilter.tags=เน€เธ‰เธžเธฒเธฐเนเธ—เน‡เธเน€เธซเธฅเนˆเธฒเธ™เธตเน‰เน€เธ—เนˆเธฒเธ™เธฑเน‰เธ™ +completion.workflow.eventFilter.tags-ignore=เธ‚เน‰เธฒเธกเนเธ—เน‡เธเน€เธซเธฅเนˆเธฒเธ™เธตเน‰ +completion.workflow.eventFilter.paths=เน€เธชเน‰เธ™เธ—เธฒเธ‡เน€เธซเธฅเนˆเธฒเธ™เธตเน‰เน€เธ—เนˆเธฒเธ™เธฑเน‰เธ™ +completion.workflow.eventFilter.paths-ignore=เธ‚เน‰เธฒเธกเน€เธชเน‰เธ™เธ—เธฒเธ‡เน€เธซเธฅเนˆเธฒเธ™เธตเน‰ +completion.workflow.eventFilter.workflows=เธŠเธทเนˆเธญเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธ™เนˆเธฒเธˆเธฑเธšเธ•เธฒเธกเธญเธ‡ +completion.workflow.eventFilter.cron=เธเธณเธซเธ™เธ”เธเธฒเธฃเธ„เธฃเธญเธ™ เน€เธ„เธฃเธทเนˆเธญเธ‡เธˆเธฑเธเธฃเน€เธฅเน‡เธเน† +completion.workflow.permission.actions=เธเธฒเธฃเธฃเธฑเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเนเธฅเธฐเธชเนˆเธงเธ™เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ +completion.workflow.permission.artifact-metadata=เธšเธฑเธ™เธ—เธถเธเธ‚เน‰เธญเธกเธนเธฅเน€เธกเธ•เธฒเธ‚เธญเธ‡เธญเธฒเธฃเนŒเธ•เธดเนเธŸเธเธ•เนŒ +completion.workflow.permission.attestations=เธเธฒเธฃเธฃเธฑเธšเธฃเธญเธ‡เธชเธดเนˆเธ‡เธ›เธฃเธฐเธ”เธดเธฉเธเนŒ +completion.workflow.permission.checks=เธ•เธฃเธงเธˆเธชเธญเธšเธเธฒเธฃเธงเธดเนˆเธ‡เนเธฅเธฐเธซเน‰เธญเธ‡เธชเธงเธตเธ— +completion.workflow.permission.code-quality=เธฃเธฒเธขเธ‡เธฒเธ™เธ„เธธเธ“เธ เธฒเธžเธฃเธซเธฑเธช +completion.workflow.permission.contents=เน€เธ™เธทเน‰เธญเธซเธฒเธ—เธตเนˆเน€เธเน‡เธšเธ‚เน‰เธญเธกเธนเธฅ +completion.workflow.permission.deployments=เธเธฒเธฃเธ›เธฃเธฑเธšเนƒเธŠเน‰ +completion.workflow.permission.discussions=เธเธฒเธฃเธญเธ เธดเธ›เธฃเธฒเธข +completion.workflow.permission.id-token=เน‚เธ—เน€เธ„เน‡เธ™ OpenID Connect +completion.workflow.permission.issues=เธ›เธฃเธฐเน€เธ”เน‡เธ™เธ•เนˆเธฒเธ‡เน† +completion.workflow.permission.models=เธฃเธธเนˆเธ™ GitHub +completion.workflow.permission.packages=เนเธžเน‡เธ„เน€เธเธˆ GitHub +completion.workflow.permission.pages=เธซเธ™เน‰เธฒ GitHub +completion.workflow.permission.pull-requests=เธ”เธถเธ‡เธ„เธณเธ‚เธญ +completion.workflow.permission.security-events=เธเธฒเธฃเธชเนเธเธ™เน‚เธ„เน‰เธ”เนเธฅเธฐเน€เธซเธ•เธธเธเธฒเธฃเธ“เนŒเธ”เน‰เธฒเธ™เธ„เธงเธฒเธกเธ›เธฅเธญเธ”เธ เธฑเธข +completion.workflow.permission.statuses=เธขเธทเธ™เธขเธฑเธ™เธชเธ–เธฒเธ™เธฐ +completion.workflow.permission.vulnerability-alerts=เธเธฒเธฃเนเธˆเน‰เธ‡เน€เธ•เธทเธญเธ™ Dependabot +completion.workflow.permission.value.read=เธชเธดเธ—เธ˜เธดเนŒเธเธฒเธฃเธญเนˆเธฒเธ™ +completion.workflow.permission.value.write=เธชเธดเธ—เธ˜เธดเนŒเธเธฒเธฃเน€เธ‚เธตเธขเธ™ เธฃเธงเธกเธเธฒเธฃเธญเนˆเธฒเธ™ +completion.workflow.permission.value.none=เน„เธกเนˆเธกเธตเธเธฒเธฃเน€เธ‚เน‰เธฒเธ–เธถเธ‡ +completion.workflow.permission.shorthand.read-all=เธชเธดเธ—เธ˜เธดเนŒเธ—เธฑเน‰เธ‡เธซเธกเธ”เธญเนˆเธฒเธ™ เธœเน‰เธฒเธซเนˆเธกเธœเธทเธ™เนƒเธซเธเนˆ. +completion.workflow.permission.shorthand.write-all=เธชเธดเธ—เธ˜เธดเนŒเธ—เธฑเน‰เธ‡เธซเธกเธ”เน€เธ‚เธตเธขเธ™ เธ„เน‰เธญเธ™เนƒเธซเธเนˆ. +completion.workflow.permission.shorthand.empty=เธ›เธดเธ”เธเธฒเธฃเนƒเธŠเน‰เธ‡เธฒเธ™เธเธฒเธฃเธญเธ™เธธเธเธฒเธ•เน‚เธ—เน€เธ„เน‡เธ™ +completion.workflow.job.name=เธŠเธทเนˆเธญเธ—เธตเนˆเนเธชเธ”เธ‡เธ‡เธฒเธ™ +completion.workflow.job.permissions=เธชเธดเธ—เธ˜เธดเนŒเน‚เธ—เน€เธ„เน‡เธ™เธ‡เธฒเธ™ +completion.workflow.job.needs=เธ‡เธฒเธ™เธ—เธตเนˆเธ•เน‰เธญเธ‡เธฃเธญ. +completion.workflow.job.if=เธชเธ เธฒเธžเธ‡เธฒเธ™ +completion.workflow.job.runs-on=เธ›เน‰เธฒเธขเธ™เธฑเธเธงเธดเนˆเธ‡เธซเธฃเธทเธญเธเธฅเธธเนˆเธก +completion.workflow.job.snapshot=เธ เธฒเธžเธ™เธฑเธเธงเธดเนˆเธ‡ +completion.workflow.job.environment=เธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธกเธเธฒเธฃเธ›เธฃเธฑเธšเนƒเธŠเน‰ +completion.workflow.job.concurrency=เธฅเน‡เธญเธ„เธเธฒเธฃเธ—เธณเธ‡เธฒเธ™เธžเธฃเน‰เธญเธกเธเธฑเธ™ +completion.workflow.job.outputs=เน€เธญเธฒเธ•เนŒเธžเธธเธ•เธ‡เธฒเธ™เธญเธทเนˆเธ™เธชเธฒเธกเธฒเธฃเธ–เธญเนˆเธฒเธ™เน„เธ”เน‰ +completion.workflow.job.env=เธ•เธฑเธงเนเธ›เธฃเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธกเธ‡เธฒเธ™ +completion.workflow.job.defaults=เธเธฒเธฃเธ•เธฑเน‰เธ‡เธ„เนˆเธฒเน€เธฃเธดเนˆเธกเธ•เน‰เธ™เธ‚เธญเธ‡เธ‡เธฒเธ™ +completion.workflow.job.steps=เธฃเธฒเธขเธเธฒเธฃเธ‚เธฑเน‰เธ™เธ•เธญเธ™ เธ‡เธฒเธ™เธˆเธฃเธดเธ‡. +completion.workflow.job.timeout-minutes=เธซเธกเธ”เน€เธงเธฅเธฒเธ‡เธฒเธ™เนƒเธ™เน„เธกเนˆเธเธตเนˆเธ™เธฒเธ—เธต +completion.workflow.job.strategy=เน€เธกเธ—เธฃเธดเธเธ‹เนŒเนเธฅเธฐเธเธฅเธขเธธเธ—เธ˜เนŒเธเธฒเธฃเธˆเธฑเธ”เธ•เธฒเธฃเธฒเธ‡เน€เธงเธฅเธฒ +completion.workflow.job.continue-on-error=เธ›เธฅเนˆเธญเธขเนƒเธซเน‰เธ‡เธฒเธ™เธ™เธตเน‰เธฅเน‰เธกเน€เธซเธฅเธงเน€เธšเธฒเน† +completion.workflow.job.container=เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒเธชเธณเธซเธฃเธฑเธšเธ‡เธฒเธ™เธ™เธตเน‰ +completion.workflow.job.services=เธ•เธนเน‰เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒเธšเธฃเธดเธเธฒเธฃเธฃเธ–เน„เธ‹เธ”เนŒเธ„เธฒเธฃเนŒ +completion.workflow.job.uses=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเนƒเธŠเน‰เธ‹เน‰เธณเน„เธ”เน‰เนƒเธ™เธเธฒเธฃเน‚เธ—เธฃ +completion.workflow.job.with=เธญเธดเธ™เธžเธธเธ•เธชเธณเธซเธฃเธฑเธšเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเน€เธฃเธตเธขเธเธงเนˆเธฒ +completion.workflow.job.secrets=เธ‚เน‰เธญเธกเธนเธฅเธฅเธฑเธšเธชเธณเธซเธฃเธฑเธšเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเน€เธฃเธตเธขเธเธงเนˆเธฒ +completion.workflow.defaultsRun.shell=เน€เธŠเธฅเธฅเนŒเธ”เธตเธŸเธญเธฅเธ•เนŒเธชเธณเธซเธฃเธฑเธšเธ‚เธฑเน‰เธ™เธ•เธญเธ™เธเธฒเธฃเธฃเธฑเธ™ +completion.workflow.defaultsRun.working-directory=เน„เธ”เน€เธฃเน‡เธเธ—เธญเธฃเธตเธเธฒเธฃเธ—เธณเธ‡เธฒเธ™เน€เธฃเธดเนˆเธกเธ•เน‰เธ™ +completion.workflow.concurrency.group=เธฅเน‡เธญเธ„เธŠเธทเนˆเธญเธชเธณเธซเธฃเธฑเธšเธเธฒเธฃเธฃเธฑเธ™เธ—เธตเนˆเธญเธขเธนเนˆเนƒเธ™เธ„เธดเธง +completion.workflow.concurrency.cancel-in-progress=เธขเธเน€เธฅเธดเธเธเธฒเธฃเน€เธฃเธตเธขเธเนƒเธŠเน‰เธเธฒเธฃเธˆเธฑเธšเธ„เธนเนˆเนเธšเธšเน€เธเนˆเธฒ +completion.workflow.environment.name=เธŠเธทเนˆเธญเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธก +completion.workflow.environment.url=เธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธก URL +completion.workflow.strategy.matrix=เนเธเธ™เน€เธกเธ—เธฃเธดเธเธ‹เนŒเนเธฅเธฐเธ•เธฑเธงเนเธ›เธฃ +completion.workflow.strategy.fail-fast=เธขเธเน€เธฅเธดเธเน€เธกเธ—เธฃเธดเธเธ‹เนŒเธžเธตเนˆเธ™เน‰เธญเธ‡เน€เธกเธทเนˆเธญเน€เธเธดเธ”เธ„เธงเธฒเธกเธฅเน‰เธกเน€เธซเธฅเธง +completion.workflow.strategy.max-parallel=เธเธฒเธ„เธฃเธญเธšเธ„เธงเธฒเธกเน€เธ—เนˆเธฒเน€เธ—เธตเธขเธกเธ‚เธญเธ‡เน€เธกเธ—เธฃเธดเธเธ‹เนŒ +completion.workflow.matrix.include=เน€เธžเธดเนˆเธกเธŠเธธเธ”เธ„เนˆเธฒเธœเธชเธกเน€เธกเธ—เธฃเธดเธเธ‹เนŒ +completion.workflow.matrix.exclude=เธฅเธšเธŠเธธเธ”เธ„เนˆเธฒเธœเธชเธกเน€เธกเธ—เธฃเธดเธเธ‹เนŒ +completion.workflow.step.id=เธฃเธซเธฑเธชเธ‚เธฑเน‰เธ™เธ•เธญเธ™เธชเธณเธซเธฃเธฑเธšเธเธฒเธฃเธญเน‰เธฒเธ‡เธญเธดเธ‡ +completion.workflow.step.if=เธชเธ เธฒเธžเธ‚เธฑเน‰เธ™ +completion.workflow.step.name=เธŠเธทเนˆเธญเธ—เธตเนˆเนเธชเธ”เธ‡เธ‚เธฑเน‰เธ™เธ•เธญเธ™ +completion.workflow.step.uses=เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃเธ—เธตเนˆเธˆเธฐเน€เธฃเธตเธขเธเนƒเธŠเน‰ +completion.workflow.step.run=เน€เธŠเธฅเธฅเนŒเธชเธ„เธฃเธดเธ›เธ•เนŒเธ—เธตเนˆเธˆเธฐเธฃเธฑเธ™ +completion.workflow.step.shell=เน€เธŠเธฅเธฅเนŒเธชเธณเธซเธฃเธฑเธšเธ‚เธฑเน‰เธ™เธ•เธญเธ™เธเธฒเธฃเธฃเธฑเธ™เธ™เธตเน‰ +completion.workflow.step.with=เธญเธดเธ™เธžเธธเธ•เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ +completion.workflow.step.env=เธ•เธฑเธงเนเธ›เธฃเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธกเธ‚เธฑเน‰เธ™เธ•เธญเธ™ +completion.workflow.step.continue-on-error=เธ›เธฅเนˆเธญเธขเนƒเธซเน‰เธ‚เธฑเน‰เธ™เธ•เธญเธ™เธ™เธตเน‰เธฅเน‰เธกเน€เธซเธฅเธงเธญเธขเนˆเธฒเธ‡เธ™เธธเนˆเธกเธ™เธงเธฅ +completion.workflow.step.timeout-minutes=เธซเธกเธ”เน€เธงเธฅเธฒเธ‚เธฑเน‰เธ™เธ•เธญเธ™เนƒเธ™เน„เธกเนˆเธเธตเนˆเธ™เธฒเธ—เธต +completion.workflow.step.working-directory=เน„เธ”เน€เธฃเน‡เธเธ—เธญเธฃเธตเธเธฒเธฃเธ—เธณเธ‡เธฒเธ™เธ‚เธฑเน‰เธ™เธ•เธญเธ™ +completion.workflow.container.image=เธ เธฒเธžเธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒ +completion.workflow.container.credentials=เธ‚เน‰เธญเธกเธนเธฅเธฃเธฑเธšเธฃเธญเธ‡เธเธฒเธฃเธฅเธ‡เธ—เธฐเน€เธšเธตเธขเธ™ +completion.workflow.container.env=เธ•เธฑเธงเนเธ›เธฃเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธกเธ‚เธญเธ‡เธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒ +completion.workflow.container.ports=เธžเธญเธฃเนŒเธ•เธ—เธตเนˆเธˆเธฐเน€เธ›เธดเธ”เน€เธœเธข +completion.workflow.container.volumes=เน„เธ”เธฃเธŸเนŒเธ‚เน‰เธญเธกเธนเธฅเธ—เธตเนˆเธˆเธฐเน€เธกเธฒเธ™เธ•เนŒ +completion.workflow.container.options=Docker เธชเธฃเน‰เธฒเธ‡เธ•เธฑเธงเน€เธฅเธทเธญเธ +completion.workflow.service.image=เธญเธดเธกเน€เธกเธˆเธ„เธญเธ™เน€เธ—เธ™เน€เธ™เธญเธฃเนŒเธšเธฃเธดเธเธฒเธฃ +completion.workflow.service.credentials=เธ‚เน‰เธญเธกเธนเธฅเธฃเธฑเธšเธฃเธญเธ‡เธเธฒเธฃเธฅเธ‡เธ—เธฐเน€เธšเธตเธขเธ™ +completion.workflow.service.env=เธ•เธฑเธงเนเธ›เธฃเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธกเธเธฒเธฃเธšเธฃเธดเธเธฒเธฃ +completion.workflow.service.ports=เธžเธญเธฃเนŒเธ•เธšเธฃเธดเธเธฒเธฃ +completion.workflow.service.volumes=เธ›เธฃเธดเธกเธฒเธ“เธเธฒเธฃเนƒเธซเน‰เธšเธฃเธดเธเธฒเธฃ +completion.workflow.service.options=Docker เธชเธฃเน‰เธฒเธ‡เธ•เธฑเธงเน€เธฅเธทเธญเธ +completion.workflow.credentials.username=เธŠเธทเนˆเธญเธœเธนเน‰เนƒเธŠเน‰เธฃเธตเธˆเธดเธชเธ—เธฃเธต +completion.workflow.credentials.password=เธฃเธซเธฑเธชเธœเนˆเธฒเธ™เธฃเธตเธˆเธดเธชเธ—เธฃเธตเธซเธฃเธทเธญเน‚เธ—เน€เธ„เน‡เธ™ +completion.workflow.inputType.string=เธเธฒเธฃเธ›เน‰เธญเธ™เธ‚เน‰เธญเธ„เธงเธฒเธก +completion.workflow.inputType.boolean=เธญเธดเธ™เธžเธธเธ•เธˆเธฃเธดเธ‡เธซเธฃเธทเธญเน€เธ—เน‡เธˆ +completion.workflow.inputType.choice=เธญเธดเธ™เธžเธธเธ•เธ•เธฑเธงเน€เธฅเธทเธญเธเนเธšเธšเน€เธฅเธทเนˆเธญเธ™เธฅเธ‡ +completion.workflow.inputType.number=เธเธฒเธฃเธ›เน‰เธญเธ™เธ•เธฑเธงเน€เธฅเธ‚ +completion.workflow.inputType.environment=เธญเธดเธ™เธžเธธเธ•เน€เธ„เธฃเธทเนˆเธญเธ‡เธกเธทเธญเน€เธฅเธทเธญเธเธชเธ เธฒเธžเนเธงเธ”เธฅเน‰เธญเธก +completion.workflow.boolean.true=เนƒเธŠเนˆ เธžเธฅเธดเธเธกเธฑเธ™ +completion.workflow.boolean.false=เน„เธกเนˆ. เนƒเธซเน‰เธกเธฑเธ™เธกเธทเธ”. +completion.workflow.runner.ubuntu-latest=เธ™เธฑเธเธงเธดเนˆเธ‡ Ubuntu เธฅเนˆเธฒเธชเธธเธ” +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 เธงเธดเนˆเธ‡ +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 เธงเธดเนˆเธ‡ +completion.workflow.runner.windows-latest=เธ™เธฑเธเธงเธดเนˆเธ‡ Windows เธฅเนˆเธฒเธชเธธเธ” +completion.workflow.runner.windows-2025=เธ•เธฑเธงเธฃเธฑเธ™ Windows Server 2025 +completion.workflow.runner.windows-2022=เธ•เธฑเธงเธฃเธฑเธ™ Windows Server 2022 +completion.workflow.runner.macos-latest=เธ™เธฑเธเธงเธดเนˆเธ‡ macOS เธฅเนˆเธฒเธชเธธเธ” +completion.workflow.runner.macos-15=macOS 15 เธงเธดเนˆเธ‡ +completion.workflow.runner.macos-14=macOS 14 เธงเธดเนˆเธ‡ +completion.workflow.runner.self-hosted=เธ™เธฑเธเธงเธดเนˆเธ‡เธ‚เธญเธ‡เธ„เธธเธ“เน€เธญเธ‡ เธ„เธ“เธฐเธฅเธฐเธ„เธฃเธชเธฑเธ•เธงเนŒเธ‚เธญเธ‡เธ„เธธเธ“ +completion.steps.outputs=เธŠเธธเธ”เน€เธญเธฒเธ•เนŒเธžเธธเธ•เธ—เธตเนˆเธเธณเธซเธ™เธ”เน„เธงเน‰เธชเธณเธซเธฃเธฑเธšเธ‚เธฑเน‰เธ™เธ•เธญเธ™ +completion.steps.conclusion=เธœเธฅเธฅเธฑเธžเธ˜เนŒเธ‚เธญเธ‡เธ‚เธฑเน‰เธ™เธ•เธญเธ™เธ—เธตเนˆเน€เธชเธฃเน‡เธˆเธชเธกเธšเธนเธฃเธ“เนŒเธซเธฅเธฑเธ‡เธˆเธฒเธเนƒเธŠเน‰เธ‚เน‰เธญเธœเธดเธ”เธžเธฅเธฒเธ”เธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃเธ•เนˆเธญ +completion.steps.outcome=เธœเธฅเธฅเธฑเธžเธ˜เนŒเธ‚เธญเธ‡เธ‚เธฑเน‰เธ™เธ•เธญเธ™เธ—เธตเนˆเน€เธชเธฃเน‡เธˆเธชเธกเธšเธนเธฃเธ“เนŒเธเนˆเธญเธ™เธ—เธตเนˆเธˆเธฐเนƒเธŠเน‰เธ‚เน‰เธญเธœเธดเธ”เธžเธฅเธฒเธ”เธ•เนˆเธญเน„เธ› +completion.jobs.outputs=เธŠเธธเธ”เน€เธญเธฒเธ•เนŒเธžเธธเธ•เธ—เธตเนˆเธเธณเธซเธ™เธ”เน„เธงเน‰เธชเธณเธซเธฃเธฑเธšเธ‡เธฒเธ™ +completion.jobs.result=เธœเธฅเธ‚เธญเธ‡เธ‡เธฒเธ™. +settings.displayName=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub +settings.language.label=เธ เธฒเธฉเธฒ: +settings.language.system=IDE/เธ„เนˆเธฒเน€เธฃเธดเนˆเธกเธ•เน‰เธ™เธ‚เธญเธ‡เธฃเธฐเธšเธš +settings.cache.title=เนเธ„เธŠเธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ +settings.cache.column.key=เธฃเธซเธฑเธชเนเธ„เธŠ +settings.cache.column.name=เธŠเธทเนˆเธญ +settings.cache.column.kind=เธŠเธ™เธดเธ” +settings.cache.column.state=เธฃเธฑเธ +settings.cache.column.expires=เธซเธกเธ”เธญเธฒเธขเธธ +settings.cache.kind.local=เธ—เน‰เธญเธ‡เธ–เธดเนˆเธ™ +settings.cache.kind.remote=เธฃเธฐเธขเธฐเน„เธเธฅ +settings.cache.state.resolved=เนเธเน‰เน„เธ‚เนเธฅเน‰เธง +settings.cache.state.pending=เธฃเธญเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ +settings.cache.state.expired=เน€เธซเธกเน‡เธ™เธญเธฑเธš +settings.cache.state.suppressed=เธฃเธฐเธ‡เธฑเธš +settings.cache.refresh=เธ•เธฒเธฃเธฒเธ‡ Refresh +settings.cache.deleteSelected=เธฅเธšเธ—เธตเนˆเน€เธฅเธทเธญเธเน„เธงเน‰ +settings.cache.deleteAll=เธฅเธšเธ—เธฑเน‰เธ‡เธซเธกเธ” +settings.cache.export=เธชเนˆเธ‡เธญเธญเธ +settings.cache.import=เธ™เธณเน€เธ‚เน‰เธฒ +settings.cache.summary=เนเธ„เธŠ: เธฃเธฒเธขเธเธฒเธฃ {0}, {1} เนเธเน‰เน„เธ‚เนเธฅเน‰เธง, เธฃเธฐเธขเธฐเน„เธเธฅ {2}, {3} เน€เธเนˆเธฒ, {4} เธ›เธดเธ”เน€เธชเธตเธขเธ‡ เนเธ„เธŠ: {5} KB +settings.cache.noneSelected=เน€เธฅเธทเธญเธเนเธ–เธงเนเธ„เธŠเธเนˆเธญเธ™ เน„เธกเน‰เธเธงเธฒเธ”เธ›เธเธดเน€เธชเธ˜เธเธฒเธฃเธ„เธฒเธ”เน€เธ”เธฒ +settings.cache.deleteSelected.done=เธฅเธšเธฃเธฒเธขเธเธฒเธฃเนเธ„เธŠ {0} เธกเธตเน€เธกเธ†เธเธธเนˆเธ™เน€เธฅเน‡เธเน† เธ›เธเธ„เธฅเธธเธกเธญเธขเธนเนˆ +settings.cache.deleteAll.confirm=เธฅเธšเธฃเธฒเธขเธเธฒเธฃเนเธ„เธŠเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub เธ—เธฑเน‰เธ‡เธซเธกเธ”เธซเธฃเธทเธญเน„เธกเนˆ +settings.cache.deleteAll.done=เธฅเธšเธฃเธฒเธขเธเธฒเธฃเนเธ„เธŠเธ—เธฑเน‰เธ‡เธซเธกเธ”เนเธฅเน‰เธง เธ•เธญเธ™เธ™เธตเน‰เนเธ„เธŠเน€เธ‡เธตเธขเธšเธญเธขเนˆเธฒเธ‡เธ™เนˆเธฒเธชเธ‡เธชเธฑเธข +settings.cache.export.done=เธฃเธฒเธขเธเธฒเธฃเนเธ„เธŠ {0} เธ—เธตเนˆเธชเนˆเธ‡เธญเธญเธเนเธฅเน‰เธง เธชเธฑเธ•เธงเนŒเน€เธเน‡เธšเธ–เธฒเธงเธฃเธ‚เธ™เธฒเธ”เน€เธฅเน‡เธเธ–เธนเธเธ‚เธฑเธ‡เธญเธขเธนเนˆเนƒเธ™เธเธฃเธ‡ +settings.cache.import.done=เธฃเธฒเธขเธเธฒเธฃเนเธ„เธŠเธ—เธตเนˆเธ™เธณเน€เธ‚เน‰เธฒ เธชเธฑเธ•เธงเนŒเธฃเน‰เธฒเธขเน€เธเน‡เธšเธ–เธฒเธงเธฃเธ›เธฃเธฐเธžเธคเธ•เธดเธ•เธ™ +settings.cache.import.unsupported=เน„เธŸเธฅเนŒเนเธ„เธŠเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub เธ—เธตเนˆเน„เธกเนˆเธฃเธญเธ‡เธฃเธฑเธš +settings.cache.import.brokenLine=เธšเธฃเธฃเธ—เธฑเธ”เนเธ„เธŠเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub เนƒเธŠเน‰เธ‡เธฒเธ™เน„เธกเนˆเน„เธ”เน‰ +settings.cache.import.brokenKey=เธ„เธตเธขเนŒเนเธ„เธŠเน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ GitHub เนƒเธŠเน‰เธ‡เธฒเธ™เน„เธกเนˆเน„เธ”เน‰ +settings.support.button=เธชเธ™เธฑเธšเธชเธ™เธธเธ™เธ›เธฅเธฑเนŠเธเธญเธดเธ™เธ™เธตเน‰ +settings.support.tooltip=เน€เธ›เธดเธ”เธซเธ™เน‰เธฒเธชเธ™เธฑเธšเธชเธ™เธธเธ™ +settings.support.line.0=เธ›เน‰เธญเธ™เน€เธ•เธฒเธซเธฅเธญเธก +settings.support.line.1=เธ‹เธทเน‰เธญเธเธฒเนเธŸเธžเธฒเธฃเนŒเน€เธ‹เธญเธฃเนŒ +settings.support.line.2=เธชเธ™เธฑเธšเธชเธ™เธธเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธตเนˆเธกเธตเธœเธตเธชเธดเธ‡เธ™เน‰เธญเธขเธฅเธ‡ +workflow.run.jobs.title=เธ‡เธฒเธ™เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒ +workflow.run.jobs.root=เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธ—เธณเธ‡เธฒเธ™ +workflow.run.jobs.description=เนเธœเธ™เธœเธฑเธ‡เธ‡เธฒเธ™เธเธฒเธฃเธ”เธณเน€เธ™เธดเธ™เธเธฒเธฃ GitHub เนเธฅเธฐเธšเธฑเธ™เธ—เธถเธเธ‡เธฒเธ™เธ—เธตเนˆเน€เธฅเธทเธญเธ +workflow.run.tree.done=เน€เธชเธฃเน‡เธˆเนเธฅเน‰เธง +workflow.run.tree.failed=เธฅเน‰เธกเน€เธซเธฅเธง +workflow.run.tree.skipped=เธ‚เน‰เธฒเธกเน„เธ› +workflow.run.tree.warn=เน€เธ•เธทเธญเธ™ +workflow.run.tree.err=เธœเธดเธ”เธžเธฅเธฒเธ” +workflow.run.delete.tooltip=เธฅเธšเธเธฒเธฃเธฃเธฑเธ™ +workflow.run.delete.noRun=เธขเธฑเธ‡เน„เธกเนˆเธกเธตเธฃเธซเธฑเธชเธฃเธฑเธ™ +workflow.run.delete.requested=เธเธณเธฅเธฑเธ‡เธฅเธšเธเธฒเธฃเธฃเธฑเธ™ {0} +workflow.run.delete.done=เธฅเธšเธเธฒเธฃเธฃเธฑเธ™ {0} เนเธฅเน‰เธง +workflow.run.delete.http=เธฅเธš HTTP {0} เธญเนŠเธญเธŸ. +workflow.run.delete.failed=เธฅเธšเธกเธญเธ”: {0} +workflow.run.rerun.all.tooltip=เน€เธฃเธตเธขเธเนƒเธŠเน‰เน€เธงเธดเธฃเนŒเธเน‚เธŸเธฅเธงเนŒเธญเธตเธเธ„เธฃเธฑเน‰เธ‡ +workflow.run.rerun.failed.tooltip=เธฃเธฑเธ™เธ‡เธฒเธ™เธ—เธตเนˆเธฅเน‰เธกเน€เธซเธฅเธงเธญเธตเธเธ„เธฃเธฑเน‰เธ‡ +workflow.run.rerun.noRun=เธขเธฑเธ‡เน„เธกเนˆเธกเธตเธฃเธซเธฑเธชเธฃเธฑเธ™ +workflow.run.rerun.all.requested=เธฃเน‰เธญเธ‡เธ‚เธญเธเธฒเธฃเธฃเธฑเธ™เธ‹เน‰เธณ: {0} +workflow.run.rerun.failed.requested=เธ‡เธฒเธ™เธ—เธตเนˆเธฃเน‰เธญเธ‡เธ‚เธญเธฅเน‰เธกเน€เธซเธฅเธง: {0} +workflow.run.rerun.all.done=เธฃเธฑเธ™เธ‹เน‰เธณเนƒเธ™เธ„เธดเธง: {0} +workflow.run.rerun.failed.done=เธ‡เธฒเธ™เธ—เธตเนˆเธฅเน‰เธกเน€เธซเธฅเธงเธญเธขเธนเนˆเนƒเธ™เธ„เธดเธง: {0} +workflow.run.rerun.http=เธฃเธฑเธ™ HTTP {0} เธญเธตเธเธ„เธฃเธฑเน‰เธ‡ เธญเนŠเธญเธŸ. +workflow.run.rerun.failed=เธฃเธฑเธ™เธ‹เน‰เธณเธกเธญเธ”: {0} +workflow.run.download.log.tooltip=เธšเธฑเธ™เธ—เธถเธเธšเธฑเธ™เธ—เธถเธเธ‡เธฒเธ™ +workflow.run.download.artifacts.tooltip=เธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ”เธชเธดเนˆเธ‡เธ›เธฃเธฐเธ”เธดเธฉเธเนŒ +workflow.run.download.noRun=เธขเธฑเธ‡เน„เธกเนˆเธกเธตเธฃเธซเธฑเธชเธฃเธฑเธ™ +workflow.run.download.log.requested=เธเธณเธฅเธฑเธ‡เธ”เธถเธ‡เธ‚เน‰เธญเธกเธนเธฅเธšเธฑเธ™เธ—เธถเธเธชเธณเธซเธฃเธฑเธš {0} +workflow.run.download.log.done=เธšเธฑเธ™เธ—เธถเธเธšเธฑเธ™เธ—เธถเธเนเธฅเน‰เธง: {0} +workflow.run.download.artifacts.requested=เธเธณเธฅเธฑเธ‡เธ”เธถเธ‡เธชเธดเนˆเธ‡เธ›เธฃเธฐเธ”เธดเธฉเธเนŒ +workflow.run.download.artifacts.empty=เน„เธกเนˆเธกเธตเธชเธดเนˆเธ‡เธ›เธฃเธฐเธ”เธดเธฉเธเนŒ เธ„เธงเธฒเธกเธงเนˆเธฒเธ‡เน€เธ›เธฅเนˆเธฒเน€เธฅเน‡เธเน† +workflow.run.download.artifact.expired=เธญเธฒเธฃเนŒเธ•เธดเนเธŸเธเธ•เนŒเธซเธกเธ”เธญเธฒเธขเธธ: {0} +workflow.run.download.artifact.done=เธšเธฑเธ™เธ—เธถเธเธชเธดเนˆเธ‡เธ›เธฃเธฐเธ”เธดเธฉเธเนŒเนเธฅเน‰เธง: {0} -> {1} +workflow.run.download.failed=เธ”เธฒเธงเธ™เนŒเน‚เธซเธฅเธ”เธกเธญเธ”: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_tr.properties b/src/main/resources/messages/GitHubWorkflowBundle_tr.properties new file mode 100644 index 0000000..2807735 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_tr.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub ฤฐลŸ AkฤฑลŸฤฑ +plugin.description=GitHub Eylemleri iลŸ akฤฑลŸฤฑ dosyalarฤฑ desteฤŸi +group.GitHubWorkflow.Tools.text=GitHub ฤฐลŸ AkฤฑลŸฤฑ +group.GitHubWorkflow.Tools.description=GitHub ฤฐลŸ AkฤฑลŸฤฑ eklenti araรงlarฤฑ +action.GitHubWorkflow.RefreshActionCache.text=Refresh Eylem ร–nbelleฤŸi +action.GitHubWorkflow.RefreshActionCache.description=Refresh, uzak GitHub Eylemlerini ve yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ meta verilerini รงรถzdรผ +action.GitHubWorkflow.RestoreActionWarnings.text=Eylem Uyarฤฑlarฤฑnฤฑ Geri Yรผkle +action.GitHubWorkflow.RestoreActionWarnings.description=BastฤฑrฤฑlmฤฑลŸ eylem, giriลŸ ve รงฤฑkฤฑลŸ doฤŸrulama uyarฤฑlarฤฑnฤฑ geri yรผkleyin +action.GitHubWorkflow.ClearActionCache.text=Eylem ร–nbelleฤŸini Temizle +action.GitHubWorkflow.ClearActionCache.description=ร–nbelleฤŸe alฤฑnmฤฑลŸ GitHub Eylemlerini ve yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ meta verilerini temizleyin +notification.cache.cleared={0} รถnbelleฤŸe alฤฑnmฤฑลŸ GitHub ฤฐลŸ AkฤฑลŸฤฑ giriลŸleri temizlendi. +notification.cache.refresh.started=Refreshing {0} uzak GitHub ฤฐลŸ AkฤฑลŸฤฑ giriลŸlerini รถnbelleฤŸe aldฤฑ. +notification.warnings.restored={0} GitHub ฤฐลŸ AkฤฑลŸฤฑ giriลŸleri iรงin uyarฤฑlar geri yรผklendi. +workflow.run.configuration.display=GitHub ฤฐลŸ AkฤฑลŸฤฑ +workflow.run.configuration.description=GitHub Eylemleri iลŸ akฤฑลŸฤฑ รงalฤฑลŸtฤฑrmalarฤฑnฤฑ gรถnderin ve takip edin +workflow.run.configuration.name=GitHub ฤฐลŸ AkฤฑลŸฤฑ: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=Sahip +workflow.run.field.repo=Depo +workflow.run.field.workflow=ฤฐลŸ akฤฑลŸฤฑ dosyasฤฑ +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=Token env var geri dรถnรผลŸรผ +workflow.run.inputs.title=workflow_dispatch giriลŸleri (anahtar=deฤŸer) +workflow.run.error.apiUrl=GitHub API URL gereklidir. +workflow.run.error.repository=GitHub deposu sahibi ve adฤฑ gereklidir. +workflow.run.error.workflow=ฤฐลŸ akฤฑลŸฤฑ dosyasฤฑ gerekli. +workflow.run.error.ref=ลžube veya etiket referansฤฑ gerekli. +workflow.run.error.inputs=GitHub workflow_dispatch en fazla 25 giriลŸi destekler. +workflow.run.gutter.stop=ฤฐลŸ akฤฑลŸฤฑ รงalฤฑลŸmasฤฑnฤฑ durdur +workflow.run.gutter.stop.text=ฤฐลŸ akฤฑลŸฤฑ รงalฤฑลŸmasฤฑnฤฑ durdur +workflow.run.gutter.stop.description=Bu รงalฤฑลŸtฤฑrmayฤฑ iptal et +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=koลŸ: +workflow.log.warning=uyarฤฑ: +workflow.log.error=hata: +workflow.run.cancel.requested=ฤฐptal talebi: {0}. +workflow.run.stop.before.id=Durdurma istendi. Henรผz รงalฤฑลŸtฤฑrma kimliฤŸi yok. +workflow.run.cancel.http=HTTP {0}''i iptal edin. Of. +workflow.run.cancel.failed=ฤฐptal baลŸarฤฑsฤฑz oldu: {0} +workflow.run.interrupted=Kesintiye uฤŸradฤฑ. +workflow.run.link=ร‡alฤฑลŸtฤฑr: {0} +workflow.run.discovery=ร‡alฤฑลŸtฤฑrma kabul edildi. Av koลŸusu kimliฤŸi. +workflow.run.discovery.none=Henรผz รงalฤฑลŸtฤฑrma kimliฤŸi yok. Eylemler sekmesi daha fazlasฤฑnฤฑ biliyor. +workflow.run.status=Durum: {0}{1} +workflow.run.job.main=ฤฐลŸ: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Durum: {0} {1}{2}{3} +workflow.run.logs.later=Gรผnlรผkler, GitHub bunlarฤฑ yayฤฑnladฤฑฤŸฤฑnda gรถrรผnecektir. +workflow.run.job.logs.later=ฤฐลŸ {0}: {1} +workflow.run.log.failed=Gรผnlรผk indirme iลŸlemi baลŸarฤฑsฤฑz oldu: {0} +workflow.run.log.failed.job={0} iรงin gรผnlรผk indirme baลŸarฤฑsฤฑz oldu: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=ฤฐลŸ: {0} +workflow.run.job.fallbackName=ฤฐลŸ {0} +workflow.run.overview=ฤฐลŸ akฤฑลŸฤฑ รงalฤฑลŸtฤฑrmasฤฑ {0} {1}/{2} tamamlandฤฑ, {3} รงalฤฑลŸฤฑyor +workflow.run.state.ok=[Tamam] +workflow.run.state.fail=[BAลžARISIZ] +workflow.run.state.running=[KoลŸ] +workflow.run.state.waiting=[BEKLEYฤฐN] +workflow.run.dispatch.verbs=Hazฤฑrlama|KuyruฤŸa Alma|ร‡aฤŸฤฑrma|BaลŸlatma|ร–nyรผkleme +workflow.run.dispatch.objects=iลŸ akฤฑลŸฤฑ|otomasyon|boru hattฤฑ|รงalฤฑลŸtฤฑrma +workflow.run.dispatch={0} {1} {5} รผzerindeki {4} iรงin {2}{3}. +workflow.run.notification.auth=GitHub iลŸ akฤฑลŸฤฑ gรถnderimi, kimliฤŸi doฤŸrulanmฤฑลŸ bir GitHub hesabฤฑna ihtiyaรง duyar. {0}''te hesap ekleyin veya yenileyin. +workflow.run.notification.openSettings=GitHub Ayarlarฤฑnฤฑ aรงฤฑn +workflow.cache.progress.title=GitHub eylemlerini รงรถzรผmleme +workflow.cache.progress.text={0} {1} รงรถzรผmleniyor +workflow.cache.kind.action=eylem +workflow.cache.kind.workflow=iลŸ akฤฑลŸฤฑ +inspection.parameter.input=giriลŸ +inspection.parameter.secret=gizli +inspection.action.delete.invalid=Geรงersiz {0} [{1}]''i silin +inspection.action.update.major=Eylemi [{0}]''i [{1}] olarak gรผncelleyin +inspection.warning.toggle=[{1}] iรงin uyarฤฑlarฤฑ [{0}] deฤŸiลŸtirin +inspection.warning.on=รผzerinde +inspection.warning.off=kapalฤฑ +inspection.statement.incomplete=Eksik bildirim [{0}] +inspection.invalid.suffix.remove=Geรงersiz son eki kaldฤฑr [{0}] +inspection.replace.with=[{0}] ile deฤŸiลŸtirin +inspection.invalid.remove=Geรงersiz [{0}]''i kaldฤฑr +inspection.workflow.syntax.unknownTopLevelKey=Bilinmeyen iลŸ akฤฑลŸฤฑ anahtarฤฑ [{0}] +inspection.workflow.syntax.unknownEventKey=Bilinmeyen iลŸ akฤฑลŸฤฑ olayฤฑ [{0}] +inspection.workflow.syntax.unknownTriggerKey=Bilinmeyen tetikleme anahtarฤฑ [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Bilinmeyen tetikleme filtresi [{0}] +inspection.workflow.syntax.unknownTriggerValue=Bilinmeyen tetikleyici deฤŸeri [{0}] +inspection.workflow.syntax.unknownPermission=Bilinmeyen izin [{0}] +inspection.workflow.syntax.unknownPermissionValue=Bilinmeyen izin deฤŸeri [{0}] +inspection.workflow.syntax.unknownJobKey=Bilinmeyen iลŸ anahtarฤฑ [{0}] +inspection.workflow.syntax.unknownStepKey=Bilinmeyen adฤฑm anahtarฤฑ [{0}] +inspection.action.reload=[{0}]''i yeniden yรผkle +inspection.action.unresolved=ร‡รถzรผmlenmemiลŸ [{0}] - GitHub hesap eriลŸimini, รถzel depo izinlerini, hฤฑz sฤฑnฤฑrlarฤฑnฤฑ, eksik referanslarฤฑ veya eksik eylem/iลŸ akฤฑลŸฤฑ meta verilerini kontrol edin +inspection.action.jump=[{0}] dosyasฤฑna atla +inspection.output.unused=Kullanฤฑlmayan [{0}] +inspection.secret.invalid.if=[{0}] รถฤŸesini kaldฤฑr - "if" ifadelerinde sฤฑrlar geรงerli deฤŸildir +inspection.secret.replace.runtime=ร‡alฤฑลŸma zamanฤฑnda saฤŸlanmadฤฑysa, [{0}]''i [{1}] ile deฤŸiลŸtirin +inspection.needs.invalid.job=Geรงersiz iลŸ kimliฤŸini kaldฤฑr [{0}] - bu iลŸ kimliฤŸi รถnceki iลŸlerden hiรงbiriyle eลŸleลŸmiyor +documentation.description=Aรงฤฑklama: {0} +documentation.type=Tรผr: {0} +documentation.required=Gerekli: {0} +documentation.default=Varsayฤฑlan: {0} +documentation.deprecated=Kullanฤฑmdan kaldฤฑrฤฑldฤฑ: {0} +documentation.open.declaration=Aรงฤฑk beyan ({0}) +documentation.input.label=GiriลŸ +documentation.secret.label=Gizli +documentation.env.label=Ortam deฤŸiลŸkeni +documentation.matrix.label=Matris รถzelliฤŸi +documentation.need.label=Gerekli iลŸ +documentation.need.description=DoฤŸrudan iลŸe baฤŸฤฑmlฤฑlฤฑk. +documentation.needOutput.label=Gerekli iลŸ รงฤฑktฤฑsฤฑ +documentation.reusableJob.label=Yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ iลŸi +documentation.reusableJob.description=Bu yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑnda iลŸ bildirildi. +documentation.reusableJobOutput.label=Yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ iลŸ รงฤฑkฤฑลŸฤฑ +documentation.service.label=Servis konteyneri +documentation.servicePort.label=Servis portu +documentation.container.label=ฤฐลŸ kapsayฤฑcฤฑsฤฑ +documentation.symbol.label=ฤฐลŸ akฤฑลŸฤฑ sembolรผ +documentation.symbol.description=ร‡รถzรผmlenen iลŸ akฤฑลŸฤฑ ifadesi. +documentation.workflowOutput.label=ฤฐลŸ akฤฑลŸฤฑ รงฤฑkฤฑลŸฤฑ +documentation.jobOutput.label=ฤฐลŸ รงฤฑkฤฑลŸฤฑ +documentation.action.label=Eylem +documentation.externalAction.label=Harici eylem +documentation.reusableWorkflow.label=Yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ +documentation.resolvedFrom={0}''ten รงรถzรผldรผ +documentation.notResolved=henรผz รงรถzรผlmedi +documentation.inputs.title=GiriลŸler +documentation.outputs.title=ร‡ฤฑkฤฑลŸlar +documentation.secrets.title=Sฤฑrlar +documentation.value.label=DeฤŸer +documentation.step.title=Adฤฑm {0} +documentation.name.label=ฤฐsim +documentation.uses.label=Kullanฤฑm Alanlarฤฑ +documentation.run.label=ร‡alฤฑลŸtฤฑr +documentation.description.label=Aรงฤฑklama +documentation.step.label=Adฤฑm +documentation.source.label=Kaynak +documentation.stepOutput.label=Adฤฑm รงฤฑkฤฑลŸฤฑ +documentation.context.github=github baฤŸlamฤฑ +documentation.context.github.description=Geรงerli iลŸ akฤฑลŸฤฑ รงalฤฑลŸtฤฑrmasฤฑ ve olayฤฑ hakkฤฑnda bilgi. +documentation.context.gitea=gitea baฤŸlamฤฑ +documentation.context.gitea.description=GitHub Eylemleri baฤŸlamฤฑ iรงin Gitea uyumlu takma ad. +documentation.context.inputs=girdi baฤŸlamฤฑ +documentation.context.inputs.description=ฤฐลŸ akฤฑลŸฤฑ, sevkฤฑyat veya eylem giriลŸleri burada mevcuttur. +documentation.context.secrets=sฤฑrlar baฤŸlamฤฑ +documentation.context.secrets.description=Bu iลŸ akฤฑลŸฤฑnda veya yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ รงaฤŸrฤฑsฤฑnda kullanฤฑlabilen gizli deฤŸerler. +documentation.context.env=env baฤŸlamฤฑ +documentation.context.env.description=Ortam deฤŸiลŸkenleri bu konumda gรถrรผnรผr. +documentation.context.matrix=matris baฤŸlamฤฑ +documentation.context.matrix.description=Geรงerli iลŸin matris deฤŸerleri. +documentation.context.steps=adฤฑmlar baฤŸlamฤฑ +documentation.context.steps.description=ร‡ฤฑkฤฑลŸlar ve durum da dahil olmak รผzere geรงerli iลŸteki รถnceki adฤฑmlar. +documentation.context.needs=baฤŸlama ihtiyaรง var +documentation.context.needs.description=DoฤŸrudan iลŸ baฤŸฤฑmlฤฑlฤฑklarฤฑ ve bunlarฤฑn รงฤฑktฤฑlarฤฑ/sonuรงlarฤฑ. +documentation.context.jobs=iลŸler baฤŸlamฤฑ +documentation.context.jobs.description=Yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ iลŸleri ve รงฤฑktฤฑlarฤฑ. +documentation.context.outputs=รงฤฑktฤฑlar +documentation.context.outputs.description=Bu adฤฑmฤฑn veya iลŸin aรงฤฑฤŸa รงฤฑkardฤฑฤŸฤฑ รงฤฑktฤฑ deฤŸerleri. +documentation.context.result=sonuรง +documentation.context.result.description=ฤฐลŸin sonucu: baลŸarฤฑ, baลŸarฤฑsฤฑzlฤฑk, iptal edildi veya atlandฤฑ. +documentation.context.outcome=sonuรง +documentation.context.outcome.description=Hata durumunda devam etme uygulanmadan รถnceki adฤฑm sonucu. +documentation.context.conclusion=sonuรง +documentation.context.conclusion.description=Hata durumunda devam etme uygulandฤฑktan sonraki adฤฑm sonucu. +error.report.action=ฤฐstisnayฤฑ Rapor Et +error.report.description=Aรงฤฑklama +error.report.steps=ร‡oฤŸaltmanฤฑn Adฤฑmlarฤฑ +error.report.sample=Lรผtfen varsa kod รถrneฤŸini saฤŸlayฤฑn +error.report.message=Mesaj +error.report.runtime=ร‡alฤฑลŸma Zamanฤฑ Bilgileri +error.report.pluginVersion=Eklenti sรผrรผmรผ: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=YฤฑฤŸฤฑn izleme +completion.shell.bash=Bash kabuฤŸu. Linux ve macOS รงalฤฑลŸtฤฑrฤฑcฤฑlarฤฑnda bash''ฤฑ ve Windows รงalฤฑลŸtฤฑrฤฑcฤฑlarฤฑnda Windows bash iรงin Git''i kullanฤฑr. +completion.shell.sh=POSIX kabuk geri dรถnรผลŸรผ. +completion.shell.pwsh=PowerShell ร‡ekirdek. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Windows komut istemi. +completion.shell.python=Python komut รงalฤฑลŸtฤฑrฤฑcฤฑsฤฑ. +completion.runner.name=ฤฐลŸi yรผrรผten รงalฤฑลŸtฤฑrฤฑcฤฑnฤฑn adฤฑ. +completion.runner.os=ฤฐลŸi yรผrรผten รงalฤฑลŸtฤฑrฤฑcฤฑnฤฑn iลŸletim sistemi. Olasฤฑ deฤŸerler Linux, Windows veya macOS''tir. +completion.runner.arch=ฤฐลŸi yรผrรผten koลŸucunun mimarisi. Olasฤฑ deฤŸerler X86, X64, ARM veya ARM64''tรผr. +completion.runner.temp=ร‡alฤฑลŸtฤฑrฤฑcฤฑdaki geรงici dizinin yolu. Bu dizin her iลŸin baลŸฤฑnda ve sonunda boลŸaltฤฑlฤฑr. KoลŸucunun kullanฤฑcฤฑ hesabฤฑnฤฑn bunlarฤฑ silme izni olmamasฤฑ durumunda dosyalarฤฑn kaldฤฑrฤฑlmayacaฤŸฤฑnฤฑ unutmayฤฑn. +completion.runner.toolCache=GitHub tarafฤฑndan barฤฑndฤฑrฤฑlan รงalฤฑลŸtฤฑrฤฑcฤฑlar iรงin รถnceden yรผklenmiลŸ araรงlarฤฑ iรงeren dizinin yolu. +completion.runner.debug=Bu yalnฤฑzca hata ayฤฑklama gรผnlรผฤŸรผ etkinse ayarlanฤฑr ve her zaman 1 deฤŸerine sahiptir. +completion.runner.environment=ฤฐลŸi yรผrรผten koลŸucunun ortamฤฑ. Olasฤฑ deฤŸerler github''da barฤฑndฤฑrฤฑlan veya kendi kendine barฤฑndฤฑrฤฑlandฤฑr. +completion.job.status=ฤฐลŸin mevcut durumu. +completion.job.checkRunId=Geรงerli iลŸin kontrol รงalฤฑลŸtฤฑrmasฤฑ kimliฤŸi. +completion.job.container=ฤฐลŸin kapsayฤฑcฤฑsฤฑ hakkฤฑnda bilgi. +completion.job.services=Bir iลŸ iรงin oluลŸturulan hizmet kapsayฤฑcฤฑlarฤฑ. +completion.job.workflowRef=Geรงerli iลŸi tanฤฑmlayan iลŸ akฤฑลŸฤฑ dosyasฤฑnฤฑn tam referansฤฑ. +completion.job.workflowSha=Geรงerli iลŸi tanฤฑmlayan iลŸ akฤฑลŸฤฑ dosyasฤฑnฤฑn SHA taahhรผdรผ. +completion.job.workflowRepository=Geรงerli iลŸi tanฤฑmlayan iลŸ akฤฑลŸฤฑ dosyasฤฑnฤฑ iรงeren havuzun sahibi/deposu. +completion.job.workflowFilePath=Depo kรถkรผne gรถre iลŸ akฤฑลŸฤฑ dosyasฤฑ yolu. +completion.job.containerField=ฤฐลŸ konteyneri alanฤฑ +completion.job.service=ฤฐลŸ hizmeti +completion.job.serviceField=ฤฐลŸ hizmeti alanฤฑ +completion.job.mappedServicePort=EลŸlenen servis baฤŸlantฤฑ noktasฤฑ +completion.strategy.failFast=Herhangi bir matris iลŸi baลŸarฤฑsฤฑz olursa devam eden tรผm iลŸlerin iptal edilip edilmeyeceฤŸi. +completion.strategy.jobIndex=Matristeki mevcut iลŸin sฤฑfฤฑr tabanlฤฑ dizini. +completion.strategy.jobTotal=Matristeki toplam iลŸ sayฤฑsฤฑ. +completion.strategy.maxParallel=Aynฤฑ anda รงalฤฑลŸabilecek maksimum matris iลŸi sayฤฑsฤฑ. +completion.context.inputs=workflow_dispatch veya workflow_call gibi iลŸ akฤฑลŸฤฑ giriลŸleri. +completion.context.secrets=ฤฐลŸ akฤฑลŸฤฑ sฤฑrlarฤฑ. +completion.context.job=ลžu anda yรผrรผtรผlen iลŸ hakkฤฑnda bilgi. +completion.context.jobs=ฤฐลŸ akฤฑลŸฤฑ iลŸleri. +completion.context.matrix=Geรงerli matris iลŸi iรงin tanฤฑmlanan matris รถzellikleri. +completion.context.strategy=Geรงerli iลŸ iรงin matris yรผrรผtme stratejisi bilgileri. +completion.context.steps=Geรงerli iลŸte kimliฤŸe sahip adฤฑmlar. +completion.context.env=ฤฐลŸlerden ve adฤฑmlardan ortam deฤŸiลŸkenleri. +completion.context.vars=KuruluลŸ, depo ve ortam kapsamlarฤฑndan รถzel yapฤฑlandฤฑrma deฤŸiลŸkenleri. +completion.context.needs=Bu iลŸin yรผrรผtรผlebilmesi iรงin tamamlanmasฤฑ gereken iลŸler ve bunlarฤฑn รงฤฑktฤฑlarฤฑ ve sonuรงlarฤฑ. +completion.context.github=GitHub baฤŸlamฤฑndan iลŸ akฤฑลŸฤฑ รงalฤฑลŸtฤฑrmasฤฑ ve olay bilgileri. +completion.context.gitea=GitHub Eylemleri baฤŸlamฤฑ iรงin Gitea uyumlu takma ad. +completion.context.runner=Geรงerli iลŸi yรผrรผten koลŸucu hakkฤฑnda bilgi. +completion.secret.githubToken=Her iลŸ akฤฑลŸฤฑ รงalฤฑลŸtฤฑrmasฤฑ iรงin otomatik olarak oluลŸturulan belirteรง. +completion.remote.repository=Uzak depo +completion.uses.local.workflow=Yerel yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ +completion.uses.local.action=Yerel eylem +completion.uses.ref.known=Bilinen iลŸ akฤฑลŸฤฑ referansฤฑ +completion.uses.ref.remote=Uzaktan iลŸ akฤฑลŸฤฑ referansฤฑ +completion.uses.remote.known=Bilinen uzaktan iลŸlem veya yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ +completion.workflow.syntax=GitHub Eylemler iลŸ akฤฑลŸฤฑ sรถzdizimi +completion.workflow.top.name=ฤฐลŸ akฤฑลŸฤฑ gรถrรผnen adฤฑ +completion.workflow.top.run-name=Dinamik รงalฤฑลŸtฤฑrma adฤฑ +completion.workflow.top.on=ฤฐลŸ akฤฑลŸฤฑnฤฑ baลŸlatan olaylar +completion.workflow.top.permissions=Varsayฤฑlan GITHUB_TOKEN izinleri +completion.workflow.top.env=ฤฐลŸ akฤฑลŸฤฑ genelinde ortam deฤŸiลŸkenleri +completion.workflow.top.defaults=Varsayฤฑlan iลŸ ve adฤฑm ayarlarฤฑ +completion.workflow.top.concurrency=EลŸzamanlฤฑlฤฑk grubu ve iptal +completion.workflow.top.jobs=Bu iลŸ akฤฑลŸฤฑnda รงalฤฑลŸan iลŸler +completion.workflow.event.branch_protection_rule=ลžube korumasฤฑ deฤŸiลŸtirildi +completion.workflow.event.check_run=Tek kontrol รงalฤฑลŸtฤฑrmasฤฑ deฤŸiลŸtirildi +completion.workflow.event.check_suite=Kontrol paketi deฤŸiลŸtirildi +completion.workflow.event.create=ลžube veya etiket oluลŸturuldu +completion.workflow.event.delete=ลžube veya etiket silindi +completion.workflow.event.deployment=DaฤŸฤฑtฤฑm oluลŸturuldu +completion.workflow.event.deployment_status=DaฤŸฤฑtฤฑm durumu deฤŸiลŸti +completion.workflow.event.discussion=TartฤฑลŸma deฤŸiลŸtirildi +completion.workflow.event.discussion_comment=TartฤฑลŸma yorumu deฤŸiลŸtirildi +completion.workflow.event.fork=Depo รงatallandฤฑ +completion.workflow.event.gollum=Wiki sayfasฤฑ deฤŸiลŸtirildi +completion.workflow.event.image_version=Paket gรถrseli sรผrรผmรผ deฤŸiลŸtirildi +completion.workflow.event.issue_comment=Sorun veya PR yorumu deฤŸiลŸtirildi +completion.workflow.event.issues=Sorun deฤŸiลŸtirildi +completion.workflow.event.label=Etiket deฤŸiลŸtirildi +completion.workflow.event.merge_group=BirleลŸtirme sฤฑrasฤฑ kontrolรผ istendi +completion.workflow.event.milestone=Kilometre taลŸฤฑ deฤŸiลŸti +completion.workflow.event.page_build=Sayfa oluลŸturma รงalฤฑลŸtฤฑrฤฑldฤฑ +completion.workflow.event.project=Klasik proje deฤŸiลŸtirildi +completion.workflow.event.project_card=Klasik proje kartฤฑ deฤŸiลŸtirildi +completion.workflow.event.project_column=Klasik proje sรผtunu deฤŸiลŸtirildi +completion.workflow.event.public=Depo halka aรงฤฑldฤฑ +completion.workflow.event.pull_request=ร‡ekme isteฤŸi deฤŸiลŸtirildi +completion.workflow.event.pull_request_review=PR incelemesi deฤŸiลŸtirildi +completion.workflow.event.pull_request_review_comment=PR inceleme yorumu deฤŸiลŸtirildi +completion.workflow.event.pull_request_target=PR hedef baฤŸlamฤฑ. Keskin bฤฑรงaklar. +completion.workflow.event.push=Kaydetme veya etiket aktarฤฑldฤฑ +completion.workflow.event.registry_package=Paket yayฤฑnlandฤฑ veya gรผncellendi +completion.workflow.event.release=Sรผrรผm deฤŸiลŸtirildi +completion.workflow.event.repository_dispatch=ร–zel API etkinliฤŸi +completion.workflow.event.schedule=Cron tik tak. Otomatik. +completion.workflow.event.status=Kaydetme durumu deฤŸiลŸti +completion.workflow.event.watch=Kod deposuna yฤฑldฤฑz eklendi +completion.workflow.event.workflow_call=Yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ รงaฤŸrฤฑsฤฑ +completion.workflow.event.workflow_dispatch=Manuel รงalฤฑลŸtฤฑrma dรผฤŸmesi +completion.workflow.event.workflow_run=ฤฐลŸ akฤฑลŸฤฑ รงalฤฑลŸtฤฑrmasฤฑ deฤŸiลŸtirildi +completion.workflow.eventFilter.types=Etkinlik tรผrlerini sฤฑnฤฑrlayฤฑn +completion.workflow.eventFilter.branches=Sadece bu ลŸubeler +completion.workflow.eventFilter.branches-ignore=Bu ลŸubeleri atla +completion.workflow.eventFilter.tags=Yalnฤฑzca bu etiketler +completion.workflow.eventFilter.tags-ignore=Bu etiketleri atla +completion.workflow.eventFilter.paths=Sadece bu yollar +completion.workflow.eventFilter.paths-ignore=Bu yollarฤฑ atla +completion.workflow.eventFilter.workflows=ฤฐzlenecek iลŸ akฤฑลŸฤฑ adlarฤฑ +completion.workflow.eventFilter.cron=Cron programฤฑ. Kรผรงรผk saat mekanizmasฤฑ. +completion.workflow.permission.actions=ฤฐลŸ akฤฑลŸฤฑ รงalฤฑลŸtฤฑrmalarฤฑ ve eylem yapฤฑlarฤฑ +completion.workflow.permission.artifact-metadata=Yapฤฑ meta veri kayฤฑtlarฤฑ +completion.workflow.permission.attestations=Artefakt kanฤฑtlarฤฑ +completion.workflow.permission.checks=KoลŸularฤฑ ve sรผitleri kontrol edin +completion.workflow.permission.code-quality=Kod kalitesi raporlarฤฑ +completion.workflow.permission.contents=Depo iรงeriฤŸi +completion.workflow.permission.deployments=DaฤŸฤฑtฤฑmlar +completion.workflow.permission.discussions=TartฤฑลŸmalar +completion.workflow.permission.id-token=OpenID Connect belirteรงleri +completion.workflow.permission.issues=Sorunlar +completion.workflow.permission.models=GitHub Modelleri +completion.workflow.permission.packages=GitHub Paketleri +completion.workflow.permission.pages=GitHub Sayfalarฤฑ +completion.workflow.permission.pull-requests=ร‡ekme istekleri +completion.workflow.permission.security-events=Kod tarama ve gรผvenlik olaylarฤฑ +completion.workflow.permission.statuses=Durumlarฤฑ taahhรผt et +completion.workflow.permission.vulnerability-alerts=Dependabot uyarฤฑlarฤฑ +completion.workflow.permission.value.read=Okuma eriลŸimi +completion.workflow.permission.value.write=Yazma eriลŸimi, okuma dahil +completion.workflow.permission.value.none=EriลŸim yok +completion.workflow.permission.shorthand.read-all=Tรผm izinler okundu. Bรผyรผk battaniye. +completion.workflow.permission.shorthand.write-all=Tรผm izinler yazฤฑyor. Bรผyรผk รงekiรง. +completion.workflow.permission.shorthand.empty=Belirteรง izinlerini devre dฤฑลŸฤฑ bฤฑrak +completion.workflow.job.name=ฤฐลŸin gรถrรผnen adฤฑ +completion.workflow.job.permissions=ฤฐลŸ jetonu izinleri +completion.workflow.job.needs=Beklenecek iลŸler +completion.workflow.job.if=ฤฐลŸ durumu +completion.workflow.job.runs-on=KoลŸucu etiketi veya grubu +completion.workflow.job.snapshot=KoลŸucu anlฤฑk gรถrรผntรผsรผ +completion.workflow.job.environment=DaฤŸฤฑtฤฑm ortamฤฑ +completion.workflow.job.concurrency=ฤฐลŸ eลŸzamanlฤฑlฤฑk kilidi +completion.workflow.job.outputs=DiฤŸer iลŸlerin okuyabileceฤŸi รงฤฑktฤฑlar +completion.workflow.job.env=ฤฐลŸ ortamฤฑ deฤŸiลŸkenleri +completion.workflow.job.defaults=ฤฐลŸ varsayฤฑlan ayarlarฤฑ +completion.workflow.job.steps=Adฤฑm listesi. Gerรงek iลŸ. +completion.workflow.job.timeout-minutes=Dakika cinsinden iลŸ zaman aลŸฤฑmฤฑ +completion.workflow.job.strategy=Matris ve planlama stratejisi +completion.workflow.job.continue-on-error=Bu iลŸin usulca baลŸarฤฑsฤฑz olmasฤฑna izin ver +completion.workflow.job.container=Bu iลŸ iรงin konteyner +completion.workflow.job.services=Sepet servis konteynerleri +completion.workflow.job.uses=Aramak iรงin yeniden kullanฤฑlabilir iลŸ akฤฑลŸฤฑ +completion.workflow.job.with=ฤฐลŸ akฤฑลŸฤฑ adฤฑ verilen giriลŸler +completion.workflow.job.secrets=ฤฐลŸ akฤฑลŸฤฑnฤฑn sฤฑrlarฤฑ +completion.workflow.defaultsRun.shell=ร‡alฤฑลŸtฤฑrma adฤฑmlarฤฑ iรงin varsayฤฑlan kabuk +completion.workflow.defaultsRun.working-directory=Varsayฤฑlan รงalฤฑลŸma dizini +completion.workflow.concurrency.group=Sฤฑraya alฤฑnmฤฑลŸ รงalฤฑลŸtฤฑrmalar iรงin kilit adฤฑ +completion.workflow.concurrency.cancel-in-progress=Eski eลŸleลŸen รงalฤฑลŸtฤฑrmalarฤฑ iptal edin +completion.workflow.environment.name=Ortam adฤฑ +completion.workflow.environment.url=Ortam URL +completion.workflow.strategy.matrix=Matris eksenleri ve รงeลŸitleri +completion.workflow.strategy.fail-fast=BaลŸarฤฑsฤฑzlฤฑk durumunda matris kardeลŸlerini iptal et +completion.workflow.strategy.max-parallel=Matris paralellik sฤฑnฤฑrฤฑ +completion.workflow.matrix.include=Matris kombinasyonlarฤฑ ekleyin +completion.workflow.matrix.exclude=Matris kombinasyonlarฤฑnฤฑ kaldฤฑr +completion.workflow.step.id=Referanslar iรงin adฤฑm kimliฤŸi +completion.workflow.step.if=Adฤฑm koลŸulu +completion.workflow.step.name=Adฤฑm gรถrรผnen adฤฑ +completion.workflow.step.uses=ร‡alฤฑลŸtฤฑrฤฑlacak eylem +completion.workflow.step.run=ร‡alฤฑลŸtฤฑrฤฑlacak kabuk betiฤŸi +completion.workflow.step.shell=Bu รงalฤฑลŸtฤฑrma adฤฑmฤฑ iรงin kabuk +completion.workflow.step.with=Eylem giriลŸleri +completion.workflow.step.env=Adฤฑm ortamฤฑ deฤŸiลŸkenleri +completion.workflow.step.continue-on-error=Bu adฤฑmฤฑn yavaลŸรงa baลŸarฤฑsฤฑz olmasฤฑna izin verin +completion.workflow.step.timeout-minutes=Dakika cinsinden adฤฑm zaman aลŸฤฑmฤฑ +completion.workflow.step.working-directory=Adฤฑm รงalฤฑลŸma dizini +completion.workflow.container.image=Konteyner resmi +completion.workflow.container.credentials=Kayฤฑt defteri kimlik bilgileri +completion.workflow.container.env=Kapsayฤฑcฤฑ ortam deฤŸiลŸkenleri +completion.workflow.container.ports=Ortaya รงฤฑkacak baฤŸlantฤฑ noktalarฤฑ +completion.workflow.container.volumes=BaฤŸlanacak hacimler +completion.workflow.container.options=Docker oluลŸturma seรงenekleri +completion.workflow.service.image=Servis konteyneri resmi +completion.workflow.service.credentials=Kayฤฑt defteri kimlik bilgileri +completion.workflow.service.env=Hizmet ortamฤฑ deฤŸiลŸkenleri +completion.workflow.service.ports=Servis baฤŸlantฤฑ noktalarฤฑ +completion.workflow.service.volumes=Hizmet hacimleri +completion.workflow.service.options=Docker oluลŸturma seรงenekleri +completion.workflow.credentials.username=Kayฤฑt defteri kullanฤฑcฤฑ adฤฑ +completion.workflow.credentials.password=Kayฤฑt defteri ลŸifresi veya belirteci +completion.workflow.inputType.string=Metin giriลŸi +completion.workflow.inputType.boolean=DoฤŸru veya yanlฤฑลŸ giriลŸ +completion.workflow.inputType.choice=Aรงฤฑlan seรงim giriลŸi +completion.workflow.inputType.number=Sayฤฑ giriลŸi +completion.workflow.inputType.environment=Ortam seรงici giriลŸi +completion.workflow.boolean.true=Evet. ร‡evir onu. +completion.workflow.boolean.false=Hayฤฑr. Karanlฤฑkta tut. +completion.workflow.runner.ubuntu-latest=En yeni Ubuntu koลŸucusu +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 koลŸucu +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 koลŸucu +completion.workflow.runner.windows-latest=En yeni Windows koลŸucusu +completion.workflow.runner.windows-2025=Windows Sunucu 2025 รงalฤฑลŸtฤฑrฤฑcฤฑsฤฑ +completion.workflow.runner.windows-2022=Windows Sunucu 2022 รงalฤฑลŸtฤฑrฤฑcฤฑsฤฑ +completion.workflow.runner.macos-latest=En yeni macOS koลŸucusu +completion.workflow.runner.macos-15=macOS 15 koลŸucu +completion.workflow.runner.macos-14=macOS 14 koลŸucu +completion.workflow.runner.self-hosted=Kendi koลŸucusun. Senin sirkin. +completion.steps.outputs=Adฤฑm iรงin tanฤฑmlanan รงฤฑkฤฑลŸ kรผmesi. +completion.steps.conclusion=Hata durumunda devam edildikten sonra tamamlanan bir adฤฑmฤฑn sonucu uygulanฤฑr. +completion.steps.outcome=Hata durumunda devam etmeden รถnce tamamlanan bir adฤฑmฤฑn sonucu uygulanฤฑr. +completion.jobs.outputs=ฤฐลŸ iรงin tanฤฑmlanan รงฤฑktฤฑlar kรผmesi. +completion.jobs.result=ฤฐลŸin sonucu. +settings.displayName=GitHub ฤฐลŸ AkฤฑลŸฤฑ +settings.language.label=Dil: +settings.language.system=IDE/sistem varsayฤฑlanฤฑ +settings.cache.title=Eylem รถnbelleฤŸi +settings.cache.column.key=ร–nbellek anahtarฤฑ +settings.cache.column.name=ฤฐsim +settings.cache.column.kind=tรผr +settings.cache.column.state=Eyalet +settings.cache.column.expires=Sรผresi doluyor +settings.cache.kind.local=yerel +settings.cache.kind.remote=uzak +settings.cache.state.resolved=รงรถzรผldรผ +settings.cache.state.pending=beklemede +settings.cache.state.expired=bayat +settings.cache.state.suppressed=bastฤฑrฤฑlmฤฑลŸ +settings.cache.refresh=Refresh tablosu +settings.cache.deleteSelected=Seรงileni sil +settings.cache.deleteAll=Tรผmรผnรผ sil +settings.cache.export=ฤฐhracat +settings.cache.import=ฤฐthalat +settings.cache.summary=ร–nbellek: {0} giriลŸleri, {1} รงรถzรผmlendi, {2} uzak, {3} eski, {4} sessize alฤฑndฤฑ. ร–nbellek: {5} KB. +settings.cache.noneSelected=ร–nce รถnbellek satฤฑrlarฤฑnฤฑ seรงin. Sรผpรผrge tahmin yรผrรผtmeyi reddediyor. +settings.cache.deleteSelected.done={0} รถnbellek giriลŸleri silindi. Minik toz bulutu iรงeriyordu. +settings.cache.deleteAll.confirm=Tรผm GitHub ฤฐลŸ AkฤฑลŸฤฑ รถnbellek giriลŸleri silinsin mi? +settings.cache.deleteAll.done=Tรผm รถnbellek giriลŸleri silindi. ร–nbellek artฤฑk ลŸรผphe uyandฤฑracak kadar sessiz. +settings.cache.export.done=DฤฑลŸa aktarฤฑlan {0} รถnbellek giriลŸleri. Kรผรงรผk arลŸiv canavarฤฑ kafeste. +settings.cache.import.done=ฤฐรงe aktarฤฑlan รถnbellek giriลŸleri. ArลŸiv canavarฤฑ davrandฤฑ. +settings.cache.import.unsupported=Desteklenmeyen GitHub ฤฐลŸ AkฤฑลŸฤฑ รถnbellek dosyasฤฑ. +settings.cache.import.brokenLine=Bozuk GitHub ฤฐลŸ AkฤฑลŸฤฑ รถnbellek hattฤฑ. +settings.cache.import.brokenKey=Bozuk GitHub ฤฐลŸ AkฤฑลŸฤฑ รถnbellek anahtarฤฑ. +settings.support.button=Bu eklentiyi destekleyin +settings.support.tooltip=Destek sayfasฤฑnฤฑ aรงฤฑn +settings.support.line.0=Yapฤฑ fฤฑrฤฑnฤฑnฤฑ besleyin +settings.support.line.1=AyrฤฑลŸtฤฑrฤฑcฤฑ kahveyi satฤฑn alฤฑn +settings.support.line.2=Daha az rahatsฤฑz edici iลŸ akฤฑลŸฤฑna sponsor olun +workflow.run.jobs.title=ฤฐลŸ akฤฑลŸฤฑ iลŸleri +workflow.run.jobs.root=ฤฐลŸ akฤฑลŸฤฑ รงalฤฑลŸtฤฑrmasฤฑ +workflow.run.jobs.description=GitHub Eylemler iลŸ aฤŸacฤฑ ve seรงilen iลŸ gรผnlรผฤŸรผ +workflow.run.tree.done=bitti +workflow.run.tree.failed=baลŸarฤฑsฤฑz oldu +workflow.run.tree.skipped=atlandฤฑ +workflow.run.tree.warn=uyarmak +workflow.run.tree.err=hata +workflow.run.delete.tooltip=ร‡alฤฑลŸtฤฑrmayฤฑ sil +workflow.run.delete.noRun=Henรผz รงalฤฑลŸtฤฑrma kimliฤŸi yok. +workflow.run.delete.requested={0} รงalฤฑลŸtฤฑrmasฤฑ siliniyor. +workflow.run.delete.done={0} รงalฤฑลŸtฤฑrmasฤฑ silindi. +workflow.run.delete.http=HTTP {0}''i silin. Of. +workflow.run.delete.failed=Silinmedi: {0} +workflow.run.rerun.all.tooltip=ฤฐลŸ akฤฑลŸฤฑnฤฑ yeniden รงalฤฑลŸtฤฑr +workflow.run.rerun.failed.tooltip=BaลŸarฤฑsฤฑz iลŸleri yeniden รงalฤฑลŸtฤฑrฤฑn +workflow.run.rerun.noRun=Henรผz รงalฤฑลŸtฤฑrma kimliฤŸi yok. +workflow.run.rerun.all.requested=Yeniden รงalฤฑลŸtฤฑrma isteฤŸi: {0}. +workflow.run.rerun.failed.requested=BaลŸarฤฑsฤฑz istenen iลŸler: {0}. +workflow.run.rerun.all.done=Tekrar รงalฤฑลŸtฤฑrma sฤฑraya alฤฑndฤฑ: {0}. +workflow.run.rerun.failed.done=BaลŸarฤฑsฤฑz iลŸler sฤฑraya alฤฑndฤฑ: {0}. +workflow.run.rerun.http=HTTP {0}''i yeniden รงalฤฑลŸtฤฑrฤฑn. Of. +workflow.run.rerun.failed=Tekrar รงalฤฑลŸtฤฑrma baลŸarฤฑsฤฑz oldu: {0} +workflow.run.download.log.tooltip=ฤฐลŸ gรผnlรผฤŸรผnรผ kaydet +workflow.run.download.artifacts.tooltip=Yapฤฑlarฤฑ indirin +workflow.run.download.noRun=Henรผz รงalฤฑลŸtฤฑrma kimliฤŸi yok. +workflow.run.download.log.requested={0} iรงin gรผnlรผk getiriliyor. +workflow.run.download.log.done=Gรผnlรผk kaydedildi: {0}. +workflow.run.download.artifacts.requested=Eserler getiriliyor. +workflow.run.download.artifacts.empty=Eser yok. Minik boลŸluk. +workflow.run.download.artifact.expired=Yapฤฑnฤฑn sรผresi doldu: {0}. +workflow.run.download.artifact.done=Yapฤฑ kaydedildi: {0} -> {1}. +workflow.run.download.failed=ฤฐndirme baลŸarฤฑsฤฑz oldu: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_uk.properties b/src/main/resources/messages/GitHubWorkflowBundle_uk.properties new file mode 100644 index 0000000..552f490 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_uk.properties @@ -0,0 +1,442 @@ +plugin.name=ะ ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั GitHub +plugin.description=ะŸั–ะดั‚ั€ะธะผะบะฐ ั„ะฐะนะปั–ะฒ ั€ะพะฑะพั‡ะพะณะพ ั†ะธะบะปัƒ ะดั–ะน GitHub +group.GitHubWorkflow.Tools.text=ะ ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั GitHub +group.GitHubWorkflow.Tools.description=GitHub ะŸะปะฐะณั–ะฝะธ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +action.GitHubWorkflow.RefreshActionCache.text=ะšะตัˆ ะดั–ะน Refresh +action.GitHubWorkflow.RefreshActionCache.description=Refresh ะฒะธั€ั–ัˆะธะฒ ะฒั–ะดะดะฐะปะตะฝั– ะดั–ั— GitHub ั– ะฟะพะฒั‚ะพั€ะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะฝั– ะผะตั‚ะฐะดะฐะฝั– ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +action.GitHubWorkflow.RestoreActionWarnings.text=ะŸะพะฟะตั€ะตะดะถะตะฝะฝั ะฟั€ะพ ะดั–ั— ะฒั–ะดะฝะพะฒะปะตะฝะฝั +action.GitHubWorkflow.RestoreActionWarnings.description=ะ’ั–ะดะฝะพะฒะปะตะฝะฝั ะฟั€ะธะณะฝั–ั‡ะตะฝะธั… ะดั–ะน, ะฟะพะฟะตั€ะตะดะถะตะฝัŒ ะฟะตั€ะตะฒั–ั€ะบะธ ะฒะฒะตะดะตะฝะฝั ั‚ะฐ ะฒะธะฒะตะดะตะฝะฝั +action.GitHubWorkflow.ClearActionCache.text=ะžั‡ะธัั‚ะธั‚ะธ ะบะตัˆ ะดั–ะน +action.GitHubWorkflow.ClearActionCache.description=ะžั‡ะธัั‚ะธั‚ะธ ะบะตัˆะพะฒะฐะฝั– ะดั–ั— GitHub ั– ะฟะพะฒั‚ะพั€ะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะฝั– ะผะตั‚ะฐะดะฐะฝั– ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +notification.cache.cleared=ะ’ะธะดะฐะปะตะฝะพ {0} ะบะตัˆะพะฒะฐะฝั– ะทะฐะฟะธัะธ ั€ะพะฑะพั‡ะพะณะพ ั†ะธะบะปัƒ GitHub. +notification.cache.refresh.started=Refะพะฝะพะฒะปะตะฝะฝั {0} ะบะตัˆะพะฒะฐะฝะธั… ะทะฐะฟะธัั–ะฒ ะฒั–ะดะดะฐะปะตะฝะพะณะพ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ GitHub. +notification.warnings.restored=ะ’ั–ะดะฝะพะฒะปะตะฝะพ ะฟะพะฟะตั€ะตะดะถะตะฝะฝั ะดะปั ะทะฐะฟะธัั–ะฒ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ {0} GitHub. +workflow.run.configuration.display=ะ ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั GitHub +workflow.run.configuration.description=ะ’ั–ะดะฟั€ะฐะฒะปะตะฝะฝั ั‚ะฐ ะฒะธะบะพะฝะฐะฝะฝั ั€ะพะฑะพั‡ะพะณะพ ั†ะธะบะปัƒ ะดั–ะน GitHub +workflow.run.configuration.name=ะ ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั GitHub: {0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=ะ’ะปะฐัะฝะธะบ +workflow.run.field.repo=ะ ะตะฟะพะทะธั‚ะพั€ั–ะน +workflow.run.field.workflow=ะคะฐะนะป ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=ะ—ะฐะฟะฐัะฝะฐ ะทะผั–ะฝะฝะฐ ะผะฐั€ะบะตั€ะฐ env +workflow.run.inputs.title=ะ’ั…ั–ะดะฝั– ะดะฐะฝั– workflow_dispatch (ะบะปัŽั‡=ะทะฝะฐั‡ะตะฝะฝั) +workflow.run.error.apiUrl=ะŸะพั‚ั€ั–ะฑะตะฝ GitHub API URL. +workflow.run.error.repository=ะŸะพั‚ั€ั–ะฑะฝั– ะฒะปะฐัะฝะธะบ ั– ั–ะผโ€™ั ัั…ะพะฒะธั‰ะฐ GitHub. +workflow.run.error.workflow=ะŸะพั‚ั€ั–ะฑะตะฝ ั„ะฐะนะป ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ. +workflow.run.error.ref=ะŸะพั‚ั€ั–ะฑะฝะพ ะฒะบะฐะทะฐั‚ะธ ะฟะพัะธะปะฐะฝะฝั ะฝะฐ ะณั–ะปะบัƒ ะฐะฑะพ ั‚ะตะณ. +workflow.run.error.inputs=GitHub workflow_dispatch ะฟั–ะดั‚ั€ะธะผัƒั” ั‰ะพะฝะฐะนะฑั–ะปัŒัˆะต 25 ะฒั…ะพะดั–ะฒ. +workflow.run.gutter.stop=ะ—ัƒะฟะธะฝะธั‚ะธ ะทะฐะฟัƒัะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +workflow.run.gutter.stop.text=ะ—ัƒะฟะธะฝะธั‚ะธ ะทะฐะฟัƒัะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +workflow.run.gutter.stop.description=ะกะบะฐััƒะฒะฐั‚ะธ ั†ะตะน ะทะฐะฟัƒัะบ +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=ะทะฐะฟัƒัั‚ะธั‚ะธ: +workflow.log.warning=ะฟะพะฟะตั€ะตะดะถะตะฝะฝั: +workflow.log.error=ะฟะพะผะธะปะบะฐ: +workflow.run.cancel.requested=ะ—ะฐะฟะธั‚ ะฝะฐ ัะบะฐััƒะฒะฐะฝะฝั: {0}. +workflow.run.stop.before.id=ะ—ัƒะฟะธะฝะธั‚ะธ ะทะฐะฟะธั‚. ะฉะต ะฝะตะผะฐั” ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบัƒ. +workflow.run.cancel.http=ะกะบะฐััƒะฒะฐั‚ะธ HTTP {0}. ะžะน. +workflow.run.cancel.failed=ะกะบะฐััƒะฒะฐะฝะฝั ะทั–ะฟัะพะฒะฐะฝะพ: {0} +workflow.run.interrupted=ะŸะตั€ะตั€ะฒะฐะฝะธะน. +workflow.run.link=ะ—ะฐะฟัƒัะบ: {0} +workflow.run.discovery=ะŸั€ะพะฑั–ะณ ะฟั€ะธะนะฝัั‚ะพ. ะ†ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะฟะพะปัŽะฒะฐะฝะฝั. +workflow.run.discovery.none=ะฉะต ะฝะตะผะฐั” ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบัƒ. ะ’ะบะปะฐะดะบะฐ ยซะ”ั–ั—ยป ะทะฝะฐั” ะฑั–ะปัŒัˆะต. +workflow.run.status=ะกั‚ะฐั‚ัƒั: {0}{1} +workflow.run.job.main=ะŸะพัะฐะดะฐ: {0} {1} [{2}{3}{4}] +workflow.run.job.status=ะกั‚ะฐั‚ัƒั: {0} {1}{2}{3} +workflow.run.logs.later=ะ–ัƒั€ะฝะฐะปะธ ะทโ€™ัะฒะปัั‚ัŒัั, ะบะพะปะธ GitHub ั—ั… ะพะฟัƒะฑะปั–ะบัƒั”. +workflow.run.job.logs.later=ะ ะพะฑะพั‚ะฐ {0}: {1} +workflow.run.log.failed=ะŸะพะผะธะปะบะฐ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะถัƒั€ะฝะฐะปัƒ: {0} +workflow.run.log.failed.job=ะŸะพะผะธะปะบะฐ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะถัƒั€ะฝะฐะปัƒ ะดะปั {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=ะŸะพัะฐะดะฐ: {0} +workflow.run.job.fallbackName=ะ ะพะฑะพั‚ะฐ {0} +workflow.run.overview=ะ—ะฐะฟัƒัะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ {0} {1}/{2} ะฒะธะบะพะฝะฐะฝะพ, {3} ะทะฐะฟัƒั‰ะตะฝะพ +workflow.run.state.ok=[ะžะš] +workflow.run.state.fail=[ะŸะžะœะ˜ะ›ะšะ] +workflow.run.state.running=[ะ‘ะ†ะ“ะขะ˜] +workflow.run.state.waiting=[ะงะ•ะšะะ™ะขะ•] +workflow.run.dispatch.verbs=ะŸั–ะดะณะพั‚ะพะฒะบะฐ|ะŸะพัั‚ะฐะฝะพะฒะบะฐ ะฒ ั‡ะตั€ะณัƒ|ะ’ะธะบะปะธะบ|ะ—ะฐะฟัƒัะบ|ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั +workflow.run.dispatch.objects=ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั|ะฐะฒั‚ะพะผะฐั‚ะธะทะฐั†ั–ั|ะบะพะฝะฒะตั”ั€|ะทะฐะฟัƒัะบ +workflow.run.dispatch={0} {1} {2}{3} ะดะปั {4} ะฝะฐ {5}. +workflow.run.notification.auth=ะ”ะปั ะฒั–ะดะฟั€ะฐะฒะบะธ ั€ะพะฑะพั‡ะพะณะพ ั†ะธะบะปัƒ GitHub ะฟะพั‚ั€ั–ะฑะตะฝ ะฐะฒั‚ะตะฝั‚ะธั„ั–ะบะพะฒะฐะฝะธะน ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั GitHub. ะ”ะพะดะฐะนั‚ะต ะฐะฑะพ ะพะฝะพะฒั–ั‚ัŒ ะพะฑะปั–ะบะพะฒั– ะทะฐะฟะธัะธ ะฒ {0}. +workflow.run.notification.openSettings=ะ’ั–ะดะบั€ะธะนั‚ะต ะฝะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั GitHub +workflow.cache.progress.title=ะ’ะธั€ั–ัˆะตะฝะฝั ะดั–ะน GitHub +workflow.cache.progress.text=ะ’ะธั€ั–ัˆะตะฝะฝั {0} {1} +workflow.cache.kind.action=ะดั–ัŽ +workflow.cache.kind.workflow=ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั +inspection.parameter.input=ะฒะฒะตะดะตะฝะฝั +inspection.parameter.secret=ัะตะบั€ะตั‚ +inspection.action.delete.invalid=ะ’ะธะดะฐะปะธั‚ะธ ะฝะตะดั–ะนัะฝะธะน {0} [{1}] +inspection.action.update.major=ะžะฝะพะฒะธั‚ะธ ะดั–ัŽ [{0}] ะดะพ [{1}] +inspection.warning.toggle=ะŸะตั€ะตะผะบะฝัƒั‚ะธ ะฟะพะฟะตั€ะตะดะถะตะฝะฝั [{0}] ะดะปั [{1}] +inspection.warning.on=ะฝะฐ +inspection.warning.off=ะฒะธะผะบะฝะตะฝะพ +inspection.statement.incomplete=ะะตะทะฐะฒะตั€ัˆะตะฝะฐ ะทะฐัะฒะฐ [{0}] +inspection.invalid.suffix.remove=ะ’ะธะดะฐะปะธั‚ะธ ะฝะตะดั–ะนัะฝะธะน ััƒั„ั–ะบั [{0}] +inspection.replace.with=ะ—ะฐะผั–ะฝะธั‚ะธ ะฝะฐ [{0}] +inspection.invalid.remove=ะ’ะธะดะฐะปะธั‚ะธ ะฝะตะดั–ะนัะฝะธะน [{0}] +inspection.workflow.syntax.unknownTopLevelKey=ะะตะฒั–ะดะพะผะธะน ะบะปัŽั‡ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ [{0}] +inspection.workflow.syntax.unknownEventKey=ะะตะฒั–ะดะพะผะฐ ะฟะพะดั–ั ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ [{0}] +inspection.workflow.syntax.unknownTriggerKey=ะะตะฒั–ะดะพะผะธะน ั‚ั€ะธะณะตั€ะฝะธะน ะบะปัŽั‡ [{0}] +inspection.workflow.syntax.unknownTriggerFilter=ะะตะฒั–ะดะพะผะธะน ั‚ั€ะธะณะตั€ะฝะธะน ั„ั–ะปัŒั‚ั€ [{0}] +inspection.workflow.syntax.unknownTriggerValue=ะะตะฒั–ะดะพะผะต ะทะฝะฐั‡ะตะฝะฝั ั‚ั€ะธะณะตั€ะฐ [{0}] +inspection.workflow.syntax.unknownPermission=ะะตะฒั–ะดะพะผะธะน ะดะพะทะฒั–ะป [{0}] +inspection.workflow.syntax.unknownPermissionValue=ะะตะฒั–ะดะพะผะต ะทะฝะฐั‡ะตะฝะฝั ะดะพะทะฒะพะปัƒ [{0}] +inspection.workflow.syntax.unknownJobKey=ะะตะฒั–ะดะพะผะธะน ะบะปัŽั‡ ะทะฐะฒะดะฐะฝะฝั [{0}] +inspection.workflow.syntax.unknownStepKey=ะะตะฒั–ะดะพะผะฐ ะบะปะฐะฒั–ัˆะฐ ะบั€ะพะบัƒ [{0}] +inspection.action.reload=ะŸะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ [{0}] +inspection.action.unresolved=ะะตะฒะธั€ั–ัˆะตะฝะพ [{0}] โ€” ะฟะตั€ะตะฒั–ั€ั‚ะต ะดะพัั‚ัƒะฟ ะดะพ ะพะฑะปั–ะบะพะฒะพะณะพ ะทะฐะฟะธััƒ GitHub, ะดะพะทะฒะพะปะธ ะฟั€ะธะฒะฐั‚ะฝะพะณะพ ัั…ะพะฒะธั‰ะฐ, ะพะฑะผะตะถะตะฝะฝั ัˆะฒะธะดะบะพัั‚ั–, ะฒั–ะดััƒั‚ะฝั– ะฟะพัะธะปะฐะฝะฝั ะฐะฑะพ ะฒั–ะดััƒั‚ะฝั– ะผะตั‚ะฐะดะฐะฝั– ะดั–ั—/ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +inspection.action.jump=ะŸะตั€ะตะนั‚ะธ ะดะพ ั„ะฐะนะปัƒ [{0}] +inspection.output.unused=ะะตะฒะธะบะพั€ะธัั‚ะฐะฝะธะน [{0}] +inspection.secret.invalid.if=ะ’ะธะดะฐะปะธั‚ะธ [{0}] - ัะตะบั€ะตั‚ะธ ะฝะตะดั–ะนัะฝั– ะฒ ะพะฟะตั€ะฐั‚ะพั€ะฐั… if +inspection.secret.replace.runtime=ะ—ะฐะผั–ะฝั–ั‚ัŒ [{0}] ะฝะฐ [{1}] - ัะบั‰ะพ ะฒั–ะฝ ะฝะต ะฝะฐะดะฐั”ั‚ัŒัั ะฟั–ะด ั‡ะฐั ะฒะธะบะพะฝะฐะฝะฝั +inspection.needs.invalid.job=ะ’ะธะดะฐะปะธั‚ะธ ะฝะตะดั–ะนัะฝะธะน ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะทะฐะฒะดะฐะฝะฝั [{0}] โ€“ ั†ะตะน ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะทะฐะฒะดะฐะฝะฝั ะฝะต ะฒั–ะดะฟะพะฒั–ะดะฐั” ะถะพะดะฝะพะผัƒ ะฟะพะฟะตั€ะตะดะฝัŒะพะผัƒ ะทะฐะฒะดะฐะฝะฝัŽ +documentation.description=ะžะฟะธั: {0} +documentation.type=ะขะธะฟ: {0} +documentation.required=ะะตะพะฑั…ั–ะดะฝะพ: {0} +documentation.default=ะ—ะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ: {0} +documentation.deprecated=ะ—ะฐัั‚ะฐั€ั–ะปะต: {0} +documentation.open.declaration=ะ’ั–ะดะบั€ะธั‚ะฐ ะดะตะบะปะฐั€ะฐั†ั–ั ({0}) +documentation.input.label=ะ’ะฒะตะดะตะฝะฝั +documentation.secret.label=ะกะตะบั€ะตั‚ +documentation.env.label=ะ—ะผั–ะฝะฝะฐ ัะตั€ะตะดะพะฒะธั‰ะฐ +documentation.matrix.label=ะ’ะปะฐัั‚ะธะฒั–ัั‚ัŒ ะผะฐั‚ั€ะธั†ั– +documentation.need.label=ะŸะพั‚ั€ั–ะฑะฝะฐ ั€ะพะฑะพั‚ะฐ +documentation.need.description=ะŸั€ัะผะฐ ะทะฐะปะตะถะฝั–ัั‚ัŒ ะฒั–ะด ั€ะพะฑะพั‚ะธ. +documentation.needOutput.label=ะะตะพะฑั…ั–ะดะฝะธะน ั€ะตะทัƒะปัŒั‚ะฐั‚ ั€ะพะฑะพั‚ะธ +documentation.reusableJob.label=ะ‘ะฐะณะฐั‚ะพั€ะฐะทะพะฒะต ะทะฐะฒะดะฐะฝะฝั ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +documentation.reusableJob.description=ะ—ะฐะฒะดะฐะฝะฝั, ะพะณะพะปะพัˆะตะฝะต ะฒ ั†ัŒะพะผัƒ ะฑะฐะณะฐั‚ะพั€ะฐะทะพะฒะพะผัƒ ั€ะพะฑะพั‡ะพะผัƒ ะฟั€ะพั†ะตัั–. +documentation.reusableJobOutput.label=ะ‘ะฐะณะฐั‚ะพั€ะฐะทะพะฒะธะน ั€ะตะทัƒะปัŒั‚ะฐั‚ ั€ะพะฑะพั‚ะธ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +documentation.service.label=ะกะตั€ะฒั–ัะฝะธะน ะบะพะฝั‚ะตะนะฝะตั€ +documentation.servicePort.label=ะกะปัƒะถะฑะพะฒะธะน ะฟะพั€ั‚ +documentation.container.label=ะšะพะฝั‚ะตะนะฝะตั€ ะดะปั ั€ะพะฑะพั‚ะธ +documentation.symbol.label=ะกะธะผะฒะพะป ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +documentation.symbol.description=ะ’ะธั€ั–ัˆะตะฝะธะน ะฒะธั€ะฐะท ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ. +documentation.workflowOutput.label=ะ’ะธะฒั–ะด ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +documentation.jobOutput.label=ะ’ะธั…ั–ะด ั€ะพะฑะพั‚ะธ +documentation.action.label=ะ”ั–ั +documentation.externalAction.label=ะ—ะพะฒะฝั–ัˆะฝั ะดั–ั +documentation.reusableWorkflow.label=ะ‘ะฐะณะฐั‚ะพั€ะฐะทะพะฒะธะน ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั +documentation.resolvedFrom=ะฒะธั€ั–ัˆะตะฝะพ ะท {0} +documentation.notResolved=ั‰ะต ะฝะต ะฒะธั€ั–ัˆะตะฝะพ +documentation.inputs.title=ะ’ั…ั–ะดะฝั– ะดะฐะฝั– +documentation.outputs.title=ะ’ะธั…ะพะดะธ +documentation.secrets.title=ะกะตะบั€ะตั‚ะธ +documentation.value.label=ะ—ะฝะฐั‡ะตะฝะฝั +documentation.step.title=ะšั€ะพะบ {0} +documentation.name.label=ะ†ะผ''ั +documentation.uses.label=ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั +documentation.run.label=ะฑั–ะณั‚ะธ +documentation.description.label=ะพะฟะธั +documentation.step.label=ะšั€ะพะบ +documentation.source.label=ะ”ะถะตั€ะตะปะพ +documentation.stepOutput.label=ะกั‚ัƒะฟะตะฝะตะฒะธะน ะฒะธั…ั–ะด +documentation.context.github=ะบะพะฝั‚ะตะบัั‚ github +documentation.context.github.description=ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะฟะพั‚ะพั‡ะฝะธะน ะทะฐะฟัƒัะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ ั‚ะฐ ะฟะพะดั–ัŽ. +documentation.context.gitea=ะบะพะฝั‚ะตะบัั‚ gitea +documentation.context.gitea.description=Gitea-ััƒะผั–ัะฝะธะน ะฟัะตะฒะดะพะฝั–ะผ ะดะปั ะบะพะฝั‚ะตะบัั‚ัƒ ะดั–ะน GitHub. +documentation.context.inputs=ะฒะฒะพะดะธั‚ัŒ ะบะพะฝั‚ะตะบัั‚ +documentation.context.inputs.description=ะ ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั, ะฒั–ะดะฟั€ะฐะฒะปะตะฝะฝั ะฐะฑะพ ะฒะฒะตะดะตะฝะฝั ะดั–ะน ะดะพัั‚ัƒะฟะฝั– ั‚ัƒั‚. +documentation.context.secrets=ัะตะบั€ะตั‚ะฝะธะน ะบะพะฝั‚ะตะบัั‚ +documentation.context.secrets.description=ะกะตะบั€ะตั‚ะฝั– ะทะฝะฐั‡ะตะฝะฝั, ะดะพัั‚ัƒะฟะฝั– ะดะปั ั†ัŒะพะณะพ ั€ะพะฑะพั‡ะพะณะพ ั†ะธะบะปัƒ ะฐะฑะพ ะฑะฐะณะฐั‚ะพั€ะฐะทะพะฒะพะณะพ ะฒะธะบะปะธะบัƒ ั€ะพะฑะพั‡ะพะณะพ ั†ะธะบะปัƒ. +documentation.context.env=ะบะพะฝั‚ะตะบัั‚ env +documentation.context.env.description=ะ—ะผั–ะฝะฝั– ัะตั€ะตะดะพะฒะธั‰ะฐ, ะฒะธะดะธะผั– ะฒ ั†ัŒะพะผัƒ ะผั–ัั†ั–. +documentation.context.matrix=ะผะฐั‚ั€ะธั‡ะฝะธะน ะบะพะฝั‚ะตะบัั‚ +documentation.context.matrix.description=ะ—ะฝะฐั‡ะตะฝะฝั ะผะฐั‚ั€ะธั†ั– ะดะปั ะฟะพั‚ะพั‡ะฝะพั— ั€ะพะฑะพั‚ะธ. +documentation.context.steps=ะบะพะฝั‚ะตะบัั‚ ะบั€ะพะบั–ะฒ +documentation.context.steps.description=ะŸะพะฟะตั€ะตะดะฝั– ะบั€ะพะบะธ ะฟะพั‚ะพั‡ะฝะพะณะพ ะทะฐะฒะดะฐะฝะฝั, ะฒะบะปัŽั‡ะฐัŽั‡ะธ ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ ั‚ะฐ ัั‚ะฐั‚ัƒั. +documentation.context.needs=ะฟะพั‚ั€ะตะฑัƒั” ะบะพะฝั‚ะตะบัั‚ัƒ +documentation.context.needs.description=ะŸั€ัะผั– ะทะฐะปะตะถะฝะพัั‚ั– ั€ะพะฑะพั‚ะธ ั‚ะฐ ั—ั…ะฝั– ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ/ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ. +documentation.context.jobs=ะบะพะฝั‚ะตะบัั‚ ั€ะพะฑะพั‚ะธ +documentation.context.jobs.description=ะŸะพะฒั‚ะพั€ะฝะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐะฝั– ั€ะพะฑะพั‡ั– ะทะฐะฒะดะฐะฝะฝั ั‚ะฐ ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ. +documentation.context.outputs=ะฒะธั…ะพะดะธ +documentation.context.outputs.description=ะ’ะธั…ั–ะดะฝั– ะทะฝะฐั‡ะตะฝะฝั, ั‰ะพ ะฒั–ะดะบั€ะธะฒะฐัŽั‚ัŒัั ั†ะธะผ ะบั€ะพะบะพะผ ะฐะฑะพ ะทะฐะฒะดะฐะฝะฝัะผ. +documentation.context.result=ั€ะตะทัƒะปัŒั‚ะฐั‚ +documentation.context.result.description=ะ ะตะทัƒะปัŒั‚ะฐั‚ ะทะฐะฒะดะฐะฝะฝั: ัƒัะฟั–ั…, ะฝะตะฒะดะฐั‡ะฐ, ัะบะฐัะพะฒะฐะฝะพ ะฐะฑะพ ะฟั€ะพะฟัƒั‰ะตะฝะพ. +documentation.context.outcome=ั€ะตะทัƒะปัŒั‚ะฐั‚ +documentation.context.outcome.description=ะ ะตะทัƒะปัŒั‚ะฐั‚ ะบั€ะพะบัƒ ะดะพ ะทะฐัั‚ะพััƒะฒะฐะฝะฝั ะฟั€ะพะดะพะฒะถะตะฝะฝั ัƒ ั€ะฐะทั– ะฟะพะผะธะปะบะธ. +documentation.context.conclusion=ะฒะธัะฝะพะฒะพะบ +documentation.context.conclusion.description=ะ ะตะทัƒะปัŒั‚ะฐั‚ ะบั€ะพะบัƒ ะฟั–ัะปั ะทะฐัั‚ะพััƒะฒะฐะฝะฝั ะฟั€ะพะดะพะฒะถะตะฝะฝั ัƒ ั€ะฐะทั– ะฟะพะผะธะปะบะธ. +error.report.action=ะŸะพะฒั–ะดะพะผะธั‚ะธ ะฟั€ะพ ะฒะธะฝัั‚ะพะบ +error.report.description=ะพะฟะธั +error.report.steps=ะšั€ะพะบะธ ะดะปั ะฒั–ะดั‚ะฒะพั€ะตะฝะฝั +error.report.sample=ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฝะฐะดะฐะนั‚ะต ะทั€ะฐะทะพะบ ะบะพะดัƒ, ัะบั‰ะพ ั†ะต ะผะพะถะปะธะฒะพ +error.report.message=ะฟะพะฒั–ะดะพะผะปะตะฝะฝั +error.report.runtime=ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ั‡ะฐั ะฒะธะบะพะฝะฐะฝะฝั +error.report.pluginVersion=ะ’ะตั€ัั–ั ะฟะปะฐะณั–ะฝะฐ: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=Stacktrace +completion.shell.bash=ะพะฑะพะปะพะฝะบะฐ Bash. ะ’ะธะบะพั€ะธัั‚ะพะฒัƒั” bash ะดะปั ะฑั–ะณัƒะฝั–ะฒ Linux ั– macOS, ะฐ ั‚ะฐะบะพะถ Git ะดะปั Windows bash ะดะปั ะฑั–ะณัƒะฝั–ะฒ Windows. +completion.shell.sh=ะ ะตะทะตั€ะฒะฝะฐ ะพะฑะพะปะพะฝะบะฐ POSIX. +completion.shell.pwsh=ะฏะดั€ะพ PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=ะšะพะผะฐะฝะดะฝะธะน ั€ัะดะพะบ Windows. +completion.shell.python=ะ’ะธะบะพะฝัƒะฒะฐั‡ ะบะพะผะฐะฝะด Python. +completion.runner.name=ะ†ะผ''ั ะฑั–ะณัƒะฝะฐ, ัะบะธะน ะฒะธะบะพะฝัƒั” ะทะฐะฒะดะฐะฝะฝั. +completion.runner.os=ะžะฟะตั€ะฐั†ั–ะนะฝะฐ ัะธัั‚ะตะผะฐ ะฑั–ะณัƒะฝะฐ, ั‰ะพ ะฒะธะบะพะฝัƒั” ะทะฐะฒะดะฐะฝะฝั. ะœะพะถะปะธะฒั– ะทะฝะฐั‡ะตะฝะฝั: Linux, Windows ะฐะฑะพ macOS. +completion.runner.arch=ะั€ั…ั–ั‚ะตะบั‚ัƒั€ะฐ ะฑั–ะณัƒะฝะฐ, ั‰ะพ ะฒะธะบะพะฝัƒั” ะทะฐะฒะดะฐะฝะฝั. ะœะพะถะปะธะฒั– ะทะฝะฐั‡ะตะฝะฝั: X86, X64, ARM ะฐะฑะพ ARM64. +completion.runner.temp=ะจะปัั… ะดะพ ั‚ะธะผั‡ะฐัะพะฒะพะณะพ ะบะฐั‚ะฐะปะพะณัƒ ะฝะฐ ะฑั–ะณัƒะฝั–. ะฆะตะน ะบะฐั‚ะฐะปะพะณ ัะฟะพั€ะพะถะฝัั”ั‚ัŒัั ะฝะฐ ะฟะพั‡ะฐั‚ะบัƒ ั‚ะฐ ะฒ ะบั–ะฝั†ั– ะบะพะถะฝะพะณะพ ะทะฐะฒะดะฐะฝะฝั. ะ—ะฐัƒะฒะฐะถั‚ะต, ั‰ะพ ั„ะฐะนะปะธ ะฝะต ะฑัƒะดะต ะฒะธะดะฐะปะตะฝะพ, ัะบั‰ะพ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะฑั–ะณัƒะฝะฐ ะฝะต ะผะฐั” ะดะพะทะฒะพะปัƒ ะฝะฐ ั—ั… ะฒะธะดะฐะปะตะฝะฝั. +completion.runner.toolCache=ะจะปัั… ะดะพ ะบะฐั‚ะฐะปะพะณัƒ, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ ะฟะพะฟะตั€ะตะดะฝัŒะพ ะฒัั‚ะฐะฝะพะฒะปะตะฝั– ั–ะฝัั‚ั€ัƒะผะตะฝั‚ะธ ะดะปั ะทะฐะฟัƒัะบัƒ, ั€ะพะทะผั–ั‰ะตะฝะธั… ะฝะฐ GitHub. +completion.runner.debug=ะฆะต ะฒัั‚ะฐะฝะพะฒะปัŽั”ั‚ัŒัั, ะปะธัˆะต ัะบั‰ะพ ะฒะฒั–ะผะบะฝะตะฝะพ ะถัƒั€ะฝะฐะป ะฝะฐะปะฐะณะพะดะถะตะฝะฝั, ั– ะทะฐะฒะถะดะธ ะผะฐั” ะทะฝะฐั‡ะตะฝะฝั 1. +completion.runner.environment=ะกะตั€ะตะดะพะฒะธั‰ะต ะฑั–ะณัƒะฝะฐ, ัะบะธะน ะฒะธะบะพะฝัƒั” ะทะฐะฒะดะฐะฝะฝั. ะœะพะถะปะธะฒั– ะทะฝะฐั‡ะตะฝะฝั: github-hosted ะฐะฑะพ self-hosted. +completion.job.status=ะŸะพั‚ะพั‡ะฝะธะน ัั‚ะฐั‚ัƒั ั€ะพะฑะพั‚ะธ. +completion.job.checkRunId=ะ†ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะฟะตั€ะตะฒั–ั€ะบะธ ะฟะพั‚ะพั‡ะฝะพะณะพ ะทะฐะฒะดะฐะฝะฝั. +completion.job.container=ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะบะพะฝั‚ะตะนะฝะตั€ ะทะฐะฒะดะฐะฝะฝั. +completion.job.services=ะกะตั€ะฒั–ัะฝั– ะบะพะฝั‚ะตะนะฝะตั€ะธ, ัั‚ะฒะพั€ะตะฝั– ะดะปั ั€ะพะฑะพั‚ะธ. +completion.job.workflowRef=ะŸะพะฒะฝะต ะฟะพัะธะปะฐะฝะฝั ั„ะฐะนะปัƒ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ, ัะบะธะน ะฒะธะทะฝะฐั‡ะฐั” ะฟะพั‚ะพั‡ะฝะต ะทะฐะฒะดะฐะฝะฝั. +completion.job.workflowSha=ะšะพะผะผั–ั‚ SHA ั„ะฐะนะปัƒ ั€ะพะฑะพั‡ะพะณะพ ั†ะธะบะปัƒ, ัะบะธะน ะฒะธะทะฝะฐั‡ะฐั” ะฟะพั‚ะพั‡ะฝะต ะทะฐะฒะดะฐะฝะฝั. +completion.job.workflowRepository=ะ’ะปะฐัะฝะธะบ/ั€ะตะฟะพะทะธั‚ะพั€ั–ะน ัั…ะพะฒะธั‰ะฐ, ั‰ะพ ะผั–ัั‚ะธั‚ัŒ ั„ะฐะนะป ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ, ัะบะธะน ะฒะธะทะฝะฐั‡ะฐั” ะฟะพั‚ะพั‡ะฝะต ะทะฐะฒะดะฐะฝะฝั. +completion.job.workflowFilePath=ะจะปัั… ะดะพ ั„ะฐะนะปัƒ ั€ะพะฑะพั‡ะพะณะพ ั†ะธะบะปัƒ ะฒั–ะดะฝะพัะฝะพ ะบะพั€ะตะฝั ัั…ะพะฒะธั‰ะฐ. +completion.job.containerField=ะŸะพะปะต ะบะพะฝั‚ะตะนะฝะตั€ะฐ ะทะฐะฒะดะฐะฝะฝั +completion.job.service=ะกะปัƒะถะฑะฐ ะฟั€ะฐั†ะตะฒะปะฐัˆั‚ัƒะฒะฐะฝะฝั +completion.job.serviceField=ะŸะพะปะต ัะปัƒะถะฑะธ ั€ะพะฑะพั‚ะธ +completion.job.mappedServicePort=ะ’ั–ะดะพะฑั€ะฐะถะตะฝะธะน ะฟะพั€ั‚ ัะปัƒะถะฑะธ +completion.strategy.failFast=ะงะธ ะฒัั– ะฝะตะทะฐะฒะตั€ัˆะตะฝั– ะทะฐะฒะดะฐะฝะฝั ัะบะฐัะพะฒัƒัŽั‚ัŒัั, ัะบั‰ะพ ะฑัƒะดัŒ-ัะบะต ะผะฐั‚ั€ะธั‡ะฝะต ะทะฐะฒะดะฐะฝะฝั ะฝะต ะฒะดะฐั”ั‚ัŒัั. +completion.strategy.jobIndex=ะ†ะฝะดะตะบั ะฒั–ะด ะฝัƒะปั ะฟะพั‚ะพั‡ะฝะพั— ั€ะพะฑะพั‚ะธ ะฒ ะผะฐั‚ั€ะธั†ั–. +completion.strategy.jobTotal=ะ—ะฐะณะฐะปัŒะฝะฐ ะบั–ะปัŒะบั–ัั‚ัŒ ั€ะพะฑะพั‡ะธั… ะผั–ัั†ัŒ ัƒ ะผะฐั‚ั€ะธั†ั–. +completion.strategy.maxParallel=ะœะฐะบัะธะผะฐะปัŒะฝะฐ ะบั–ะปัŒะบั–ัั‚ัŒ ะทะฐะฒะดะฐะฝัŒ ะผะฐั‚ั€ะธั†ั–, ัะบั– ะผะพะถัƒั‚ัŒ ะฒะธะบะพะฝัƒะฒะฐั‚ะธัั ะพะดะฝะพั‡ะฐัะฝะพ. +completion.context.inputs=ะ’ั…ั–ะดะฝั– ะดะฐะฝั– ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ, ะฝะฐะฟั€ะธะบะปะฐะด workflow_dispatch ะฐะฑะพ workflow_call. +completion.context.secrets=ะกะตะบั€ะตั‚ะธ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ. +completion.context.job=ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะฟะพั‚ะพั‡ะฝัƒ ั€ะพะฑะพั‚ัƒ. +completion.context.jobs=ะ ะพะฑะพั‡ั– ะฟั€ะพั†ะตัะธ. +completion.context.matrix=ะ’ะปะฐัั‚ะธะฒะพัั‚ั– ะผะฐั‚ั€ะธั†ั– ะดะปั ะฟะพั‚ะพั‡ะฝะพะณะพ ะผะฐั‚ั€ะธั‡ะฝะพะณะพ ะทะฐะฒะดะฐะฝะฝั. +completion.context.strategy=ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ัั‚ั€ะฐั‚ะตะณั–ัŽ ะฒะธะบะพะฝะฐะฝะฝั ะผะฐั‚ั€ะธั†ั– ะดะปั ะฟะพั‚ะพั‡ะฝะพั— ั€ะพะฑะพั‚ะธ. +completion.context.steps=ะšั€ะพะบะธ ะท ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ะพะผ ัƒ ะฟะพั‚ะพั‡ะฝั–ะน ั€ะพะฑะพั‚ั–. +completion.context.env=ะ—ะผั–ะฝะฝั– ัะตั€ะตะดะพะฒะธั‰ะฐ ะท ะทะฐะฒะดะฐะฝัŒ ั– ะบั€ะพะบั–ะฒ. +completion.context.vars=ะกะฟะตั†ั–ะฐะปัŒะฝั– ะทะผั–ะฝะฝั– ะบะพะฝั„ั–ะณัƒั€ะฐั†ั–ั— ะท ะพั€ะณะฐะฝั–ะทะฐั†ั–ะน, ัั…ะพะฒะธั‰ ั– ะพะฑะปะฐัั‚ะตะน ัะตั€ะตะดะพะฒะธั‰ะฐ. +completion.context.needs=ะ—ะฐะฒะดะฐะฝะฝั, ัะบั– ะฟะพั‚ั€ั–ะฑะฝะพ ะทะฐะฒะตั€ัˆะธั‚ะธ, ะฟะตั€ัˆ ะฝั–ะถ ั†ะต ะทะฐะฒะดะฐะฝะฝั ะผะพะถะฝะฐ ะฑัƒะดะต ะทะฐะฟัƒัั‚ะธั‚ะธ, ะฐ ั‚ะฐะบะพะถ ั—ั…ะฝั– ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ ั‚ะฐ ั€ะตะทัƒะปัŒั‚ะฐั‚ะธ. +completion.context.github=ะ—ะฐะฟัƒัะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ ั‚ะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะฟะพะดั–ั— ะท ะบะพะฝั‚ะตะบัั‚ัƒ GitHub. +completion.context.gitea=Gitea-ััƒะผั–ัะฝะธะน ะฟัะตะฒะดะพะฝั–ะผ ะดะปั ะบะพะฝั‚ะตะบัั‚ัƒ ะดั–ะน GitHub. +completion.context.runner=ะ†ะฝั„ะพั€ะผะฐั†ั–ั ะฟั€ะพ ะฑั–ะณัƒะฝะฐ, ัะบะธะน ะฒะธะบะพะฝัƒั” ะฟะพั‚ะพั‡ะฝะต ะทะฐะฒะดะฐะฝะฝั. +completion.secret.githubToken=ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ัั‚ะฒะพั€ะตะฝะธะน ะผะฐั€ะบะตั€ ะดะปั ะบะพะถะฝะพะณะพ ะทะฐะฟัƒัะบัƒ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ. +completion.remote.repository=ะ’ั–ะดะดะฐะปะตะฝะธะน ั€ะตะฟะพะทะธั‚ะพั€ั–ะน +completion.uses.local.workflow=ะ›ะพะบะฐะปัŒะฝะธะน ะฑะฐะณะฐั‚ะพั€ะฐะทะพะฒะธะน ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั +completion.uses.local.action=ะœั–ัั†ะตะฒะฐ ะดั–ั +completion.uses.ref.known=ะ’ั–ะดะพะผะธะน ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั +completion.uses.ref.remote=ะ”ะพะฒั–ะดะบะฐ ะฟั€ะพ ะฒั–ะดะดะฐะปะตะฝะธะน ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั +completion.uses.remote.known=ะ’ั–ะดะพะผะฐ ะฒั–ะดะดะฐะปะตะฝะฐ ะดั–ั ะฐะฑะพ ะฑะฐะณะฐั‚ะพั€ะฐะทะพะฒะธะน ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั +completion.workflow.syntax=GitHub ะกะธะฝั‚ะฐะบัะธั ั€ะพะฑะพั‡ะพะณะพ ั†ะธะบะปัƒ ะดั–ะน +completion.workflow.top.name=ะ’ั–ะดะพะฑั€ะฐะถัƒะฒะฐะฝะฐ ะฝะฐะทะฒะฐ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +completion.workflow.top.run-name=ะ”ะธะฝะฐะผั–ั‡ะฝะฐ ะฝะฐะทะฒะฐ ะทะฐะฟัƒัะบัƒ +completion.workflow.top.on=ะŸะพะดั–ั—, ัะบั– ะฟะพั‡ะธะฝะฐัŽั‚ัŒ ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั +completion.workflow.top.permissions=ะ”ะพะทะฒะพะปะธ GITHUB_TOKEN ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ +completion.workflow.top.env=ะ—ะผั–ะฝะฝั– ัะตั€ะตะดะพะฒะธั‰ะฐ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +completion.workflow.top.defaults=ะŸะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฒะดะฐะฝัŒ ั– ะบั€ะพะบั–ะฒ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ +completion.workflow.top.concurrency=ะ“ั€ัƒะฟะฐ ะฟะฐั€ะฐะปะตะปั–ะทะผัƒ ั‚ะฐ ัะบะฐััƒะฒะฐะฝะฝั +completion.workflow.top.jobs=ะ—ะฐะฒะดะฐะฝะฝั, ั‰ะพ ะฑั–ะถะฐั‚ัŒ ัƒ ั†ัŒะพะผัƒ workflow +completion.workflow.event.branch_protection_rule=ะ—ะผั–ะฝะตะฝะพ ะทะฐั…ะธัั‚ ะณั–ะปะพะบ +completion.workflow.event.check_run=ะžะดะธะฝะพั‡ะฝะธะน ะฟั€ะพะณั–ะฝ ะฟะตั€ะตะฒั–ั€ะบะธ ะทะผั–ะฝะตะฝะพ +completion.workflow.event.check_suite=ะงะตะบ ะปัŽะบั ะทะผั–ะฝะตะฝะธะน +completion.workflow.event.create=ะกั‚ะฒะพั€ะตะฝะพ ะณั–ะปะบัƒ ะฐะฑะพ ั‚ะตะณ +completion.workflow.event.delete=ะ“ั–ะปะบะฐ ะฐะฑะพ ั‚ะตะณ ะฒะธะดะฐะปะตะฝะพ +completion.workflow.event.deployment=ะ ะพะทะณะพั€ั‚ะฐะฝะฝั ัั‚ะฒะพั€ะตะฝะพ +completion.workflow.event.deployment_status=ะกั‚ะฐั‚ัƒั ั€ะพะทะณะพั€ั‚ะฐะฝะฝั ะทะผั–ะฝะตะฝะพ +completion.workflow.event.discussion=ะžะฑะณะพะฒะพั€ะตะฝะฝั ะทะผั–ะฝะตะฝะพ +completion.workflow.event.discussion_comment=ะšะพะผะตะฝั‚ะฐั€ ะพะฑะณะพะฒะพั€ะตะฝะฝั ะทะผั–ะฝะตะฝะพ +completion.workflow.event.fork=ะ ะพะทะณะฐะปัƒะถะตะฝะธะน ั€ะตะฟะพะทะธั‚ะพั€ั–ะน +completion.workflow.event.gollum=ะ’ั–ะบั–-ัั‚ะพั€ั–ะฝะบะฐ ะทะผั–ะฝะตะฝะฐ +completion.workflow.event.image_version=ะ—ะผั–ะฝะตะฝะพ ะฒะตั€ัั–ัŽ ะทะพะฑั€ะฐะถะตะฝะฝั ะฟะฐะบะตั‚ะฐ +completion.workflow.event.issue_comment=ะŸั€ะพะฑะปะตะผัƒ ะฐะฑะพ ะบะพะผะตะฝั‚ะฐั€ PR ะทะผั–ะฝะตะฝะพ +completion.workflow.event.issues=ะ’ะธะฟัƒัะบ ะทะผั–ะฝะตะฝะพ +completion.workflow.event.label=ะ•ั‚ะธะบะตั‚ะบะฐ ะทะผั–ะฝะตะฝะฐ +completion.workflow.event.merge_group=ะะฐะดั–ัะปะฐะฝะพ ะทะฐะฟะธั‚ ะฝะฐ ะฟะตั€ะตะฒั–ั€ะบัƒ ั‡ะตั€ะณะธ ะทะปะธั‚ั‚ั +completion.workflow.event.milestone=ะ’ั–ั…ะฐ ะทะผั–ะฝะตะฝะฐ +completion.workflow.event.page_build=ะกั‚ะฒะพั€ะตะฝะฝั ัั‚ะพั€ั–ะฝะพะบ ะทะฐะฟัƒั‰ะตะฝะพ +completion.workflow.event.project=ะ—ะผั–ะฝะตะฝะพ ะบะปะฐัะธั‡ะฝะธะน ะฟั€ะพะตะบั‚ +completion.workflow.event.project_card=ะšะฐั€ั‚ะบะฐ ะบะปะฐัะธั‡ะฝะพะณะพ ะฟั€ะพะตะบั‚ัƒ ะทะผั–ะฝะตะฝะฐ +completion.workflow.event.project_column=ะกั‚ะพะฒะฟะตั†ัŒ ะบะปะฐัะธั‡ะฝะพะณะพ ะฟั€ะพะตะบั‚ัƒ ะทะผั–ะฝะตะฝะพ +completion.workflow.event.public=ะ ะตะฟะพะทะธั‚ะพั€ั–ะน ัั‚ะฐะฒ ะฟัƒะฑะปั–ั‡ะฝะธะผ +completion.workflow.event.pull_request=ะ—ะฐะฟะธั‚ ะฝะฐ ะพั‚ั€ะธะผะฐะฝะฝั ะทะผั–ะฝะตะฝะพ +completion.workflow.event.pull_request_review=ะžะณะปัะด PR ะทะผั–ะฝะตะฝะพ +completion.workflow.event.pull_request_review_comment=ะšะพะผะตะฝั‚ะฐั€ ะดะพ ะพะณะปัะดัƒ PR ะทะผั–ะฝะตะฝะพ +completion.workflow.event.pull_request_target=ะฆั–ะปัŒะพะฒะธะน ะบะพะฝั‚ะตะบัั‚ PR. ะ“ะพัั‚ั€ั– ะฝะพะถั–. +completion.workflow.event.push=ะ—ะฐะบั€ั–ะฟะปะตะฝะฝั ะฐะฑะพ ั‚ะตะณ ะฝะฐะดั–ัะปะฐะฝะพ +completion.workflow.event.registry_package=ะŸะฐะบะตั‚ ะพะฟัƒะฑะปั–ะบะพะฒะฐะฝะพ ะฐะฑะพ ะพะฝะพะฒะปะตะฝะพ +completion.workflow.event.release=ะ ะตะปั–ะท ะทะผั–ะฝะตะฝะพ +completion.workflow.event.repository_dispatch=ะšะพั€ะธัั‚ัƒะฒะฐั†ัŒะบะฐ ะฟะพะดั–ั API +completion.workflow.event.schedule=ะšั€ะพะฝ ะณะฐะปะพั‡ะบะฐ. ะ—ะฐะฒะพะดะฝะธะน ะผะตั…ะฐะฝั–ะทะผ. +completion.workflow.event.status=ะกั‚ะฐั‚ัƒั ะบะพะผั–ั‚ัƒ ะทะผั–ะฝะตะฝะพ +completion.workflow.event.watch=ะกั…ะพะฒะธั‰ะต ะฟะพะทะฝะฐั‡ะตะฝะพ ะทั–ั€ะพั‡ะบะพัŽ +completion.workflow.event.workflow_call=ะ‘ะฐะณะฐั‚ะพั€ะฐะทะพะฒะธะน ะฒะธะบะปะธะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +completion.workflow.event.workflow_dispatch=ะšะฝะพะฟะบะฐ ั€ัƒั‡ะฝะพะณะพ ะทะฐะฟัƒัะบัƒ +completion.workflow.event.workflow_run=ะ—ะฐะฟัƒัะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ ะทะผั–ะฝะตะฝะพ +completion.workflow.eventFilter.types=ะžะฑะผะตะถะตะฝะฝั ะฒะธะดั–ะฒ ะดั–ัะปัŒะฝะพัั‚ั– +completion.workflow.eventFilter.branches=ะขั–ะปัŒะบะธ ั†ั– ะณั–ะปะบะธ +completion.workflow.eventFilter.branches-ignore=ะŸั€ะพะฟัƒัั‚ั–ั‚ัŒ ั†ั– ะณั–ะปะบะธ +completion.workflow.eventFilter.tags=ะขั–ะปัŒะบะธ ั†ั– ั‚ะตะณะธ +completion.workflow.eventFilter.tags-ignore=ะŸั€ะพะฟัƒัั‚ั–ั‚ัŒ ั†ั– ั‚ะตะณะธ +completion.workflow.eventFilter.paths=ะขั–ะปัŒะบะธ ั†ั– ัˆะปัั…ะธ +completion.workflow.eventFilter.paths-ignore=ะŸั€ะพะฟัƒัั‚ั–ั‚ัŒ ั†ั– ัˆะปัั…ะธ +completion.workflow.eventFilter.workflows=ะกะปั–ะดะบัƒะนั‚ะต ะทะฐ ั–ะผะตะฝะฐะผะธ ั€ะพะฑะพั‡ะธั… ะฟั€ะพั†ะตัั–ะฒ +completion.workflow.eventFilter.cron=ะ ะพะทะบะปะฐะด Cron. ะšั€ะธั…ั–ั‚ะฝะธะน ะณะพะดะธะฝะฝะธะบะพะฒะธะน ะผะตั…ะฐะฝั–ะทะผ. +completion.workflow.permission.actions=ะ—ะฐะฟัƒัะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ ั‚ะฐ ะฐั€ั‚ะตั„ะฐะบั‚ะธ ะดั–ะน +completion.workflow.permission.artifact-metadata=ะ—ะฐะฟะธัะธ ะผะตั‚ะฐะดะฐะฝะธั… ะฐั€ั‚ะตั„ะฐะบั‚ั–ะฒ +completion.workflow.permission.attestations=ะั‚ะตัั‚ะฐั†ั–ั— ะฐั€ั‚ะตั„ะฐะบั‚ั–ะฒ +completion.workflow.permission.checks=ะŸะตั€ะตะฒั–ั€ั‚ะต ะฟั€ะพะณะพะฝะธ ั‚ะฐ ะบะพะผะฟะปะตะบั‚ะธ +completion.workflow.permission.code-quality=ะ—ะฒั–ั‚ะธ ะฟั€ะพ ัะบั–ัั‚ัŒ ะบะพะดัƒ +completion.workflow.permission.contents=ะ’ะผั–ัั‚ ัั…ะพะฒะธั‰ะฐ +completion.workflow.permission.deployments=ะ ะพะทะณะพั€ั‚ะฐะฝะฝั +completion.workflow.permission.discussions=ะดะธัะบัƒัั–ั— +completion.workflow.permission.id-token=ะขะพะบะตะฝะธ OpenID Connect +completion.workflow.permission.issues=ะŸะธั‚ะฐะฝะฝั +completion.workflow.permission.models=ะœะพะดะตะปั– GitHub +completion.workflow.permission.packages=ะŸะฐะบัƒะฝะบะธ GitHub +completion.workflow.permission.pages=ะกั‚ะพั€ั–ะฝะบะธ GitHub +completion.workflow.permission.pull-requests=ะ—ะฐะฟะธั‚ะธ ะฝะฐ ะฒะธั‚ัะณัƒะฒะฐะฝะฝั +completion.workflow.permission.security-events=ะกะบะฐะฝัƒะฒะฐะฝะฝั ะบะพะดัƒ ั‚ะฐ ะฟะพะดั–ั— ะฑะตะทะฟะตะบะธ +completion.workflow.permission.statuses=ะกั‚ะฐั‚ัƒัะธ ั„ั–ะบัะฐั†ั–ั— +completion.workflow.permission.vulnerability-alerts=ะกะฟะพะฒั–ั‰ะตะฝะฝั Dependabot +completion.workflow.permission.value.read=ะ”ะพัั‚ัƒะฟ ะดะปั ั‡ะธั‚ะฐะฝะฝั +completion.workflow.permission.value.write=ะ”ะพัั‚ัƒะฟ ะดะปั ะทะฐะฟะธััƒ, ั‡ะธั‚ะฐะฝะฝั ะฒะบะปัŽั‡ะตะฝะพ +completion.workflow.permission.value.none=ะะตะผะฐั” ะดะพัั‚ัƒะฟัƒ +completion.workflow.permission.shorthand.read-all=ะฃัั– ะดะพะทะฒะพะปะธ ะฟั€ะพั‡ะธั‚ะฐะฝะพ. ะ’ะตะปะธะบะฐ ะบะพะฒะดั€ะฐ. +completion.workflow.permission.shorthand.write-all=ะ’ัั– ะดะพะทะฒะพะปะธ ะฟะธัˆัƒั‚ัŒ. ะ’ะตะปะธะบะธะน ะผะพะปะพั‚ะพะบ. +completion.workflow.permission.shorthand.empty=ะ’ะธะผะบะฝัƒั‚ะธ ะดะพะทะฒะพะปะธ ะผะฐั€ะบะตั€ั–ะฒ +completion.workflow.job.name=ะ’ั–ะดะพะฑั€ะฐะถัƒะฒะฐะฝะฐ ะฝะฐะทะฒะฐ ั€ะพะฑะพั‚ะธ +completion.workflow.job.permissions=ะ”ะพะทะฒะพะปะธ ะผะฐั€ะบะตั€ะฐ ะทะฐะฒะดะฐะฝะฝั +completion.workflow.job.needs=ะ’ะฐะบะฐะฝัั–ั—, ัะบะธั… ะฟะพั‚ั€ั–ะฑะฝะพ ั‡ะตะบะฐั‚ะธ +completion.workflow.job.if=ะกั‚ะฐะฝ ั€ะพะฑะพั‚ะธ +completion.workflow.job.runs-on=ะœั–ั‚ะบะฐ ะฐะฑะพ ะณั€ัƒะฟะฐ ะฑั–ะณัƒะฝั–ะฒ +completion.workflow.job.snapshot=ะ—ะฝั–ะผะพะบ ะฑั–ะณัƒะฝะฐ +completion.workflow.job.environment=ะกะตั€ะตะดะพะฒะธั‰ะต ั€ะพะทะณะพั€ั‚ะฐะฝะฝั +completion.workflow.job.concurrency=ะ‘ะปะพะบัƒะฒะฐะฝะฝั ะฟะฐั€ะฐะปะตะปัŒะฝะพัั‚ั– ะทะฐะฒะดะฐะฝัŒ +completion.workflow.job.outputs=ะ’ะธั…ะพะดะธ, ัะบั– ะผะพะถัƒั‚ัŒ ั‡ะธั‚ะฐั‚ะธ ั–ะฝัˆั– ะทะฐะฒะดะฐะฝะฝั +completion.workflow.job.env=ะ—ะผั–ะฝะฝั– ั€ะพะฑะพั‡ะพะณะพ ัะตั€ะตะดะพะฒะธั‰ะฐ +completion.workflow.job.defaults=ะŸะฐั€ะฐะผะตั‚ั€ะธ ะทะฐะฒะดะฐะฝะฝั ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ +completion.workflow.job.steps=ะกะฟะธัะพะบ ะบั€ะพะบั–ะฒ. ะคะฐะบั‚ะธั‡ะฝะฐ ั€ะพะฑะพั‚ะฐ. +completion.workflow.job.timeout-minutes=ะขะฐะนะผ-ะฐัƒั‚ ะทะฐะฒะดะฐะฝะฝั ะฒ ั…ะฒะธะปะธะฝะฐั… +completion.workflow.job.strategy=ะœะฐั‚ั€ะธั†ั ั– ัั‚ั€ะฐั‚ะตะณั–ั ะฟะปะฐะฝัƒะฒะฐะฝะฝั +completion.workflow.job.continue-on-error=ะะตั…ะฐะน ั†ั ั€ะพะฑะพั‚ะฐ ะผ''ัะบะพ ะฟั€ะพะฒะฐะปะธั‚ัŒัั +completion.workflow.job.container=ะšะพะฝั‚ะตะนะฝะตั€ ะดะปั ั†ั–ั”ั— ั€ะพะฑะพั‚ะธ +completion.workflow.job.services=ะกะตั€ะฒั–ัะฝั– ะบะพะฝั‚ะตะนะฝะตั€ะธ ะท ะบะพะปััะบะพัŽ +completion.workflow.job.uses=ะ‘ะฐะณะฐั‚ะพั€ะฐะทะพะฒะธะน ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั ะดะปั ะฒะธะบะปะธะบัƒ +completion.workflow.job.with=ะ’ั…ั–ะดะฝั– ะดะฐะฝั– ะดะปั ะฒะธะบะปะธะบะฐะฝะพะณะพ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +completion.workflow.job.secrets=ะกะตะบั€ะตั‚ะธ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +completion.workflow.defaultsRun.shell=ะกั‚ะฐะฝะดะฐั€ั‚ะฝะฐ ะพะฑะพะปะพะฝะบะฐ ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะบั€ะพะบั–ะฒ +completion.workflow.defaultsRun.working-directory=ะ ะพะฑะพั‡ะธะน ะบะฐั‚ะฐะปะพะณ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ +completion.workflow.concurrency.group=ะะฐะทะฒะฐ ะฑะปะพะบัƒะฒะฐะฝะฝั ะดะปั ะฒะธะบะพะฝะฐะฝะฝั ะฒ ั‡ะตั€ะทั– +completion.workflow.concurrency.cancel-in-progress=ะกะบะฐััƒะฒะฐั‚ะธ ัั‚ะฐั€ั– ะฒั–ะดะฟะพะฒั–ะดะฝั– ะฟั€ะพะณะพะฝะธ +completion.workflow.environment.name=ะะฐะทะฒะฐ ัะตั€ะตะดะพะฒะธั‰ะฐ +completion.workflow.environment.url=ะกะตั€ะตะดะพะฒะธั‰ะต URL +completion.workflow.strategy.matrix=ะœะฐั‚ั€ะธั‡ะฝั– ะพัั– ั‚ะฐ ะฒะฐั€ั–ะฐะฝั‚ะธ +completion.workflow.strategy.fail-fast=ะกะบะฐััƒะฒะฐั‚ะธ ะผะฐั‚ั€ะธั†ัŽ ะฑั€ะฐั‚ั–ะฒ ั– ัะตัั‚ะตั€ ัƒ ั€ะฐะทั– ะฝะตะฒะดะฐั‡ั– +completion.workflow.strategy.max-parallel=ะšะฐะฟะฐ ะฟะฐั€ะฐะปะตะปั–ะทะผัƒ ะผะฐั‚ั€ะธั†ั– +completion.workflow.matrix.include=ะ”ะพะดะฐะนั‚ะต ะบะพะผะฑั–ะฝะฐั†ั–ั— ะผะฐั‚ั€ะธั†ัŒ +completion.workflow.matrix.exclude=ะ’ะธะดะฐะปะธั‚ะธ ะบะพะผะฑั–ะฝะฐั†ั–ั— ะผะฐั‚ั€ะธั†ัŒ +completion.workflow.step.id=ะ†ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะบั€ะพะบัƒ ะดะปั ะฟะพัะธะปะฐะฝัŒ +completion.workflow.step.if=ะฃะผะพะฒะฐ ะบั€ะพะบัƒ +completion.workflow.step.name=ะ’ั–ะดะพะฑั€ะฐะถัƒะฒะฐะฝะฐ ะฝะฐะทะฒะฐ ะบั€ะพะบัƒ +completion.workflow.step.uses=ะ”ั–ั ะดะปั ะทะฐะฟัƒัะบัƒ +completion.workflow.step.run=ะกั†ะตะฝะฐั€ั–ะน ะพะฑะพะปะพะฝะบะธ ะดะปั ะทะฐะฟัƒัะบัƒ +completion.workflow.step.shell=ะžะฑะพะปะพะฝะบะฐ ะดะปั ั†ัŒะพะณะพ ะตั‚ะฐะฟัƒ ะทะฐะฟัƒัะบัƒ +completion.workflow.step.with=ะ’ะฒะตะดะตะฝะฝั ะดั–ะน +completion.workflow.step.env=ะ—ะผั–ะฝะฝั– ัะตั€ะตะดะพะฒะธั‰ะฐ Step +completion.workflow.step.continue-on-error=ะะตั…ะฐะน ั†ะตะน ะบั€ะพะบ ะผ''ัะบะพ ะฟั€ะพะฒะฐะปะธั‚ัŒัั +completion.workflow.step.timeout-minutes=ะงะฐั ะพั‡ั–ะบัƒะฒะฐะฝะฝั ะบั€ะพะบัƒ ะฒ ั…ะฒะธะปะธะฝะฐั… +completion.workflow.step.working-directory=ะ ะพะฑะพั‡ะธะน ะบะฐั‚ะฐะปะพะณ Step +completion.workflow.container.image=ะ—ะพะฑั€ะฐะถะตะฝะฝั ะบะพะฝั‚ะตะนะฝะตั€ะฐ +completion.workflow.container.credentials=ะžะฑะปั–ะบะพะฒั– ะดะฐะฝั– ั€ะตั”ัั‚ั€ัƒ +completion.workflow.container.env=ะ—ะผั–ะฝะฝั– ัะตั€ะตะดะพะฒะธั‰ะฐ ะบะพะฝั‚ะตะนะฝะตั€ะฐ +completion.workflow.container.ports=ะŸะพั€ั‚ะธ ะดะปั ะฒะธะบั€ะธั‚ั‚ั +completion.workflow.container.volumes=ะžะฑััะณะธ ะดะปั ะผะพะฝั‚ัƒะฒะฐะฝะฝั +completion.workflow.container.options=Docker ัั‚ะฒะพั€ะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ +completion.workflow.service.image=ะ—ะพะฑั€ะฐะถะตะฝะฝั ัะตั€ะฒั–ัะฝะพะณะพ ะบะพะฝั‚ะตะนะฝะตั€ะฐ +completion.workflow.service.credentials=ะžะฑะปั–ะบะพะฒั– ะดะฐะฝั– ั€ะตั”ัั‚ั€ัƒ +completion.workflow.service.env=ะ—ะผั–ะฝะฝั– ัะตั€ะตะดะพะฒะธั‰ะฐ ัะปัƒะถะฑะธ +completion.workflow.service.ports=ะกะตั€ะฒั–ัะฝั– ะฟะพั€ั‚ะธ +completion.workflow.service.volumes=ะžะฑััะณะธ ะฟะพัะปัƒะณ +completion.workflow.service.options=Docker ัั‚ะฒะพั€ะธั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ +completion.workflow.credentials.username=ะ†ะผ''ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ั€ะตั”ัั‚ั€ัƒ +completion.workflow.credentials.password=ะŸะฐั€ะพะปัŒ ะฐะฑะพ ะผะฐั€ะบะตั€ ั€ะตั”ัั‚ั€ะฐั†ั–ั— +completion.workflow.inputType.string=ะ’ะฒะตะดะตะฝะฝั ั‚ะตะบัั‚ัƒ +completion.workflow.inputType.boolean=ะ†ัั‚ะธะฝะฝะต ั‡ะธ ั…ะธะฑะฝะต ะฒะฒะตะดะตะฝะฝั +completion.workflow.inputType.choice=ะ’ะฒะตะดะตะฝะฝั ะฒะธะฑะพั€ัƒ ะฒ ัะฟะฐะดะฝะพะผัƒ ะผะตะฝัŽ +completion.workflow.inputType.number=ะ’ะฒะตะดะตะฝะฝั ั‡ะธัะปะฐ +completion.workflow.inputType.environment=ะ’ะฒะตะดะตะฝะฝั ะทะฐัะพะฑัƒ ะฒะธะฑะพั€ัƒ ัะตั€ะตะดะพะฒะธั‰ะฐ +completion.workflow.boolean.true=ั‚ะฐะบ ะŸะตั€ะตะฒะตั€ะฝั–ั‚ัŒ ะนะพะณะพ. +completion.workflow.boolean.false=ะั–. ะขั€ะธะผะฐะนั‚ะต ั‚ะตะผะฝะพ. +completion.workflow.runner.ubuntu-latest=ะžัั‚ะฐะฝะฝั–ะน ะฑั–ะณัƒะฝ Ubuntu +completion.workflow.runner.ubuntu-24.04=ะ‘ั–ะณัƒะฝ Ubuntu 24.04 +completion.workflow.runner.ubuntu-22.04=ะ‘ั–ะณัƒะฝ Ubuntu 22.04 +completion.workflow.runner.windows-latest=ะžัั‚ะฐะฝะฝั–ะน ะฑั–ะณัƒะฝ Windows +completion.workflow.runner.windows-2025=ะ—ะฐะฟัƒัะบ Windows Server 2025 +completion.workflow.runner.windows-2022=ะ—ะฐะฟัƒัะบ Windows Server 2022 +completion.workflow.runner.macos-latest=ะžัั‚ะฐะฝะฝั–ะน ะฑั–ะณัƒะฝ macOS +completion.workflow.runner.macos-15=macOS 15 ะฑั–ะณัƒะฝ +completion.workflow.runner.macos-14=macOS 14 ะฑั–ะณัƒะฝ +completion.workflow.runner.self-hosted=ะ’ะฐัˆ ะฒะปะฐัะฝะธะน ะฑั–ะณัƒะฝ. ะ’ะฐัˆ ั†ะธั€ะบ. +completion.steps.outputs=ะะฐะฑั–ั€ ะฒะธั…ั–ะดะฝะธั… ะดะฐะฝะธั…, ะฒะธะทะฝะฐั‡ะตะฝะธั… ะดะปั ะบั€ะพะบัƒ. +completion.steps.conclusion=ะ—ะฐัั‚ะพัะพะฒัƒั”ั‚ัŒัั ั€ะตะทัƒะปัŒั‚ะฐั‚ ะทะฐะฒะตั€ัˆะตะฝะพะณะพ ะบั€ะพะบัƒ ะฟั–ัะปั ะฟั€ะพะดะพะฒะถะตะฝะฝั ะฟั–ัะปั ะฟะพะผะธะปะบะธ. +completion.steps.outcome=ะ ะตะทัƒะปัŒั‚ะฐั‚ ะทะฐะฒะตั€ัˆะตะฝะพะณะพ ะบั€ะพะบัƒ ะดะพ ะทะฐัั‚ะพััƒะฒะฐะฝะฝั ะฟั€ะพะดะพะฒะถะตะฝะฝั ะฟั–ัะปั ะฟะพะผะธะปะบะธ. +completion.jobs.outputs=ะะฐะฑั–ั€ ั€ะตะทัƒะปัŒั‚ะฐั‚ั–ะฒ, ะฒะธะทะฝะฐั‡ะตะฝะธั… ะดะปั ะทะฐะฒะดะฐะฝะฝั. +completion.jobs.result=ะ ะตะทัƒะปัŒั‚ะฐั‚ ั€ะพะฑะพั‚ะธ. +settings.displayName=ะ ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั GitHub +settings.language.label=ะผะพะฒะฐ: +settings.language.system=IDE/ัะธัั‚ะตะผะฐ ะทะฐ ะทะฐะผะพะฒั‡ัƒะฒะฐะฝะฝัะผ +settings.cache.title=ะšะตัˆ ะดั–ะน +settings.cache.column.key=ะšะปัŽั‡ ะบะตัˆัƒ +settings.cache.column.name=ะ†ะผ''ั +settings.cache.column.kind=ะ”ะพะฑั€ะธะน +settings.cache.column.state=ะ”ะตั€ะถะฐะฒะฐ +settings.cache.column.expires=ะขะตั€ะผั–ะฝ ะดั–ั— ะทะฐะบั–ะฝั‡ัƒั”ั‚ัŒัั +settings.cache.kind.local=ะผั–ัั†ะตะฒะธะน +settings.cache.kind.remote=ะดะธัั‚ะฐะฝั†ั–ะนะฝะธะน +settings.cache.state.resolved=ะฒะธั€ั–ัˆะตะฝะพ +settings.cache.state.pending=ะฒ ะพั‡ั–ะบัƒะฒะฐะฝะฝั– +settings.cache.state.expired=ะฝะตัะฒั–ะถะธะน +settings.cache.state.suppressed=ะฟั€ะธะณะฝั–ั‡ะตะฝะธะน +settings.cache.refresh=ะขะฐะฑะปะธั†ั Refresh +settings.cache.deleteSelected=ะ’ะธะดะฐะปะธั‚ะธ ะฒะธะฑั€ะฐะฝะต +settings.cache.deleteAll=ะ’ะธะดะฐะปะธั‚ะธ ะฒัะต +settings.cache.export=ะ•ะบัะฟะพั€ั‚ +settings.cache.import=ะ†ะผะฟะพั€ั‚ +settings.cache.summary=ะšะตัˆ: ะทะฐะฟะธัะธ {0}, {1} ะฒะธะฟั€ะฐะฒะปะตะฝะพ, {2} ะฒั–ะดะดะฐะปะตะฝะพ, {3} ะทะฐัั‚ะฐั€ั–ะปะต, {4} ะฒะธะผะบะฝะตะฝะพ. ะšะตัˆ: {5} KB. +settings.cache.noneSelected=ะกะฟะพั‡ะฐั‚ะบัƒ ะฒะธะฑะตั€ั–ั‚ัŒ ั€ัะดะบะธ ะบะตัˆัƒ. ะœั–ั‚ะปะฐ ะฒั–ะดะผะพะฒะปัั”ั‚ัŒัั ะฒั–ะด ะทะดะพะณะฐะดั–ะฒ. +settings.cache.deleteSelected.done=ะ’ะธะดะฐะปะตะฝั– ะทะฐะฟะธัะธ ะบะตัˆัƒ {0}. ะšั€ะธั…ั–ั‚ะฝะฐ ั…ะผะฐั€ะฐ ะฟะธะปัƒ ะผั–ัั‚ะธั‚ัŒัั. +settings.cache.deleteAll.confirm=ะ’ะธะดะฐะปะธั‚ะธ ะฒัั– ะทะฐะฟะธัะธ ะบะตัˆัƒ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ GitHub? +settings.cache.deleteAll.done=ะ’ะธะดะฐะปะตะฝะพ ะฒัั– ะทะฐะฟะธัะธ ะบะตัˆัƒ. ะขะตะฟะตั€ ัƒ ะบะตัˆั– ะฟั–ะดะพะทั€ั–ะปะพ ั‚ะธั…ะพ. +settings.cache.export.done=ะ•ะบัะฟะพั€ั‚ะพะฒะฐะฝั– ะทะฐะฟะธัะธ ะบะตัˆัƒ {0}. ะœะฐะปะตะฝัŒะบะธะน ะฐั€ั…ั–ะฒะฝะธะน ะทะฒั–ั€ ัƒ ะบะปั–ั‚ั†ั–. +settings.cache.import.done=ะ†ะผะฟะพั€ั‚ะพะฒะฐะฝั– ะทะฐะฟะธัะธ ะบะตัˆัƒ. ะั€ั…ั–ะฒะฝะธะน ะทะฒั–ั€ ะฟะพะฒั–ะฒัั. +settings.cache.import.unsupported=ะะตะฟั–ะดั‚ั€ะธะผัƒะฒะฐะฝะธะน ั„ะฐะนะป ะบะตัˆัƒ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ GitHub. +settings.cache.import.brokenLine=ะŸะพั€ัƒัˆะตะฝะธะน ั€ัะดะพะบ ะบะตัˆัƒ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ GitHub. +settings.cache.import.brokenKey=ะ—ะปะฐะผะฐะฝะธะน ะบะปัŽั‡ ะบะตัˆัƒ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ GitHub. +settings.support.button=ะŸั–ะดั‚ั€ะธะผัƒะนั‚ะต ั†ะตะน ะฟะปะฐะณั–ะฝ +settings.support.tooltip=ะ’ั–ะดะบั€ะธะนั‚ะต ัั‚ะพั€ั–ะฝะบัƒ ะฟั–ะดั‚ั€ะธะผะบะธ +settings.support.line.0=ะ—ะฐะฟั€ะฐะฒั‚ะต ะฟั–ั‡ ะดะปั ะฑัƒะดั–ะฒะฝะธั†ั‚ะฒะฐ +settings.support.line.1=ะšัƒะฟะธั‚ะธ ะฟะฐั€ัะตั€ ะบะฐะฒะธ +settings.support.line.2=ะกะฟะพะฝัะพั€ัƒะนั‚ะต ะผะตะฝัˆะต ะฝะตะฟั€ะธั”ะผะฝะธั… ั€ะพะฑะพั‡ะธั… ะฟั€ะพั†ะตัั–ะฒ +workflow.run.jobs.title=ะ ะพะฑะพั‡ั– ะฟั€ะพั†ะตัะธ +workflow.run.jobs.root=ะ—ะฐะฟัƒัะบ ั€ะพะฑะพั‡ะพะณะพ ะฟั€ะพั†ะตััƒ +workflow.run.jobs.description=GitHub ะ”ะตั€ะตะฒะพ ะทะฐะฒะดะฐะฝัŒ ะดั–ะน ั– ะฒะธะฑั€ะฐะฝะธะน ะถัƒั€ะฝะฐะป ะทะฐะฒะดะฐะฝัŒ +workflow.run.tree.done=ะทั€ะพะฑะปะตะฝะพ +workflow.run.tree.failed=ะฝะต ะฒะดะฐะปะพัั +workflow.run.tree.skipped=ะฟั€ะพะฟัƒั‰ะตะฝะพ +workflow.run.tree.warn=ะฟะพะฟะตั€ะตะดะธั‚ะธ +workflow.run.tree.err=ะฟะพะผะธะปะบะฐ +workflow.run.delete.tooltip=ะ’ะธะดะฐะปะธั‚ะธ ะทะฐะฟัƒัะบ +workflow.run.delete.noRun=ะฉะต ะฝะตะผะฐั” ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบัƒ. +workflow.run.delete.requested=ะ’ะธะดะฐะปะตะฝะฝั ะทะฐะฟัƒัะบัƒ {0}. +workflow.run.delete.done=ะ—ะฐะฟัƒัะบ {0} ะฒะธะดะฐะปะตะฝะพ. +workflow.run.delete.http=ะ’ะธะดะฐะปะธั‚ะธ HTTP {0}. ะžะน. +workflow.run.delete.failed=ะ’ะธะดะฐะปะธั‚ะธ ะฝะตัะฟั€ะฐะฒะฝั–ัั‚ัŒ: {0} +workflow.run.rerun.all.tooltip=ะŸะตั€ะตะทะฐะฟัƒัั‚ะธั‚ะธ ั€ะพะฑะพั‡ะธะน ะฟั€ะพั†ะตั +workflow.run.rerun.failed.tooltip=ะŸะตั€ะตะทะฐะฟัƒัั‚ั–ั‚ัŒ ะฝะตะฒะดะฐะปั– ะทะฐะฒะดะฐะฝะฝั +workflow.run.rerun.noRun=ะฉะต ะฝะตะผะฐั” ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบัƒ. +workflow.run.rerun.all.requested=ะŸะพั‚ั€ั–ะฑะฝะธะน ะฟะพะฒั‚ะพั€ะฝะธะน ะทะฐะฟัƒัะบ: {0}. +workflow.run.rerun.failed.requested=ะ—ะฐะฟะธั‚ะฐะฝั– ะฝะตะฒะดะฐะปั– ะทะฐะฒะดะฐะฝะฝั: {0}. +workflow.run.rerun.all.done=ะŸะพะฒั‚ะพั€ะฝะธะน ะทะฐะฟัƒัะบ ัƒ ั‡ะตั€ะทั–: {0}. +workflow.run.rerun.failed.done=ะะตะฒะดะฐะปั– ะทะฐะฒะดะฐะฝะฝั ะฒ ั‡ะตั€ะทั–: {0}. +workflow.run.rerun.http=ะŸะพะฒั‚ะพั€ะฝะพ ะทะฐะฟัƒัั‚ั–ั‚ัŒ HTTP {0}. ะžะน. +workflow.run.rerun.failed=ะŸะตั€ะตะทะฐะฟัƒัะบ ะทั–ะฟัะพะฒะฐะฝะธะน: {0} +workflow.run.download.log.tooltip=ะ—ะฑะตั€ะตะณั‚ะธ ะถัƒั€ะฝะฐะป ั€ะพะฑะพั‚ะธ +workflow.run.download.artifacts.tooltip=ะ—ะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ะฐั€ั‚ะตั„ะฐะบั‚ะธ +workflow.run.download.noRun=ะฉะต ะฝะตะผะฐั” ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ะฐ ะทะฐะฟัƒัะบัƒ. +workflow.run.download.log.requested=ะžั‚ั€ะธะผะฐะฝะฝั ะถัƒั€ะฝะฐะปัƒ ะดะปั {0}. +workflow.run.download.log.done=ะ–ัƒั€ะฝะฐะป ะทะฑะตั€ะตะถะตะฝะพ: {0}. +workflow.run.download.artifacts.requested=ะžั‚ั€ะธะผะฐะฝะฝั ะฐั€ั‚ะตั„ะฐะบั‚ั–ะฒ. +workflow.run.download.artifacts.empty=ะ‘ะตะท ะฐั€ั‚ะตั„ะฐะบั‚ั–ะฒ. ะšั€ะธั…ั–ั‚ะฝะฐ ะฟะพั€ะพะถะฝะตั‡ะฐ. +workflow.run.download.artifact.expired=ะขะตั€ะผั–ะฝ ะดั–ั— ะฐั€ั‚ะตั„ะฐะบั‚ัƒ ะผะธะฝัƒะฒ: {0}. +workflow.run.download.artifact.done=ะั€ั‚ะตั„ะฐะบั‚ ะทะฑะตั€ะตะถะตะฝะพ: {0} -> {1}. +workflow.run.download.failed=ะ—ะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ะทะฐะบั–ะฝั‡ะธะปะพัั: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_vi.properties b/src/main/resources/messages/GitHubWorkflowBundle_vi.properties new file mode 100644 index 0000000..6a4382a --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_vi.properties @@ -0,0 +1,442 @@ +plugin.name=Quy trรฌnh lร m viแป‡c cแปงa GitHub +plugin.description=Hแป— trแปฃ cรกc tแป‡p quy trรฌnh lร m viแป‡c cแปงa Hร nh ฤ‘แป™ng GitHub +group.GitHubWorkflow.Tools.text=Quy trรฌnh lร m viแป‡c cแปงa GitHub +group.GitHubWorkflow.Tools.description=Cรดng cแปฅ bแป• trแปฃ quy trรฌnh lร m viแป‡c GitHub +action.GitHubWorkflow.RefreshActionCache.text=Bแป™ ฤ‘แป‡m hร nh ฤ‘แป™ng Refresh +action.GitHubWorkflow.RefreshActionCache.description=Refresh ฤ‘รฃ giแบฃi quyแบฟt cรกc Hร nh ฤ‘แป™ng GitHub tแปซ xa vร  siรชu dแปฏ liแป‡u quy trรฌnh lร m viแป‡c cรณ thแปƒ sแปญ dแปฅng lแบกi +action.GitHubWorkflow.RestoreActionWarnings.text=Khรดi phแปฅc cแบฃnh bรกo hร nh ฤ‘แป™ng +action.GitHubWorkflow.RestoreActionWarnings.description=Khรดi phแปฅc cแบฃnh bรกo xรกc thแปฑc hร nh ฤ‘แป™ng, ฤ‘แบงu vร o vร  ฤ‘แบงu ra bแป‹ chแบทn +action.GitHubWorkflow.ClearActionCache.text=Xรณa bแป™ nhแป› ฤ‘แป‡m hร nh ฤ‘แป™ng +action.GitHubWorkflow.ClearActionCache.description=Xรณa cรกc hร nh ฤ‘แป™ng GitHub ฤ‘ฦฐแปฃc lฦฐu trong bแป™ nhแป› cache vร  siรชu dแปฏ liแป‡u quy trรฌnh lร m viแป‡c cรณ thแปƒ sแปญ dแปฅng lแบกi +notification.cache.cleared=ฤรฃ xรณa cรกc mแปฅc nhแบญp Luแป“ng cรดng viแป‡c GitHub ฤ‘ฦฐแปฃc lฦฐu trong bแป™ nhแป› ฤ‘แป‡m {0}. +notification.cache.refresh.started=Refreshing {0} ฤ‘รฃ lฦฐu cรกc mแปฅc nhแบญp Quy trรฌnh lร m viแป‡c GitHub tแปซ xa vร o bแป™ nhแป› ฤ‘แป‡m. +notification.warnings.restored=ฤรฃ khรดi phแปฅc cแบฃnh bรกo cho cรกc mแปฅc nhแบญp Quy trรฌnh lร m viแป‡c {0} GitHub. +workflow.run.configuration.display=Quy trรฌnh lร m viแป‡c cแปงa GitHub +workflow.run.configuration.description=Gแปญi vร  theo dรตi cรกc lแบงn chแบกy quy trรฌnh lร m viแป‡c cแปงa Hร nh ฤ‘แป™ng GitHub +workflow.run.configuration.name=Quy trรฌnh lร m viแป‡c cแปงa GitHub: {0} +workflow.run.field.apiUrl=URL API +workflow.run.field.owner=chแปง sแปŸ hแปฏu +workflow.run.field.repo=Kho lฦฐu trแปฏ +workflow.run.field.workflow=Tแป‡p quy trรฌnh cรดng viแป‡c +workflow.run.field.ref=Tham chiแบฟu +workflow.run.field.tokenEnv=Dแปฑ phรฒng mรฃ thรดng bรกo env var +workflow.run.inputs.title=ฤแบงu vร o workflow_dispatch (khรณa=giรก trแป‹) +workflow.run.error.apiUrl=URL API GitHub lร  bแบฏt buแป™c. +workflow.run.error.repository=Cแบงn phแบฃi cรณ tรชn vร  chแปง sแปŸ hแปฏu kho lฦฐu trแปฏ GitHub. +workflow.run.error.workflow=Cแบงn cรณ tแป‡p quy trรฌnh cรดng viแป‡c. +workflow.run.error.ref=Chi nhรกnh hoแบทc thแบป ref lร  bแบฏt buแป™c. +workflow.run.error.inputs=GitHub workflow_dispatch hแป— trแปฃ tแป‘i ฤ‘a 25 ฤ‘แบงu vร o. +workflow.run.gutter.stop=Dแปซng chแบกy quy trรฌnh cรดng viแป‡c +workflow.run.gutter.stop.text=Dแปซng chแบกy quy trรฌnh cรดng viแป‡c +workflow.run.gutter.stop.description=Hแปงy lแบงn chแบกy nร y +workflow.run.auth.settings=Cร i ฤ‘แบทt> Kiแปƒm soรกt phiรชn bแบฃn> GitHub +workflow.log.command=chแบกy: +workflow.log.warning=cแบฃnh bรกo: +workflow.log.error=lแป—i: +workflow.run.cancel.requested=ฤรฃ yรชu cแบงu hแปงy: {0}. +workflow.run.stop.before.id=Dแปซng yรชu cแบงu. Chฦฐa cรณ id chแบกy. +workflow.run.cancel.http=Hแปงy HTTP {0}. ร”i. +workflow.run.cancel.failed=Hแปงy bแป xรฌ hฦกi: {0} +workflow.run.interrupted=Bแป‹ giรกn ฤ‘oแบกn. +workflow.run.link=Chแบกy: {0} +workflow.run.discovery=Chแบกy ฤ‘ฦฐแปฃc chแบฅp nhแบญn. Sฤƒn chแบกy id. +workflow.run.discovery.none=Chฦฐa cรณ id chแบกy. Tab hร nh ฤ‘แป™ng biแบฟt nhiแปu hฦกn. +workflow.run.status=Trแบกng thรกi: {0}{1} +workflow.run.job.main=Cรดng viแป‡c: {0} {1} [{2}{3}{4}] +workflow.run.job.status=Trแบกng thรกi: {0} {1}{2}{3} +workflow.run.logs.later=Nhแบญt kรฝ sแบฝ xuแบฅt hiแป‡n khi GitHub xuแบฅt bแบฃn chรบng. +workflow.run.job.logs.later=Cรดng viแป‡c {0}: {1} +workflow.run.log.failed=Tแบฃi xuแป‘ng nhแบญt kรฝ khรดng thร nh cรดng: {0} +workflow.run.log.failed.job=Tแบฃi xuแป‘ng nhแบญt kรฝ khรดng thร nh cรดng cho {0}: {1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=Cรดng viแป‡c: {0} +workflow.run.job.fallbackName=Viแป‡c lร m {0} +workflow.run.overview=ฤรฃ chแบกy xong quy trรฌnh lร m viแป‡c {0} {1}/{2}, {3} ฤ‘ang chแบกy +workflow.run.state.ok=[ฤฦฐแปฃc rแป“i] +workflow.run.state.fail=[THแบคT Bแบ I] +workflow.run.state.running=[CHแบ Y] +workflow.run.state.waiting=[CHแปœ] +workflow.run.dispatch.verbs=Chuแบฉn bแป‹|Xแบฟp hร ng|Triแป‡u hแป“i|Ra mแบฏt|KhแปŸi ฤ‘แป™ng +workflow.run.dispatch.objects=quy trรฌnh lร m viแป‡c|tแปฑ ฤ‘แป™ng hรณa|ฤ‘ฦฐแปng แป‘ng|chแบกy +workflow.run.dispatch={0} {1} {2}{3} dร nh cho {4} trรชn {5}. +workflow.run.notification.auth=Cรดng vฤƒn luแป“ng cรดng viแป‡c GitHub cแบงn cรณ tร i khoแบฃn GitHub ฤ‘ฦฐแปฃc xรกc thแปฑc. Thรชm hoแบทc lร m mแป›i tร i khoแบฃn trong {0}. +workflow.run.notification.openSettings=MแปŸ cร i ฤ‘แบทt GitHub +workflow.cache.progress.title=Giแบฃi quyแบฟt cรกc hร nh ฤ‘แป™ng GitHub +workflow.cache.progress.text=Giแบฃi quyแบฟt {0} {1} +workflow.cache.kind.action=hร nh ฤ‘แป™ng +workflow.cache.kind.workflow=quy trรฌnh lร m viแป‡c +inspection.parameter.input=ฤ‘แบงu vร o +inspection.parameter.secret=bรญ mแบญt +inspection.action.delete.invalid=Xรณa {0} khรดng hแปฃp lแป‡ [{1}] +inspection.action.update.major=Cแบญp nhแบญt hร nh ฤ‘แป™ng [{0}] thร nh [{1}] +inspection.warning.toggle=Chuyแปƒn ฤ‘แป•i cแบฃnh bรกo [{0}] cho [{1}] +inspection.warning.on=trรชn +inspection.warning.off=tแบฏt +inspection.statement.incomplete=Tuyรชn bแป‘ chฦฐa ฤ‘แบงy ฤ‘แปง [{0}] +inspection.invalid.suffix.remove=Xรณa hแบญu tแป‘ khรดng hแปฃp lแป‡ [{0}] +inspection.replace.with=Thay thแบฟ bแบฑng [{0}] +inspection.invalid.remove=Xรณa [{0}] khรดng hแปฃp lแป‡ +inspection.workflow.syntax.unknownTopLevelKey=Khรณa quy trรฌnh lร m viแป‡c khรดng xรกc ฤ‘แป‹nh [{0}] +inspection.workflow.syntax.unknownEventKey=Sแปฑ kiแป‡n quy trรฌnh lร m viแป‡c khรดng xรกc ฤ‘แป‹nh [{0}] +inspection.workflow.syntax.unknownTriggerKey=Khรณa kรญch hoแบกt khรดng xรกc ฤ‘แป‹nh [{0}] +inspection.workflow.syntax.unknownTriggerFilter=Bแป™ lแปc kรญch hoแบกt khรดng xรกc ฤ‘แป‹nh [{0}] +inspection.workflow.syntax.unknownTriggerValue=Giรก trแป‹ kรญch hoแบกt khรดng xรกc ฤ‘แป‹nh [{0}] +inspection.workflow.syntax.unknownPermission=Quyแปn khรดng xรกc ฤ‘แป‹nh [{0}] +inspection.workflow.syntax.unknownPermissionValue=Giรก trแป‹ quyแปn khรดng xรกc ฤ‘แป‹nh [{0}] +inspection.workflow.syntax.unknownJobKey=Mรฃ cรดng viแป‡c khรดng xรกc ฤ‘แป‹nh [{0}] +inspection.workflow.syntax.unknownStepKey=Khรณa bฦฐแป›c khรดng xรกc ฤ‘แป‹nh [{0}] +inspection.action.reload=Tแบฃi lแบกi [{0}] +inspection.action.unresolved=[{0}] chฦฐa ฤ‘ฦฐแปฃc giแบฃi quyแบฟt - kiแปƒm tra quyแปn truy cแบญp tร i khoแบฃn GitHub, quyแปn cแปงa kho lฦฐu trแปฏ riรชng, giแป›i hแบกn tแป‘c ฤ‘แป™, thiแบฟu giแป›i thiแป‡u hoแบทc thiแบฟu siรชu dแปฏ liแป‡u hร nh ฤ‘แป™ng/quy trรฌnh cรดng viแป‡c +inspection.action.jump=Chuyแปƒn tแป›i tแบญp tin [{0}] +inspection.output.unused=Chฦฐa sแปญ dแปฅng [{0}] +inspection.secret.invalid.if=Xรณa [{0}] - Bรญ mแบญt khรดng hแปฃp lแป‡ trong cรขu lแป‡nh `if` +inspection.secret.replace.runtime=Thay thแบฟ [{0}] bแบฑng [{1}] - nแบฟu nรณ khรดng ฤ‘ฦฐแปฃc cung cแบฅp khi chแบกy +inspection.needs.invalid.job=Xรณa jobId khรดng hแปฃp lแป‡ [{0}] - jobId nร y khรดng khแป›p vแป›i bแบฅt kแปณ cรดng viแป‡c nร o trฦฐแป›c ฤ‘รณ +documentation.description=Mรด tแบฃ: {0} +documentation.type=Loแบกi: {0} +documentation.required=Bแบฏt buแป™c: {0} +documentation.default=Mแบทc ฤ‘แป‹nh: {0} +documentation.deprecated=Khรดng dรนng nแปฏa: {0} +documentation.open.declaration=Khai bรกo mแปŸ ({0}) +documentation.input.label=ฤ‘แบงu vร o +documentation.secret.label=bรญ mแบญt +documentation.env.label=Biแบฟn mรดi trฦฐแปng +documentation.matrix.label=Thuแป™c tรญnh ma trแบญn +documentation.need.label=Viแป‡c lร m cแบงn thiแบฟt +documentation.need.description=Phแปฅ thuแป™c cรดng viแป‡c trแปฑc tiแบฟp. +documentation.needOutput.label=ฤแบงu ra cรดng viแป‡c cแบงn thiแบฟt +documentation.reusableJob.label=Cรดng viแป‡c quy trรฌnh lร m viแป‡c cรณ thแปƒ tรกi sแปญ dแปฅng +documentation.reusableJob.description=Cรดng viแป‡c ฤ‘ฦฐแปฃc khai bรกo trong quy trรฌnh lร m viแป‡c cรณ thแปƒ sแปญ dแปฅng lแบกi nร y. +documentation.reusableJobOutput.label=ฤแบงu ra cรดng viแป‡c cแปงa quy trรฌnh lร m viแป‡c cรณ thแปƒ tรกi sแปญ dแปฅng +documentation.service.label=Thรนng chแปฉa dแป‹ch vแปฅ +documentation.servicePort.label=Cแบฃng dแป‹ch vแปฅ +documentation.container.label=Vรนng chแปฉa cรดng viแป‡c +documentation.symbol.label=Biแปƒu tฦฐแปฃng quy trรฌnh lร m viแป‡c +documentation.symbol.description=ฤรฃ giแบฃi quyแบฟt biแปƒu thแปฉc quy trรฌnh cรดng viแป‡c. +documentation.workflowOutput.label=ฤแบงu ra cแปงa quy trรฌnh lร m viแป‡c +documentation.jobOutput.label=ฤแบงu ra cรดng viแป‡c +documentation.action.label=hร nh ฤ‘แป™ng +documentation.externalAction.label=Hร nh ฤ‘แป™ng bรชn ngoร i +documentation.reusableWorkflow.label=Quy trรฌnh lร m viแป‡c cรณ thแปƒ tรกi sแปญ dแปฅng +documentation.resolvedFrom=ฤ‘ฦฐแปฃc giแบฃi quyแบฟt tแปซ {0} +documentation.notResolved=chฦฐa giแบฃi quyแบฟt ฤ‘ฦฐแปฃc +documentation.inputs.title=ฤแบงu vร o +documentation.outputs.title=ฤ‘แบงu ra +documentation.secrets.title=Bรญ mแบญt +documentation.value.label=Giรก trแป‹ +documentation.step.title=Bฦฐแป›c {0} +documentation.name.label=Tรชn +documentation.uses.label=Cรดng dแปฅng +documentation.run.label=Chแบกy +documentation.description.label=Mรด tแบฃ +documentation.step.label=Bฦฐแป›c +documentation.source.label=Nguแป“n +documentation.stepOutput.label=Bฦฐแป›c ฤ‘แบงu ra +documentation.context.github=bแป‘i cแบฃnh github +documentation.context.github.description=Thรดng tin vแป sแปฑ kiแป‡n vร  lแบงn chแบกy quy trรฌnh cรดng viแป‡c hiแป‡n tแบกi. +documentation.context.gitea=bแป‘i cแบฃnh gitea +documentation.context.gitea.description=Bรญ danh tฦฐฦกng thรญch Gitea cho bแป‘i cแบฃnh Hร nh ฤ‘แป™ng GitHub. +documentation.context.inputs=bแป‘i cแบฃnh ฤ‘แบงu vร o +documentation.context.inputs.description=ฤแบงu vร o quy trรฌnh lร m viแป‡c, ฤ‘iแปu phแป‘i hoแบทc hร nh ฤ‘แป™ng cรณ sแบตn แปŸ ฤ‘รขy. +documentation.context.secrets=bแป‘i cแบฃnh bรญ mแบญt +documentation.context.secrets.description=Cรกc giรก trแป‹ bรญ mแบญt cรณ sแบตn cho dรฒng cรดng viแป‡c nร y hoแบทc cuแป™c gแปi dรฒng cรดng viแป‡c cรณ thแปƒ sแปญ dแปฅng lแบกi. +documentation.context.env=bแป‘i cแบฃnh mรดi trฦฐแปng +documentation.context.env.description=Cรกc biแบฟn mรดi trฦฐแปng hiแปƒn thแป‹ แปŸ vแป‹ trรญ nร y. +documentation.context.matrix=bแป‘i cแบฃnh ma trแบญn +documentation.context.matrix.description=Giรก trแป‹ ma trแบญn cho cรดng viแป‡c hiแป‡n tแบกi. +documentation.context.steps=bแป‘i cแบฃnh cรกc bฦฐแป›c +documentation.context.steps.description=Cรกc bฦฐแป›c trฦฐแป›c ฤ‘รณ trong cรดng viแป‡c hiแป‡n tแบกi, bao gแป“m kแบฟt quแบฃ ฤ‘แบงu ra vร  trแบกng thรกi. +documentation.context.needs=cแบงn bแป‘i cแบฃnh +documentation.context.needs.description=Sแปฑ phแปฅ thuแป™c trแปฑc tiแบฟp vร o cรดng viแป‡c vร  ฤ‘แบงu ra/kแบฟt quแบฃ cแปงa chรบng. +documentation.context.jobs=bแป‘i cแบฃnh viแป‡c lร m +documentation.context.jobs.description=Cรกc cรดng viแป‡c vร  ฤ‘แบงu ra cแปงa quy trรฌnh lร m viแป‡c cรณ thแปƒ tรกi sแปญ dแปฅng. +documentation.context.outputs=kแบฟt quแบฃ ฤ‘แบงu ra +documentation.context.outputs.description=Giรก trแป‹ ฤ‘แบงu ra ฤ‘ฦฐแปฃc hiแปƒn thแป‹ bแปŸi bฦฐแป›c hoแบทc cรดng viแป‡c nร y. +documentation.context.result=kแบฟt quแบฃ +documentation.context.result.description=Kแบฟt quแบฃ cรดng viแป‡c: thร nh cรดng, thแบฅt bแบกi, bแป‹ hแปงy hoแบทc bแป qua. +documentation.context.outcome=kแบฟt quแบฃ +documentation.context.outcome.description=Bฦฐแป›c kแบฟt quแบฃ trฦฐแป›c khi รกp dแปฅng lแป—i tiแบฟp tแปฅc. +documentation.context.conclusion=kแบฟt luแบญn +documentation.context.conclusion.description=Bฦฐแป›c kแบฟt quแบฃ sau khi รกp dแปฅng lแป—i tiแบฟp tแปฅc. +error.report.action=Bรกo cรกo ngoแบกi lแป‡ +error.report.description=Mรด tแบฃ +error.report.steps=Cรกc bฦฐแป›c ฤ‘แปƒ tรกi tแบกo +error.report.sample=Vui lรฒng cung cแบฅp mแบซu mรฃ nแบฟu cรณ +error.report.message=Tin nhแบฏn +error.report.runtime=Thรดng tin thแปi gian chแบกy +error.report.pluginVersion=Phiรชn bแบฃn plugin: {0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=dแบฅu vแบฟt ngฤƒn xแบฟp +completion.shell.bash=Vแป sรฒ. Sแปญ dแปฅng bash trรชn trรฌnh chแบกy Linux vร  macOS vร  bash Git cho Windows trรชn trรฌnh chแบกy Windows. +completion.shell.sh=Dแปฑ phรฒng vแป POSIX. +completion.shell.pwsh=Lรตi PowerShell. +completion.shell.powershell=Windows PowerShell. +completion.shell.cmd=Dแบฅu nhแบฏc lแป‡nh cแปงa Windows. +completion.shell.python=Trรฌnh chแบกy lแป‡nh Python. +completion.runner.name=Tรชn cแปงa ngฦฐแปi chแบกy thแปฑc hiแป‡n cรดng viแป‡c. +completion.runner.os=Hแป‡ ฤ‘iแปu hร nh cแปงa ร hแบญu thแปฑc hiแป‡n cรดng viแป‡c. Cรกc giรก trแป‹ cรณ thแปƒ lร  Linux, Windows hoแบทc macOS. +completion.runner.arch=Kiแบฟn trรบc cแปงa ร hแบญu thแปฑc hiแป‡n cรดng viแป‡c. Cรกc giรก trแป‹ cรณ thแปƒ cรณ lร  X86, X64, ARM hoแบทc ARM64. +completion.runner.temp=ฤฦฐแปng dแบซn ฤ‘แบฟn thฦฐ mแปฅc tแบกm thแปi trรชn ร hแบญu. Thฦฐ mแปฅc nร y ฤ‘ฦฐแปฃc lร m trแป‘ng khi bแบฏt ฤ‘แบงu vร  kแบฟt thรบc mแป—i cรดng viแป‡c. Lฦฐu รฝ rแบฑng cรกc tแบญp tin sแบฝ khรดng bแป‹ xรณa nแบฟu tร i khoแบฃn ngฦฐแปi dรนng cแปงa ngฦฐแปi chแบกy khรดng cรณ quyแปn xรณa chรบng. +completion.runner.toolCache=ฤฦฐแปng dแบซn ฤ‘แบฟn thฦฐ mแปฅc chแปฉa cรกc cรดng cแปฅ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt sแบตn cho cรกc ร hแบญu ฤ‘ฦฐแปฃc lฦฐu trแปฏ trรชn mรกy chแปง GitHub. +completion.runner.debug=ฤiแปu nร y chแป‰ ฤ‘ฦฐแปฃc ฤ‘แบทt nแบฟu tรญnh nฤƒng ghi nhแบญt kรฝ gแปก lแป—i ฤ‘ฦฐแปฃc bแบญt vร  luรดn cรณ giรก trแป‹ lร  1. +completion.runner.environment=Mรดi trฦฐแปng cแปงa ngฦฐแปi chแบกy thแปฑc hiแป‡n cรดng viแป‡c. Cรกc giรก trแป‹ cรณ thแปƒ ฤ‘ฦฐแปฃc lฦฐu trแปฏ trรชn github hoแบทc tแปฑ lฦฐu trแปฏ. +completion.job.status=Tรฌnh trแบกng hiแป‡n tแบกi cแปงa cรดng viแป‡c. +completion.job.checkRunId=ID chแบกy kiแปƒm tra cแปงa cรดng viแป‡c hiแป‡n tแบกi. +completion.job.container=Thรดng tin vแป thรนng chแปฉa cรดng viแป‡c. +completion.job.services=Cรกc thรนng chแปฉa dแป‹ch vแปฅ ฤ‘ฦฐแปฃc tแบกo cho mแป™t cรดng viแป‡c. +completion.job.workflowRef=Tham chiแบฟu ฤ‘แบงy ฤ‘แปง cแปงa tแป‡p quy trรฌnh cรดng viแป‡c xรกc ฤ‘แป‹nh cรดng viแป‡c hiแป‡n tแบกi. +completion.job.workflowSha=Cam kแบฟt SHA cแปงa tแป‡p quy trรฌnh cรดng viแป‡c xรกc ฤ‘แป‹nh cรดng viแป‡c hiแป‡n tแบกi. +completion.job.workflowRepository=Chแปง sแปŸ hแปฏu/repo cแปงa kho lฦฐu trแปฏ chแปฉa tแป‡p quy trรฌnh cรดng viแป‡c xรกc ฤ‘แป‹nh cรดng viแป‡c hiแป‡n tแบกi. +completion.job.workflowFilePath=ฤฦฐแปng dแบซn tแป‡p quy trรฌnh lร m viแป‡c, liรชn quan ฤ‘แบฟn thฦฐ mแปฅc gแป‘c cแปงa kho lฦฐu trแปฏ. +completion.job.containerField=Trฦฐแปng vรนng chแปฉa cรดng viแป‡c +completion.job.service=Dแป‹ch vแปฅ viแป‡c lร m +completion.job.serviceField=Lฤฉnh vแปฑc dแป‹ch vแปฅ viแป‡c lร m +completion.job.mappedServicePort=Cแป•ng dแป‹ch vแปฅ ฤ‘ฦฐแปฃc รกnh xแบก +completion.strategy.failFast=Liแป‡u tแบฅt cแบฃ cรดng viแป‡c ฤ‘ang thแปฑc hiแป‡n cรณ bแป‹ hแปงy hay khรดng nแบฟu bแบฅt kแปณ cรดng viแป‡c ma trแบญn nร o bแป‹ lแป—i. +completion.strategy.jobIndex=Chแป‰ sแป‘ dแปฑa trรชn sแป‘ 0 cแปงa cรดng viแป‡c hiแป‡n tแบกi trong ma trแบญn. +completion.strategy.jobTotal=Tแป•ng sแป‘ cรดng viแป‡c trong ma trแบญn. +completion.strategy.maxParallel=Sแป‘ lฦฐแปฃng cรดng viแป‡c ma trแบญn tแป‘i ฤ‘a cรณ thแปƒ chแบกy ฤ‘แป“ng thแปi. +completion.context.inputs=ฤแบงu vร o quy trรฌnh lร m viแป‡c, chแบณng hแบกn nhฦฐ Workflow_dispatch hoแบทc Workflow_call. +completion.context.secrets=Bรญ mแบญt quy trรฌnh lร m viแป‡c. +completion.context.job=Thรดng tin vแป cรดng viแป‡c hiแป‡n ฤ‘ang thแปฑc hiแป‡n. +completion.context.jobs=Cรดng viแป‡c quy trรฌnh lร m viแป‡c. +completion.context.matrix=Thuแป™c tรญnh ma trแบญn ฤ‘ฦฐแปฃc xรกc ฤ‘แป‹nh cho cรดng viแป‡c ma trแบญn hiแป‡n tแบกi. +completion.context.strategy=Thรดng tin chiแบฟn lฦฐแปฃc thแปฑc hiแป‡n ma trแบญn cho cรดng viแป‡c hiแป‡n tแบกi. +completion.context.steps=Cรกc bฦฐแป›c cรณ id trong cรดng viแป‡c hiแป‡n tแบกi. +completion.context.env=Biแบฟn mรดi trฦฐแปng tแปซ cรดng viแป‡c vร  cรกc bฦฐแป›c. +completion.context.vars=Cรกc biแบฟn cแบฅu hรฌnh tรนy chแป‰nh tแปซ phแบกm vi tแป• chแปฉc, kho lฦฐu trแปฏ vร  mรดi trฦฐแปng. +completion.context.needs=Cรกc cรดng viแป‡c phแบฃi hoร n thร nh trฦฐแป›c khi cรดng viแป‡c nร y cรณ thแปƒ chแบกy, cรนng vแป›i kแบฟt quแบฃ ฤ‘แบงu ra vร  kแบฟt quแบฃ cแปงa chรบng. +completion.context.github=Thรดng tin sแปฑ kiแป‡n vร  hoแบกt ฤ‘แป™ng cแปงa quy trรฌnh lร m viแป‡c tแปซ ngแปฏ cแบฃnh GitHub. +completion.context.gitea=Bรญ danh tฦฐฦกng thรญch Gitea cho bแป‘i cแบฃnh Hร nh ฤ‘แป™ng GitHub. +completion.context.runner=Thรดng tin vแป ngฦฐแปi chแบกy ฤ‘ang thแปฑc hiแป‡n cรดng viแป‡c hiแป‡n tแบกi. +completion.secret.githubToken=Mรฃ thรดng bรกo ฤ‘ฦฐแปฃc tแบกo tแปฑ ฤ‘แป™ng cho mแป—i lแบงn chแบกy quy trรฌnh cรดng viแป‡c. +completion.remote.repository=Kho lฦฐu trแปฏ tแปซ xa +completion.uses.local.workflow=Quy trรฌnh lร m viแป‡c cรณ thแปƒ tรกi sแปญ dแปฅng cแปฅc bแป™ +completion.uses.local.action=Hร nh ฤ‘แป™ng cแปฅc bแป™ +completion.uses.ref.known=Tham chiแบฟu quy trรฌnh lร m viแป‡c ฤ‘รฃ biแบฟt +completion.uses.ref.remote=Tham khแบฃo quy trรฌnh lร m viแป‡c tแปซ xa +completion.uses.remote.known=Hร nh ฤ‘แป™ng tแปซ xa ฤ‘รฃ biแบฟt hoแบทc quy trรฌnh lร m viแป‡c cรณ thแปƒ sแปญ dแปฅng lแบกi +completion.workflow.syntax=Cรบ phรกp quy trรฌnh lร m viแป‡c cแปงa Hร nh ฤ‘แป™ng GitHub +completion.workflow.top.name=Tรชn hiแปƒn thแป‹ quy trรฌnh lร m viแป‡c +completion.workflow.top.run-name=Tรชn chแบกy ฤ‘แป™ng +completion.workflow.top.on=Cรกc sแปฑ kiแป‡n bแบฏt ฤ‘แบงu quy trรฌnh lร m viแป‡c +completion.workflow.top.permissions=Quyแปn GITHUB_TOKEN mแบทc ฤ‘แป‹nh +completion.workflow.top.env=Biแบฟn mรดi trฦฐแปng trรชn toร n quy trรฌnh lร m viแป‡c +completion.workflow.top.defaults=Cร i ฤ‘แบทt cรดng viแป‡c vร  bฦฐแป›c mแบทc ฤ‘แป‹nh +completion.workflow.top.concurrency=Nhรณm ฤ‘แป“ng thแปi vร  hแปงy bแป +completion.workflow.top.jobs=Cรกc cรดng viแป‡c chแบกy trong quy trรฌnh lร m viแป‡c nร y +completion.workflow.event.branch_protection_rule=Bแบฃo vแป‡ chi nhรกnh ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.check_run=ฤรฃ thay ฤ‘แป•i lแบงn chแบกy kiแปƒm tra ฤ‘ฦกn lแบป +completion.workflow.event.check_suite=Bแป™ kiแปƒm tra ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.create=ฤรฃ tแบกo nhรกnh hoแบทc thแบป +completion.workflow.event.delete=Chi nhรกnh hoแบทc thแบป ฤ‘รฃ bแป‹ xรณa +completion.workflow.event.deployment=ฤรฃ tแบกo triแปƒn khai +completion.workflow.event.deployment_status=Trแบกng thรกi triแปƒn khai ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.discussion=Cuแป™c thแบฃo luแบญn ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.discussion_comment=Bรฌnh luแบญn thแบฃo luแบญn ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.fork=Kho lฦฐu trแปฏ rแบฝ nhรกnh +completion.workflow.event.gollum=Trang Wiki ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.image_version=Phiรชn bแบฃn hรฌnh แบฃnh gรณi ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.issue_comment=Sแปฑ cแป‘ hoแบทc nhแบญn xรฉt PR ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.issues=Vแบฅn ฤ‘แป ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.label=ฤรฃ thay ฤ‘แป•i nhรฃn +completion.workflow.event.merge_group=ฤรฃ yรชu cแบงu kiแปƒm tra hร ng ฤ‘แปฃi hแปฃp nhแบฅt +completion.workflow.event.milestone=Cแป™t mแป‘c ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.page_build=Cรกc trang ฤ‘รฃ ฤ‘ฦฐแปฃc xรขy dแปฑng +completion.workflow.event.project=Dแปฑ รกn cแป• ฤ‘iแปƒn ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.project_card=Thแบป dแปฑ รกn cแป• ฤ‘iแปƒn ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.project_column=Cแป™t dแปฑ รกn cแป• ฤ‘iแปƒn ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.public=Kho lฦฐu trแปฏ ฤ‘รฃ ฤ‘ฦฐแปฃc cรดng khai +completion.workflow.event.pull_request=Yรชu cแบงu kรฉo ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.pull_request_review=ฤรกnh giรก PR ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.pull_request_review_comment=ฤรฃ thay ฤ‘แป•i nhแบญn xรฉt ฤ‘รกnh giรก PR +completion.workflow.event.pull_request_target=Bแป‘i cแบฃnh mแปฅc tiรชu PR. Nhแปฏng con dao sแบฏc bรฉn. +completion.workflow.event.push=Cam kแบฟt hoแบทc ฤ‘แบฉy thแบป +completion.workflow.event.registry_package=Gรณi ฤ‘ฦฐแปฃc xuแบฅt bแบฃn hoแบทc cแบญp nhแบญt +completion.workflow.event.release=ฤรฃ thay ฤ‘แป•i bแบฃn phรกt hร nh +completion.workflow.event.repository_dispatch=Sแปฑ kiแป‡n API tรนy chแป‰nh +completion.workflow.event.schedule=Cron ฤ‘รกnh dแบฅu. ฤแป“ng hแป“. +completion.workflow.event.status=Trแบกng thรกi cam kแบฟt ฤ‘รฃ thay ฤ‘แป•i +completion.workflow.event.watch=Kho lฦฐu trแปฏ ฤ‘ฦฐแปฃc gแบฏn dแบฅu sao +completion.workflow.event.workflow_call=Cuแป™c gแปi quy trรฌnh lร m viแป‡c cรณ thแปƒ tรกi sแปญ dแปฅng +completion.workflow.event.workflow_dispatch=Nรบt chแบกy thแปง cรดng +completion.workflow.event.workflow_run=ฤรฃ thay ฤ‘แป•i quy trรฌnh lร m viแป‡c +completion.workflow.eventFilter.types=Giแป›i hแบกn cรกc loแบกi hoแบกt ฤ‘แป™ng +completion.workflow.eventFilter.branches=Chแป‰ nhแปฏng nhรกnh nร y +completion.workflow.eventFilter.branches-ignore=Bแป qua cรกc nhรกnh nร y +completion.workflow.eventFilter.tags=Chแป‰ nhแปฏng thแบป nร y +completion.workflow.eventFilter.tags-ignore=Bแป qua cรกc thแบป nร y +completion.workflow.eventFilter.paths=Chแป‰ nhแปฏng con ฤ‘ฦฐแปng nร y +completion.workflow.eventFilter.paths-ignore=Bแป qua nhแปฏng con ฤ‘ฦฐแปng nร y +completion.workflow.eventFilter.workflows=Tรชn quy trรฌnh lร m viแป‡c cแบงn xem +completion.workflow.eventFilter.cron=Lแป‹ch trรฌnh cron. ฤแป“ng hแป“ nhแป xรญu. +completion.workflow.permission.actions=Cรกc hoแบกt ฤ‘แป™ng cแปงa quy trรฌnh lร m viแป‡c vร  cรกc tแบกo phแบฉm hร nh ฤ‘แป™ng +completion.workflow.permission.artifact-metadata=Bแบฃn ghi siรชu dแปฏ liแป‡u tแบกo tรกc +completion.workflow.permission.attestations=Chแปฉng thแปฑc hiแป‡n vแบญt +completion.workflow.permission.checks=Kiแปƒm tra cรกc lแบงn chแบกy vร  dรฃy phรฒng +completion.workflow.permission.code-quality=Bรกo cรกo chแบฅt lฦฐแปฃng mรฃ +completion.workflow.permission.contents=Nแป™i dung kho lฦฐu trแปฏ +completion.workflow.permission.deployments=Triแปƒn khai +completion.workflow.permission.discussions=Thแบฃo luแบญn +completion.workflow.permission.id-token=Mรฃ thรดng bรกo kแบฟt nแป‘i OpenID +completion.workflow.permission.issues=vแบฅn ฤ‘แป +completion.workflow.permission.models=Mรด hรฌnh GitHub +completion.workflow.permission.packages=Gรณi GitHub +completion.workflow.permission.pages=Trang GitHub +completion.workflow.permission.pull-requests=Kรฉo yรชu cแบงu +completion.workflow.permission.security-events=Sแปฑ kiแป‡n quรฉt mรฃ vร  bแบฃo mแบญt +completion.workflow.permission.statuses=Trแบกng thรกi cam kแบฟt +completion.workflow.permission.vulnerability-alerts=Cแบฃnh bรกo phแปฅ thuแป™c +completion.workflow.permission.value.read=quyแปn truy cแบญp ฤ‘แปc +completion.workflow.permission.value.write=Quyแปn truy cแบญp ghi, bao gแป“m ฤ‘แปc +completion.workflow.permission.value.none=Khรดng cรณ quyแปn truy cแบญp +completion.workflow.permission.shorthand.read-all=Tแบฅt cแบฃ cรกc quyแปn ฤ‘แปc. Chฤƒn lแป›n. +completion.workflow.permission.shorthand.write-all=Tแบฅt cแบฃ cรกc quyแปn viแบฟt. Cรขy bรบa lแป›n. +completion.workflow.permission.shorthand.empty=Vรด hiแป‡u hรณa quyแปn truy cแบญp mรฃ thรดng bรกo +completion.workflow.job.name=Tรชn hiแปƒn thแป‹ cรดng viแป‡c +completion.workflow.job.permissions=Quyแปn mรฃ thรดng bรกo cรดng viแป‡c +completion.workflow.job.needs=Cรดng viแป‡c chแป ฤ‘แปฃi +completion.workflow.job.if=ฤiแปu kiแป‡n cรดng viแป‡c +completion.workflow.job.runs-on=Nhรฃn hoแบทc nhรณm ร hแบญu +completion.workflow.job.snapshot=แบขnh chแปฅp ngฦฐแปi chแบกy +completion.workflow.job.environment=Mรดi trฦฐแปng triแปƒn khai +completion.workflow.job.concurrency=Khรณa ฤ‘แป“ng thแปi cรดng viแป‡c +completion.workflow.job.outputs=ฤแบงu ra cรกc cรดng viแป‡c khรกc cรณ thแปƒ ฤ‘แปc +completion.workflow.job.env=Biแบฟn mรดi trฦฐแปng cรดng viแป‡c +completion.workflow.job.defaults=Cร i ฤ‘แบทt mแบทc ฤ‘แป‹nh cรดng viแป‡c +completion.workflow.job.steps=Danh sรกch bฦฐแป›c. Cรดng viแป‡c thแปฑc tแบฟ. +completion.workflow.job.timeout-minutes=Thแปi gian chแป cรดng viแป‡c tรญnh bแบฑng phรบt +completion.workflow.job.strategy=Chiแบฟn lฦฐแปฃc ma trแบญn vร  lแบญp kแบฟ hoแบกch +completion.workflow.job.continue-on-error=Hรฃy ฤ‘แปƒ cรดng viแป‡c nร y thแบฅt bแบกi nhแบน nhร ng +completion.workflow.job.container=Vรนng chแปฉa cho cรดng viแป‡c nร y +completion.workflow.job.services=Thรนng dแป‹ch vแปฅ sidecar +completion.workflow.job.uses=Quy trรฌnh lร m viแป‡c cรณ thแปƒ tรกi sแปญ dแปฅng ฤ‘แปƒ gแปi +completion.workflow.job.with=ฤแบงu vร o cho quy trรฌnh cรดng viแป‡c ฤ‘ฦฐแปฃc gแปi +completion.workflow.job.secrets=Bรญ mแบญt cho quy trรฌnh lร m viแป‡c ฤ‘ฦฐแปฃc gแปi lร  +completion.workflow.defaultsRun.shell=Shell mแบทc ฤ‘แป‹nh cho cรกc bฦฐแป›c chแบกy +completion.workflow.defaultsRun.working-directory=Thฦฐ mแปฅc lร m viแป‡c mแบทc ฤ‘แป‹nh +completion.workflow.concurrency.group=Khรณa tรชn cho cรกc lฦฐแปฃt chแบกy xแบฟp hร ng +completion.workflow.concurrency.cancel-in-progress=Hแปงy cรกc lฦฐแปฃt chแบกy so khแป›p cลฉ hฦกn +completion.workflow.environment.name=Tรชn mรดi trฦฐแปng +completion.workflow.environment.url=URL mรดi trฦฐแปng +completion.workflow.strategy.matrix=Trแปฅc ma trแบญn vร  cรกc biแบฟn thแปƒ +completion.workflow.strategy.fail-fast=Hแปงy bแป ma trแบญn anh chแป‹ em khi thแบฅt bแบกi +completion.workflow.strategy.max-parallel=Giแป›i hแบกn song song cแปงa ma trแบญn +completion.workflow.matrix.include=Thรชm kแบฟt hแปฃp ma trแบญn +completion.workflow.matrix.exclude=Xรณa kแบฟt hแปฃp ma trแบญn +completion.workflow.step.id=Id bฦฐแป›c ฤ‘แปƒ tham khแบฃo +completion.workflow.step.if=ฤiแปu kiแป‡n bฦฐแป›c +completion.workflow.step.name=Tรชn hiแปƒn thแป‹ bฦฐแป›c +completion.workflow.step.uses=Hร nh ฤ‘แป™ng ฤ‘แปƒ chแบกy +completion.workflow.step.run=Tแบญp lแป‡nh Shell ฤ‘แปƒ chแบกy +completion.workflow.step.shell=Shell cho bฦฐแป›c chแบกy nร y +completion.workflow.step.with=ฤแบงu vร o hร nh ฤ‘แป™ng +completion.workflow.step.env=Bฦฐแป›c biแบฟn mรดi trฦฐแปng +completion.workflow.step.continue-on-error=Hรฃy ฤ‘แปƒ bฦฐแป›c nร y thแบฅt bแบกi nhแบน nhร ng +completion.workflow.step.timeout-minutes=Thแปi gian chแป cแปงa bฦฐแป›c tรญnh bแบฑng phรบt +completion.workflow.step.working-directory=Bฦฐแป›c thฦฐ mแปฅc lร m viแป‡c +completion.workflow.container.image=Hรฌnh แบฃnh vรนng chแปฉa +completion.workflow.container.credentials=Thรดng tin ฤ‘ฤƒng kรฝ +completion.workflow.container.env=Biแบฟn mรดi trฦฐแปng vรนng chแปฉa +completion.workflow.container.ports=Cรกc cแป•ng ฤ‘แปƒ lแป™ +completion.workflow.container.volumes=Tแบญp ฤ‘แปƒ gแบฏn kแบฟt +completion.workflow.container.options=Tรนy chแปn tแบกo Docker +completion.workflow.service.image=Hรฌnh แบฃnh container dแป‹ch vแปฅ +completion.workflow.service.credentials=Thรดng tin ฤ‘ฤƒng kรฝ +completion.workflow.service.env=Biแบฟn mรดi trฦฐแปng dแป‹ch vแปฅ +completion.workflow.service.ports=Cแบฃng dแป‹ch vแปฅ +completion.workflow.service.volumes=Khแป‘i lฦฐแปฃng dแป‹ch vแปฅ +completion.workflow.service.options=Tรนy chแปn tแบกo Docker +completion.workflow.credentials.username=Tรชn ngฦฐแปi dรนng ฤ‘ฤƒng kรฝ +completion.workflow.credentials.password=ฤฤƒng kรฝ mแบญt khแบฉu hoแบทc mรฃ thรดng bรกo +completion.workflow.inputType.string=Nhแบญp vฤƒn bแบฃn +completion.workflow.inputType.boolean=ฤแบงu vร o ฤ‘รบng hoแบทc sai +completion.workflow.inputType.choice=ฤแบงu vร o lแปฑa chแปn thแบฃ xuแป‘ng +completion.workflow.inputType.number=Nhแบญp sแป‘ +completion.workflow.inputType.environment=ฤแบงu vร o cแปงa bแป™ chแปn mรดi trฦฐแปng +completion.workflow.boolean.true=Vรขng. Bแบญt nรณ lรชn. +completion.workflow.boolean.false=Khรดng. Giแปฏ nรณ trong bรณng tแป‘i. +completion.workflow.runner.ubuntu-latest=ร hแบญu Ubuntu mแป›i nhแบฅt +completion.workflow.runner.ubuntu-24.04=Ngฦฐแปi chแบกy Ubuntu 24.04 +completion.workflow.runner.ubuntu-22.04=Ngฦฐแปi chแบกy Ubuntu 22.04 +completion.workflow.runner.windows-latest=ร hแบญu Windows mแป›i nhแบฅt +completion.workflow.runner.windows-2025=Ngฦฐแปi chแบกy Windows Server 2025 +completion.workflow.runner.windows-2022=Ngฦฐแปi chแบกy Windows Server 2022 +completion.workflow.runner.macos-latest=ร hแบญu macOS mแป›i nhแบฅt +completion.workflow.runner.macos-15=ร hแบญu macOS 15 +completion.workflow.runner.macos-14=ร hแบญu macOS 14 +completion.workflow.runner.self-hosted=ร hแบญu cแปงa riรชng bแบกn. Rแบกp xiแบฟc cแปงa bแบกn. +completion.steps.outputs=Tแบญp hแปฃp cรกc ฤ‘แบงu ra ฤ‘ฦฐแปฃc xรกc ฤ‘แป‹nh cho bฦฐแป›c nร y. +completion.steps.conclusion=Kแบฟt quแบฃ cแปงa mแป™t bฦฐแป›c ฤ‘รฃ hoร n thร nh sau khi tiแบฟp tแปฅc xแบฃy ra lแป—i. +completion.steps.outcome=Kแบฟt quแบฃ cแปงa mแป™t bฦฐแป›c ฤ‘รฃ hoร n thร nh trฦฐแป›c khi รกp dแปฅng lแป—i tiแบฟp tแปฅc. +completion.jobs.outputs=Tแบญp hแปฃp cรกc ฤ‘แบงu ra ฤ‘ฦฐแปฃc xรกc ฤ‘แป‹nh cho cรดng viแป‡c. +completion.jobs.result=Kแบฟt quแบฃ cแปงa cรดng viแป‡c. +settings.displayName=Quy trรฌnh lร m viแป‡c cแปงa GitHub +settings.language.label=Ngรดn ngแปฏ: +settings.language.system=IDE/mแบทc ฤ‘แป‹nh hแป‡ thแป‘ng +settings.cache.title=Bแป™ ฤ‘แป‡m hร nh ฤ‘แป™ng +settings.cache.column.key=Khรณa bแป™ ฤ‘แป‡m +settings.cache.column.name=Tรชn +settings.cache.column.kind=loแบกi +settings.cache.column.state=tiแปƒu bang +settings.cache.column.expires=Hแบฟt hแบกn +settings.cache.kind.local=ฤ‘แป‹a phฦฐฦกng +settings.cache.kind.remote=tแปซ xa +settings.cache.state.resolved=ฤ‘รฃ giแบฃi quyแบฟt +settings.cache.state.pending=ฤ‘ang chแป xแปญ lรฝ +settings.cache.state.expired=cลฉ kแปน +settings.cache.state.suppressed=ฤ‘ร n รกp +settings.cache.refresh=Bแบฃng Refresh +settings.cache.deleteSelected=Xรณa ฤ‘รฃ chแปn +settings.cache.deleteAll=Xรณa tแบฅt cแบฃ +settings.cache.export=Xuแบฅt khแบฉu +settings.cache.import=Nhแบญp khแบฉu +settings.cache.summary=Bแป™ nhแป› ฤ‘แป‡m: Cรกc mแปฅc {0}, {1} ฤ‘รฃ giแบฃi quyแบฟt, {2} tแปซ xa, {3} cลฉ, {4} bแป‹ tแบฏt tiแบฟng. Bแป™ ฤ‘แป‡m: {5} KB. +settings.cache.noneSelected=Chแปn hร ng bแป™ ฤ‘แป‡m ฤ‘แบงu tiรชn. Cรขy chแป•i tแปซ chแป‘i phแปng ฤ‘oรกn. +settings.cache.deleteSelected.done=ฤรฃ xรณa cรกc mแปฅc bแป™ ฤ‘แป‡m {0}. Cรณ ฤ‘รกm mรขy bแปฅi nhแป. +settings.cache.deleteAll.confirm=Xรณa tแบฅt cแบฃ cรกc mแปฅc trong bแป™ nhแป› ฤ‘แป‡m cแปงa GitHub Workflow? +settings.cache.deleteAll.done=ฤรฃ xรณa tแบฅt cแบฃ cรกc mแปฅc trong bแป™ ฤ‘แป‡m. Bแป™ nhแป› ฤ‘แป‡m bรขy giแป im lแบทng mแป™t cรกch ฤ‘รกng ngแป. +settings.cache.export.done=ฤรฃ xuแบฅt cรกc mแปฅc bแป™ nhแป› ฤ‘แป‡m {0}. Con thรบ lฦฐu trแปฏ nhแป bแป‹ nhแป‘t trong lแป“ng. +settings.cache.import.done=Cรกc mแปฅc bแป™ ฤ‘แป‡m ฤ‘รฃ nhแบญp. Con thรบ lฦฐu trแปฏ ฤ‘รฃ cฦฐ xแปญ. +settings.cache.import.unsupported=Tแป‡p bแป™ ฤ‘แป‡m quy trรฌnh lร m viแป‡c GitHub khรดng ฤ‘ฦฐแปฃc hแป— trแปฃ. +settings.cache.import.brokenLine=Dรฒng bแป™ ฤ‘แป‡m quy trรฌnh lร m viแป‡c GitHub bแป‹ hแปng. +settings.cache.import.brokenKey=Khรณa bแป™ ฤ‘แป‡m quy trรฌnh lร m viแป‡c GitHub bแป‹ hแปng. +settings.support.button=Hแป— trแปฃ plugin nร y +settings.support.tooltip=MแปŸ trang hแป— trแปฃ +settings.support.line.0=Cung cแบฅp cho lรฒ xรขy dแปฑng +settings.support.line.1=Mua cร  phรช phรขn tรญch cรบ phรกp +settings.support.line.2=Nhร  tร i trแปฃ รญt quy trรฌnh lร m viแป‡c bแป‹ รกm แบฃnh hฦกn +workflow.run.jobs.title=Cรดng viแป‡c trong quy trรฌnh lร m viแป‡c +workflow.run.jobs.root=Chแบกy quy trรฌnh cรดng viแป‡c +workflow.run.jobs.description=GitHub Cรขy cรดng viแป‡c hร nh ฤ‘แป™ng vร  nhแบญt kรฝ cรดng viแป‡c ฤ‘รฃ chแปn +workflow.run.tree.done=xong +workflow.run.tree.failed=thแบฅt bแบกi +workflow.run.tree.skipped=bแป qua +workflow.run.tree.warn=cแบฃnh bรกo +workflow.run.tree.err=lแป—i +workflow.run.delete.tooltip=Xรณa lฦฐแปฃt chแบกy +workflow.run.delete.noRun=Chฦฐa cรณ id chแบกy. +workflow.run.delete.requested=ฤang xรณa chแบกy {0}. +workflow.run.delete.done=Chแบกy {0} ฤ‘รฃ bแป‹ xรณa. +workflow.run.delete.http=Xรณa HTTP {0}. ร”i. +workflow.run.delete.failed=Xรณa lแป—i: {0} +workflow.run.rerun.all.tooltip=Chแบกy lแบกi quy trรฌnh cรดng viแป‡c +workflow.run.rerun.failed.tooltip=Chแบกy lแบกi cรกc cรดng viแป‡c thแบฅt bแบกi +workflow.run.rerun.noRun=Chฦฐa cรณ id chแบกy. +workflow.run.rerun.all.requested=Yรชu cแบงu chแบกy lแบกi: {0}. +workflow.run.rerun.failed.requested=Cรดng viแป‡c khรดng thร nh cรดng ฤ‘ฦฐแปฃc yรชu cแบงu: {0}. +workflow.run.rerun.all.done=Chแบกy lแบกi hร ng ฤ‘แปฃi: {0}. +workflow.run.rerun.failed.done=Cรดng viแป‡c khรดng thร nh cรดng trong hร ng ฤ‘แปฃi: {0}. +workflow.run.rerun.http=Chแบกy lแบกi HTTP {0}. ร”i. +workflow.run.rerun.failed=Chแบกy lแบกi bแป‹ lแป—i: {0} +workflow.run.download.log.tooltip=Lฦฐu nhแบญt kรฝ cรดng viแป‡c +workflow.run.download.artifacts.tooltip=Tแบฃi xuแป‘ng hiแป‡n vแบญt +workflow.run.download.noRun=Chฦฐa cรณ id chแบกy. +workflow.run.download.log.requested=ฤang tรฌm nแบกp nhแบญt kรฝ cho {0}. +workflow.run.download.log.done=Nhแบญt kรฝ ฤ‘รฃ lฦฐu: {0}. +workflow.run.download.artifacts.requested=ฤang tรฌm kiแบฟm hiแป‡n vแบญt. +workflow.run.download.artifacts.empty=Khรดng cรณ hiแป‡n vแบญt. Khoแบฃng trแป‘ng nhแป xรญu. +workflow.run.download.artifact.expired=Hiแป‡n vแบญt ฤ‘รฃ hแบฟt hแบกn: {0}. +workflow.run.download.artifact.done=Hiแป‡n vแบญt ฤ‘รฃ lฦฐu: {0} -> {1}. +workflow.run.download.failed=Tแบฃi xuแป‘ng khรดng thร nh cรดng: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties b/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties new file mode 100644 index 0000000..6273f98 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties @@ -0,0 +1,442 @@ +plugin.name=GitHub ๅทฅไฝœๆต็จ‹ +plugin.description=ๆ”ฏๆŒ GitHub ๆ“ไฝœๅทฅไฝœๆต็จ‹ๆ–‡ไปถ +group.GitHubWorkflow.Tools.text=GitHub ๅทฅไฝœๆต็จ‹ +group.GitHubWorkflow.Tools.description=GitHub ๅทฅไฝœๆต็จ‹ๆ’ไปถๅทฅๅ…ท +action.GitHubWorkflow.RefreshActionCache.text=Refresh ๆ“ไฝœ็ผ“ๅญ˜ +action.GitHubWorkflow.RefreshActionCache.description=Refresh ่งฃๆžไบ†่ฟœ็จ‹ GitHub ๆ“ไฝœๅ’Œๅฏ้‡็”จ็š„ๅทฅไฝœๆต็จ‹ๅ…ƒๆ•ฐๆฎ +action.GitHubWorkflow.RestoreActionWarnings.text=ๆขๅคๆ“ไฝœ่ญฆๅ‘Š +action.GitHubWorkflow.RestoreActionWarnings.description=ๆขๅคๆŠ‘ๅˆถ็š„ๆ“ไฝœใ€่พ“ๅ…ฅๅ’Œ่พ“ๅ‡บ้ชŒ่ฏ่ญฆๅ‘Š +action.GitHubWorkflow.ClearActionCache.text=ๆธ…้™คๆ“ไฝœ็ผ“ๅญ˜ +action.GitHubWorkflow.ClearActionCache.description=ๆธ…้™ค็ผ“ๅญ˜็š„ GitHub ๆ“ไฝœๅ’Œๅฏ้‡็”จ็š„ๅทฅไฝœๆต็จ‹ๅ…ƒๆ•ฐๆฎ +notification.cache.cleared=ๅทฒๆธ…้™ค {0} ็ผ“ๅญ˜็š„ GitHub ๅทฅไฝœๆตๆก็›ฎใ€‚ +notification.cache.refresh.started=Refreshing {0} ็ผ“ๅญ˜ไบ†่ฟœ็จ‹ GitHub ๅทฅไฝœๆตๆก็›ฎใ€‚ +notification.warnings.restored=ๆขๅคไบ† {0} GitHub ๅทฅไฝœๆต็จ‹ๆก็›ฎ็š„่ญฆๅ‘Šใ€‚ +workflow.run.configuration.display=GitHub ๅทฅไฝœๆต็จ‹ +workflow.run.configuration.description=่ฐƒๅบฆๅนถ้ตๅพช GitHub Actions ๅทฅไฝœๆต็จ‹่ฟ่กŒ +workflow.run.configuration.name=GitHub ๅทฅไฝœๆต็จ‹๏ผš{0} +workflow.run.field.apiUrl=API URL +workflow.run.field.owner=ไธšไธป +workflow.run.field.repo=ๅญ˜ๅ‚จๅบ“ +workflow.run.field.workflow=ๅทฅไฝœๆต็จ‹ๆ–‡ไปถ +workflow.run.field.ref=Ref +workflow.run.field.tokenEnv=ไปค็‰Œ็Žฏๅขƒๅ˜้‡ๅŽๅค‡ +workflow.run.inputs.title=workflow_dispatch ่พ“ๅ…ฅ๏ผˆ้”ฎ=ๅ€ผ๏ผ‰ +workflow.run.error.apiUrl=GitHub API URL ๆ˜ฏๅฟ…้œ€็š„ใ€‚ +workflow.run.error.repository=GitHub ๅญ˜ๅ‚จๅบ“ๆ‰€ๆœ‰่€…ๅ’Œๅ็งฐๆ˜ฏๅฟ…้œ€็š„ใ€‚ +workflow.run.error.workflow=้œ€่ฆๅทฅไฝœๆต็จ‹ๆ–‡ไปถใ€‚ +workflow.run.error.ref=้œ€่ฆๅˆ†ๆ”ฏๆˆ–ๆ ‡็ญพๅผ•็”จใ€‚ +workflow.run.error.inputs=GitHub workflow_dispatch ๆœ€ๅคšๆ”ฏๆŒ 25 ไธช่พ“ๅ…ฅใ€‚ +workflow.run.gutter.stop=ๅœๆญขๅทฅไฝœๆต็จ‹่ฟ่กŒ +workflow.run.gutter.stop.text=ๅœๆญขๅทฅไฝœๆต็จ‹่ฟ่กŒ +workflow.run.gutter.stop.description=ๅ–ๆถˆๆœฌๆฌก่ฟ่กŒ +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.log.command=่ฟ่กŒ๏ผš +workflow.log.warning=่ญฆๅ‘Š๏ผš +workflow.log.error=้”™่ฏฏ๏ผš +workflow.run.cancel.requested=ๅ–ๆถˆ่ฏทๆฑ‚๏ผš{0}ใ€‚ +workflow.run.stop.before.id=่ฆๆฑ‚ๅœๆญขใ€‚่ฟ˜ๆฒกๆœ‰่ฟ่กŒ IDใ€‚ +workflow.run.cancel.http=ๅ–ๆถˆ HTTP {0}ใ€‚้’ฑๅธใ€‚ +workflow.run.cancel.failed=ๅ–ๆถˆๅคฑ่ดฅ๏ผš{0} +workflow.run.interrupted=่ขซๆ‰“ๆ–ญไบ†ใ€‚ +workflow.run.link=่ฟ่กŒ๏ผš{0} +workflow.run.discovery=่ฟ่กŒๅทฒๆŽฅๅ—ใ€‚็‹ฉ็ŒŽ่ท‘idใ€‚ +workflow.run.discovery.none=่ฟ˜ๆฒกๆœ‰่ฟ่กŒ IDใ€‚ๆ“ไฝœ้€‰้กนๅกไบ†่งฃๆ›ดๅคšใ€‚ +workflow.run.status=็Šถๆ€๏ผš{0}{1} +workflow.run.job.main=ไฝœไธš๏ผš{0} {1} [{2}{3}{4}] +workflow.run.job.status=็Šถๆ€๏ผš {0} {1}{2}{3} +workflow.run.logs.later=ๅฝ“ GitHub ๅ‘ๅธƒๆ—ฅๅฟ—ๆ—ถ๏ผŒๅฐ†ไผšๅ‡บ็Žฐๆ—ฅๅฟ—ใ€‚ +workflow.run.job.logs.later=ไฝœไธš {0}: {1} +workflow.run.log.failed=ๆ—ฅๅฟ—ไธ‹่ฝฝๅคฑ่ดฅ๏ผš{0} +workflow.run.log.failed.job={0} ็š„ๆ—ฅๅฟ—ไธ‹่ฝฝๅคฑ่ดฅ๏ผš{1} +workflow.run.job.url=URL: {0} +workflow.run.job.header=ๅทฅไฝœ๏ผš{0} +workflow.run.job.fallbackName=ไฝœไธš {0} +workflow.run.overview=ๅทฅไฝœๆต็จ‹่ฟ่กŒ {0} {1}/{2} ๅทฒๅฎŒๆˆ๏ผŒ{3} ๆญฃๅœจ่ฟ่กŒ +workflow.run.state.ok=[็กฎๅฎš] +workflow.run.state.fail=[ๅคฑ่ดฅ] +workflow.run.state.running=[่ฟ่กŒ] +workflow.run.state.waiting=[็ญ‰ๅพ…] +workflow.run.dispatch.verbs=ๅฏๅŠจ|ๆŽ’้˜Ÿ|ๅฌๅ”ค|ๅฏๅŠจ|ๅผ•ๅฏผ +workflow.run.dispatch.objects=ๅทฅไฝœๆต็จ‹|่‡ชๅŠจๅŒ–|็ฎก้“|่ฟ่กŒ +workflow.run.dispatch={0} {1} {2}{3} ้€‚็”จไบŽ {5} ไธŠ็š„ {4}ใ€‚ +workflow.run.notification.auth=GitHub ๅทฅไฝœๆต่ฐƒๅบฆ้œ€่ฆ็ป่ฟ‡่บซไปฝ้ชŒ่ฏ็š„ GitHub ๅธๆˆทใ€‚ๅœจ {0} ไธญๆทปๅŠ ๆˆ–ๅˆทๆ–ฐๅธๆˆทใ€‚ +workflow.run.notification.openSettings=ๆ‰“ๅผ€ GitHub ่ฎพ็ฝฎ +workflow.cache.progress.title=่งฃๅ†ณ GitHub ๆ“ไฝœ +workflow.cache.progress.text=่งฃๆž {0} {1} +workflow.cache.kind.action=่กŒๅŠจ +workflow.cache.kind.workflow=ๅทฅไฝœๆต็จ‹ +inspection.parameter.input=่พ“ๅ…ฅ +inspection.parameter.secret=็ง˜ๅฏ† +inspection.action.delete.invalid=ๅˆ ้™คๆ— ๆ•ˆ็š„{0} [{1}] +inspection.action.update.major=ๅฐ†ๆ“ไฝœ [{0}] ๆ›ดๆ–ฐไธบ [{1}] +inspection.warning.toggle=ๅˆ‡ๆข [{1}] ็š„่ญฆๅ‘Š [{0}] +inspection.warning.on=ไธŠ +inspection.warning.off=ๅ…ณ้—ญ +inspection.statement.incomplete=ๅฃฐๆ˜ŽไธๅฎŒๆ•ด [{0}] +inspection.invalid.suffix.remove=ๅˆ ้™คๆ— ๆ•ˆๅŽ็ผ€ [{0}] +inspection.replace.with=ๆ›ฟๆขไธบ [{0}] +inspection.invalid.remove=ๅˆ ้™คๆ— ๆ•ˆ็š„[{0}] +inspection.workflow.syntax.unknownTopLevelKey=ๆœช็Ÿฅ็š„ๅทฅไฝœๆต็จ‹ๅฏ†้’ฅ [{0}] +inspection.workflow.syntax.unknownEventKey=ๆœช็Ÿฅๅทฅไฝœๆตไบ‹ไปถ [{0}] +inspection.workflow.syntax.unknownTriggerKey=ๆœช็Ÿฅ่งฆๅ‘้”ฎ [{0}] +inspection.workflow.syntax.unknownTriggerFilter=ๆœช็Ÿฅ่งฆๅ‘่ฟ‡ๆปคๅ™จ [{0}] +inspection.workflow.syntax.unknownTriggerValue=ๆœช็Ÿฅ่งฆๅ‘ๅ€ผ [{0}] +inspection.workflow.syntax.unknownPermission=ๆœช็Ÿฅๆƒ้™ [{0}] +inspection.workflow.syntax.unknownPermissionValue=ๆœช็Ÿฅ็š„ๆƒ้™ๅ€ผ [{0}] +inspection.workflow.syntax.unknownJobKey=ๆœช็Ÿฅไฝœไธšๅฏ†้’ฅ [{0}] +inspection.workflow.syntax.unknownStepKey=ๆœช็Ÿฅๆญฅ้ชค้”ฎ [{0}] +inspection.action.reload=้‡ๆ–ฐๅŠ ่ฝฝ[{0}] +inspection.action.unresolved=ๆœช่งฃๅ†ณ็š„ [{0}] - ๆฃ€ๆŸฅ GitHub ๅธๆˆท่ฎฟ้—ฎใ€็งๆœ‰ๅญ˜ๅ‚จๅบ“ๆƒ้™ใ€้€Ÿ็އ้™ๅˆถใ€็ผบๅฐ‘ๅผ•็”จๆˆ–็ผบๅฐ‘ๆ“ไฝœ/ๅทฅไฝœๆตๅ…ƒๆ•ฐๆฎ +inspection.action.jump=่ทณ่ฝฌๅˆฐๆ–‡ไปถ [{0}] +inspection.output.unused=ๆœชไฝฟ็”จ[{0}] +inspection.secret.invalid.if=ๅˆ ้™ค [{0}] - ็ง˜ๅฏ†ๅœจโ€œifโ€่ฏญๅฅไธญๆ— ๆ•ˆ +inspection.secret.replace.runtime=ๅฐ† [{0}] ๆ›ฟๆขไธบ [{1}] - ๅฆ‚ๆžœ่ฟ่กŒๆ—ถๆœชๆไพ› +inspection.needs.invalid.job=ๅˆ ้™คๆ— ๆ•ˆ็š„ jobId [{0}] - ๆญค jobId ไธŽไปปไฝ•ๅ…ˆๅ‰็š„ไฝœไธš้ƒฝไธๅŒน้… +documentation.description=ๆ่ฟฐ๏ผš{0} +documentation.type=็ฑปๅž‹๏ผš{0} +documentation.required=ๅฟ…้œ€๏ผš{0} +documentation.default=้ป˜่ฎคๅ€ผ๏ผš{0} +documentation.deprecated=ๅทฒๅผƒ็”จ๏ผš{0} +documentation.open.declaration=ๅผ€ๆ”พๅฃฐๆ˜Ž๏ผˆ{0}๏ผ‰ +documentation.input.label=่พ“ๅ…ฅ +documentation.secret.label=็ง˜ๅฏ† +documentation.env.label=็Žฏๅขƒๅ˜้‡ +documentation.matrix.label=็Ÿฉ้˜ตๆ€ง่ดจ +documentation.need.label=้œ€่ฆ็š„ๅทฅไฝœ +documentation.need.description=็›ดๆŽฅๅทฅไฝœไพ่ต–ใ€‚ +documentation.needOutput.label=้œ€่ฆ็š„ๅทฅไฝœ่พ“ๅ‡บ +documentation.reusableJob.label=ๅฏ้‡ๅคไฝฟ็”จ็š„ๅทฅไฝœๆต็จ‹ไฝœไธš +documentation.reusableJob.description=ๅœจๆญคๅฏ้‡็”จๅทฅไฝœๆต็จ‹ไธญๅฃฐๆ˜Ž็š„ไฝœไธšใ€‚ +documentation.reusableJobOutput.label=ๅฏ้‡ๅคไฝฟ็”จ็š„ๅทฅไฝœๆต็จ‹ไฝœไธš่พ“ๅ‡บ +documentation.service.label=ๆœๅŠกๅฎนๅ™จ +documentation.servicePort.label=ๆœๅŠก็ซฏๅฃ +documentation.container.label=ไฝœไธšๅฎนๅ™จ +documentation.symbol.label=ๅทฅไฝœๆต็จ‹็ฌฆๅท +documentation.symbol.description=ๅทฒ่งฃๅ†ณ็š„ๅทฅไฝœๆต่กจ่พพๅผใ€‚ +documentation.workflowOutput.label=ๅทฅไฝœๆต็จ‹่พ“ๅ‡บ +documentation.jobOutput.label=ไฝœไธš่พ“ๅ‡บ +documentation.action.label=่กŒๅŠจ +documentation.externalAction.label=ๅค–้ƒจ่กŒๅŠจ +documentation.reusableWorkflow.label=ๅฏ้‡ๅคไฝฟ็”จ็š„ๅทฅไฝœๆต็จ‹ +documentation.resolvedFrom=ไปŽ {0} ่งฃๅ†ณ +documentation.notResolved=ๅฐšๆœช่งฃๅ†ณ +documentation.inputs.title=่พ“ๅ…ฅ +documentation.outputs.title=่พ“ๅ‡บ +documentation.secrets.title=็ง˜ๅฏ† +documentation.value.label=ไปทๅ€ผ +documentation.step.title=ๆญฅ้ชค {0} +documentation.name.label=ๅ็งฐ +documentation.uses.label=็”จ้€” +documentation.run.label=่ฟ่กŒ +documentation.description.label=ๆ่ฟฐ +documentation.step.label=ๆญฅ้ชค +documentation.source.label=ๆฅๆบ +documentation.stepOutput.label=ๆญฅ่ฟ›่พ“ๅ‡บ +documentation.context.github=githubไธŠไธ‹ๆ–‡ +documentation.context.github.description=ๆœ‰ๅ…ณๅฝ“ๅ‰ๅทฅไฝœๆต็จ‹่ฟ่กŒๅ’Œไบ‹ไปถ็š„ไฟกๆฏใ€‚ +documentation.context.gitea=gitea ไธŠไธ‹ๆ–‡ +documentation.context.gitea.description=GitHub ๆ“ไฝœไธŠไธ‹ๆ–‡็š„ Gitea ๅ…ผๅฎนๅˆซๅใ€‚ +documentation.context.inputs=่พ“ๅ…ฅไธŠไธ‹ๆ–‡ +documentation.context.inputs.description=ๆญคๅค„ๆไพ›ๅทฅไฝœๆต็จ‹ใ€่ฐƒๅบฆๆˆ–ๆ“ไฝœ่พ“ๅ…ฅใ€‚ +documentation.context.secrets=็ง˜ๅฏ†่ƒŒๆ™ฏ +documentation.context.secrets.description=ๆญคๅทฅไฝœๆตๆˆ–ๅฏ้‡็”จๅทฅไฝœๆต่ฐƒ็”จๅฏ็”จ็š„็ง˜ๅฏ†ๅ€ผใ€‚ +documentation.context.env=็ŽฏๅขƒไธŠไธ‹ๆ–‡ +documentation.context.env.description=็Žฏๅขƒๅ˜้‡ๅœจๆญคไฝ็ฝฎๅฏ่งใ€‚ +documentation.context.matrix=็Ÿฉ้˜ตไธŠไธ‹ๆ–‡ +documentation.context.matrix.description=ๅฝ“ๅ‰ไฝœไธš็š„็Ÿฉ้˜ตๅ€ผใ€‚ +documentation.context.steps=ๆญฅ้ชคไธŠไธ‹ๆ–‡ +documentation.context.steps.description=ๅฝ“ๅ‰ไฝœไธšไธญ็š„ๅ…ˆๅ‰ๆญฅ้ชค๏ผŒๅŒ…ๆ‹ฌ่พ“ๅ‡บๅ’Œ็Šถๆ€ใ€‚ +documentation.context.needs=้œ€่ฆ่ƒŒๆ™ฏ +documentation.context.needs.description=็›ดๆŽฅไฝœไธšไพ่ต–ๆ€งๅŠๅ…ถ่พ“ๅ‡บ/็ป“ๆžœใ€‚ +documentation.context.jobs=ๅทฅไฝœ่ƒŒๆ™ฏ +documentation.context.jobs.description=ๅฏ้‡็”จ็š„ๅทฅไฝœๆต็จ‹ไฝœไธšๅ’Œ่พ“ๅ‡บใ€‚ +documentation.context.outputs=่พ“ๅ‡บ +documentation.context.outputs.description=ๆญคๆญฅ้ชคๆˆ–ไฝœไธšๅ…ฌๅผ€็š„่พ“ๅ‡บๅ€ผใ€‚ +documentation.context.result=็ป“ๆžœ +documentation.context.result.description=ไฝœไธš็ป“ๆžœ๏ผšๆˆๅŠŸใ€ๅคฑ่ดฅใ€ๅ–ๆถˆๆˆ–่ทณ่ฟ‡ใ€‚ +documentation.context.outcome=็ป“ๆžœ +documentation.context.outcome.description=ๅบ”็”จ้”™่ฏฏ็ปง็ปญไน‹ๅ‰็š„ๆญฅ้ชค็ป“ๆžœใ€‚ +documentation.context.conclusion=็ป“่ฎบ +documentation.context.conclusion.description=ๅบ”็”จ้”™่ฏฏ็ปง็ปญๅŽ็š„ๆญฅ้ชค็ป“ๆžœใ€‚ +error.report.action=ๆŠฅๅ‘Šๅผ‚ๅธธ +error.report.description=ๆ่ฟฐ +error.report.steps=้‡็Žฐๆญฅ้ชค +error.report.sample=ๅฆ‚ๆžœ้€‚็”จ๏ผŒ่ฏทๆไพ›ไปฃ็ ็คบไพ‹ +error.report.message=็•™่จ€ +error.report.runtime=่ฟ่กŒๆ—ถไฟกๆฏ +error.report.pluginVersion=ๆ’ไปถ็‰ˆๆœฌ๏ผš{0} +error.report.ide=IDE: {0} +error.report.os=OS: {0} +error.report.stacktrace=ๅ †ๆ ˆ่ทŸ่ธช +completion.shell.bash=Bashๅค–ๅฃณใ€‚ๅœจ Linux ๅ’Œ macOS ่ฟ่กŒๅ™จไธŠไฝฟ็”จ bash๏ผŒๅœจ Windows ่ฟ่กŒๅ™จไธŠไฝฟ็”จ Git ่ฟ›่กŒ Windows bashใ€‚ +completion.shell.sh=POSIX shell ๅŽๅค‡ใ€‚ +completion.shell.pwsh=PowerShell ๆ ธๅฟƒใ€‚ +completion.shell.powershell=Windows PowerShellใ€‚ +completion.shell.cmd=Windows ๅ‘ฝไปคๆ็คบ็ฌฆใ€‚ +completion.shell.python=Python ๅ‘ฝไปค่ฟ่กŒ็จ‹ๅบใ€‚ +completion.runner.name=ๆ‰ง่กŒไฝœไธš็š„่ฟ่กŒ็จ‹ๅบ็š„ๅ็งฐใ€‚ +completion.runner.os=ๆ‰ง่กŒไฝœไธš็š„่ฟ่กŒ็จ‹ๅบ็š„ๆ“ไฝœ็ณป็ปŸใ€‚ๅฏ่ƒฝ็š„ๅ€ผไธบ Linuxใ€Windows ๆˆ– macOSใ€‚ +completion.runner.arch=ๆ‰ง่กŒไฝœไธš็š„่ฟ่กŒ็จ‹ๅบ็š„ๆžถๆž„ใ€‚ๅฏ่ƒฝ็š„ๅ€ผไธบ X86ใ€X64ใ€ARM ๆˆ– ARM64ใ€‚ +completion.runner.temp=่ฟ่กŒๅ™จไธŠไธดๆ—ถ็›ฎๅฝ•็š„่ทฏๅพ„ใ€‚่ฏฅ็›ฎๅฝ•ๅœจๆฏไธชไฝœไธšๅผ€ๅง‹ๅ’Œ็ป“ๆŸๆ—ถ้ƒฝไผš่ขซๆธ…็ฉบใ€‚่ฏทๆณจๆ„๏ผŒๅฆ‚ๆžœ่ฟ่กŒ่€…็š„็”จๆˆทๅธๆˆทๆ— ๆƒๅˆ ้™คๆ–‡ไปถ๏ผŒๅˆ™ๆ–‡ไปถไธไผš่ขซๅˆ ้™คใ€‚ +completion.runner.toolCache=ๅŒ…ๅซ GitHub ๆ‰˜็ฎก่ฟ่กŒๅ™จ้ข„่ฃ…ๅทฅๅ…ท็š„็›ฎๅฝ•่ทฏๅพ„ใ€‚ +completion.runner.debug=ไป…ๅฝ“ๅฏ็”จ่ฐƒ่ฏ•ๆ—ฅๅฟ—่ฎฐๅฝ•ๆ—ถๆ‰่ฎพ็ฝฎๆญคๅ€ผ๏ผŒๅนถไธ”ๅ€ผๅง‹็ปˆไธบ 1ใ€‚ +completion.runner.environment=่ฟ่กŒๅ™จๆ‰ง่กŒไฝœไธš็š„็Žฏๅขƒใ€‚ๅฏ่ƒฝ็š„ๅ€ผๆ˜ฏ github ๆ‰˜็ฎกๆˆ–่‡ชๆ‰˜็ฎกใ€‚ +completion.job.status=ๅทฅไฝœ็š„ๅฝ“ๅ‰็Šถๆ€ใ€‚ +completion.job.checkRunId=ๅฝ“ๅ‰ไฝœไธš็š„ๆฃ€ๆŸฅ่ฟ่กŒ IDใ€‚ +completion.job.container=ๆœ‰ๅ…ณไฝœไธšๅฎนๅ™จ็š„ไฟกๆฏใ€‚ +completion.job.services=ไธบไฝœไธšๅˆ›ๅปบ็š„ๆœๅŠกๅฎนๅ™จใ€‚ +completion.job.workflowRef=ๅฎšไน‰ๅฝ“ๅ‰ไฝœไธš็š„ๅทฅไฝœๆต็จ‹ๆ–‡ไปถ็š„ๅฎŒๆ•ดๅ‚่€ƒใ€‚ +completion.job.workflowSha=ๅฎšไน‰ๅฝ“ๅ‰ไฝœไธš็š„ๅทฅไฝœๆตๆ–‡ไปถ็š„ๆไบค SHAใ€‚ +completion.job.workflowRepository=ๅŒ…ๅซๅฎšไน‰ๅฝ“ๅ‰ไฝœไธš็š„ๅทฅไฝœๆตๆ–‡ไปถ็š„ๅญ˜ๅ‚จๅบ“็š„ๆ‰€ๆœ‰่€…/ๅญ˜ๅ‚จๅบ“ใ€‚ +completion.job.workflowFilePath=ๅทฅไฝœๆตๆ–‡ไปถ่ทฏๅพ„๏ผŒ็›ธๅฏนไบŽๅญ˜ๅ‚จๅบ“ๆ น็›ฎๅฝ•ใ€‚ +completion.job.containerField=ไฝœไธšๅฎนๅ™จ้ข†ๅŸŸ +completion.job.service=ๅฐฑไธšๆœๅŠก +completion.job.serviceField=ๅฐฑไธšๆœๅŠก้ข†ๅŸŸ +completion.job.mappedServicePort=ๆ˜ ๅฐ„็š„ๆœๅŠก็ซฏๅฃ +completion.strategy.failFast=ๅฆ‚ๆžœไปปไฝ•็Ÿฉ้˜ตไฝœไธšๅคฑ่ดฅ๏ผŒๆ˜ฏๅฆๅ–ๆถˆๆ‰€ๆœ‰ๆญฃๅœจ่ฟ›่กŒ็š„ไฝœไธšใ€‚ +completion.strategy.jobIndex=็Ÿฉ้˜ตไธญๅฝ“ๅ‰ไฝœไธš็š„ไปŽ้›ถๅผ€ๅง‹็š„็ดขๅผ•ใ€‚ +completion.strategy.jobTotal=็Ÿฉ้˜ตไธญ็š„่Œไฝๆ€ปๆ•ฐใ€‚ +completion.strategy.maxParallel=ๅฏไปฅๅŒๆ—ถ่ฟ่กŒ็š„็Ÿฉ้˜ตไฝœไธš็š„ๆœ€ๅคงๆ•ฐ้‡ใ€‚ +completion.context.inputs=ๅทฅไฝœๆต่พ“ๅ…ฅ๏ผŒไพ‹ๅฆ‚ workflow_dispatch ๆˆ– workflow_callใ€‚ +completion.context.secrets=ๅทฅไฝœๆต็จ‹็š„็ง˜ๅฏ†ใ€‚ +completion.context.job=ๆœ‰ๅ…ณๅฝ“ๅ‰ๆญฃๅœจ่ฟ่กŒ็š„ไฝœไธš็š„ไฟกๆฏใ€‚ +completion.context.jobs=ๅทฅไฝœๆต็จ‹ไฝœไธšใ€‚ +completion.context.matrix=ไธบๅฝ“ๅ‰็Ÿฉ้˜ตไฝœไธšๅฎšไน‰็š„็Ÿฉ้˜ตๅฑžๆ€งใ€‚ +completion.context.strategy=ๅฝ“ๅ‰ไฝœไธš็š„็Ÿฉ้˜ตๆ‰ง่กŒ็ญ–็•ฅไฟกๆฏใ€‚ +completion.context.steps=ๅฝ“ๅ‰ไฝœไธšไธญๅธฆๆœ‰ id ็š„ๆญฅ้ชคใ€‚ +completion.context.env=ๆฅ่‡ชไฝœไธšๅ’Œๆญฅ้ชค็š„็Žฏๅขƒๅ˜้‡ใ€‚ +completion.context.vars=ๆฅ่‡ช็ป„็ป‡ใ€ๅญ˜ๅ‚จๅบ“ๅ’Œ็Žฏๅขƒ่Œƒๅ›ด็š„่‡ชๅฎšไน‰้…็ฝฎๅ˜้‡ใ€‚ +completion.context.needs=ๅœจๆญคไฝœไธš่ฟ่กŒไน‹ๅ‰ๅฟ…้กปๅฎŒๆˆ็š„ไฝœไธšๅŠๅ…ถ่พ“ๅ‡บๅ’Œ็ป“ๆžœใ€‚ +completion.context.github=ๆฅ่‡ช GitHub ไธŠไธ‹ๆ–‡็š„ๅทฅไฝœๆต่ฟ่กŒๅ’Œไบ‹ไปถไฟกๆฏใ€‚ +completion.context.gitea=GitHub ๆ“ไฝœไธŠไธ‹ๆ–‡็š„ Gitea ๅ…ผๅฎนๅˆซๅใ€‚ +completion.context.runner=ๆœ‰ๅ…ณๆ‰ง่กŒๅฝ“ๅ‰ไฝœไธš็š„่ฟ่กŒ็จ‹ๅบ็š„ไฟกๆฏใ€‚ +completion.secret.githubToken=ไธบๆฏไธชๅทฅไฝœๆต็จ‹่ฟ่กŒ่‡ชๅŠจๅˆ›ๅปบไปค็‰Œใ€‚ +completion.remote.repository=่ฟœ็จ‹ๅญ˜ๅ‚จๅบ“ +completion.uses.local.workflow=ๆœฌๅœฐๅฏ้‡็”จๅทฅไฝœๆต็จ‹ +completion.uses.local.action=ๅฝ“ๅœฐ่กŒๅŠจ +completion.uses.ref.known=ๅทฒ็Ÿฅ็š„ๅทฅไฝœๆต็จ‹ๅ‚่€ƒ +completion.uses.ref.remote=่ฟœ็จ‹ๅทฅไฝœๆต็จ‹ๅ‚่€ƒ +completion.uses.remote.known=ๅทฒ็Ÿฅ็š„่ฟœ็จ‹ๆ“ไฝœๆˆ–ๅฏ้‡ๅคไฝฟ็”จ็š„ๅทฅไฝœๆต็จ‹ +completion.workflow.syntax=GitHub ๆ“ไฝœๅทฅไฝœๆต่ฏญๆณ• +completion.workflow.top.name=ๅทฅไฝœๆต็จ‹ๆ˜พ็คบๅ็งฐ +completion.workflow.top.run-name=ๅŠจๆ€่ฟ่กŒๅ็งฐ +completion.workflow.top.on=ๅฏๅŠจๅทฅไฝœๆต็จ‹็š„ไบ‹ไปถ +completion.workflow.top.permissions=้ป˜่ฎค GITHUB_TOKEN ๆƒ้™ +completion.workflow.top.env=ๅทฅไฝœๆต็จ‹่Œƒๅ›ด็š„็Žฏๅขƒๅ˜้‡ +completion.workflow.top.defaults=้ป˜่ฎคไฝœไธšๅ’Œๆญฅ้ชค่ฎพ็ฝฎ +completion.workflow.top.concurrency=ๅนถๅ‘็ป„ๅ’Œๅ–ๆถˆ +completion.workflow.top.jobs=ๅœจๆญคๅทฅไฝœๆต็จ‹ไธญ่ฟ่กŒ็š„ไฝœไธš +completion.workflow.event.branch_protection_rule=ๅˆ†ๆ”ฏไฟๆŠคๅทฒๆ›ดๆ”น +completion.workflow.event.check_run=ๅ•ๆฌกๆฃ€ๆŸฅ่ฟ่กŒๅทฒๆ›ดๆ”น +completion.workflow.event.check_suite=ๆฃ€ๆŸฅๅฅ—ไปถๅทฒๆ›ดๆ”น +completion.workflow.event.create=ๅˆ›ๅปบๅˆ†ๆ”ฏๆˆ–ๆ ‡็ญพ +completion.workflow.event.delete=ๅˆ†ๆ”ฏๆˆ–ๆ ‡็ญพๅทฒๅˆ ้™ค +completion.workflow.event.deployment=้ƒจ็ฝฒๅทฒๅˆ›ๅปบ +completion.workflow.event.deployment_status=้ƒจ็ฝฒ็Šถๆ€ๅทฒๆ›ดๆ”น +completion.workflow.event.discussion=่ฎจ่ฎบๅ‘็”Ÿๅ˜ๅŒ– +completion.workflow.event.discussion_comment=่ฎจ่ฎบ่ฏ„่ฎบๅทฒๆ›ดๆ”น +completion.workflow.event.fork=ๅญ˜ๅ‚จๅบ“ๅˆ†ๅ‰ +completion.workflow.event.gollum=็ปดๅŸบ้กต้ขๅทฒๆ›ดๆ”น +completion.workflow.event.image_version=ๅŒ…้•œๅƒ็‰ˆๆœฌๅทฒๆ›ดๆ”น +completion.workflow.event.issue_comment=้—ฎ้ข˜ๆˆ– PR ่ฏ„่ฎบๅทฒๆ›ดๆ”น +completion.workflow.event.issues=้—ฎ้ข˜ๅทฒๆ›ดๆ”น +completion.workflow.event.label=ๆ ‡็ญพๅทฒๆ›ดๆ”น +completion.workflow.event.merge_group=่ฏทๆฑ‚ๅˆๅนถ้˜Ÿๅˆ—ๆฃ€ๆŸฅ +completion.workflow.event.milestone=้‡Œ็จ‹็ข‘ๅ‘็”Ÿๅ˜ๅŒ– +completion.workflow.event.page_build=้กต้ขๆž„ๅปบ่ฟ่กŒ +completion.workflow.event.project=็ปๅ…ธ้กน็›ฎๅ˜ไบ† +completion.workflow.event.project_card=็ปๅ…ธ้กน็›ฎๅกๅทฒๆ›ดๆ”น +completion.workflow.event.project_column=็ปๅ…ธ้กน็›ฎๆ ็›ฎๅทฒๆ›ดๆ”น +completion.workflow.event.public=ๅญ˜ๅ‚จๅบ“ๅทฒๅ…ฌๅผ€ +completion.workflow.event.pull_request=ๆ‹‰ๅ–่ฏทๆฑ‚ๅทฒๆ›ดๆ”น +completion.workflow.event.pull_request_review=PR ่ฏ„่ฎบๅทฒๆ›ดๆ”น +completion.workflow.event.pull_request_review_comment=PR ่ฏ„่ฎบๅทฒๆ›ดๆ”น +completion.workflow.event.pull_request_target=PR ็›ฎๆ ‡ไธŠไธ‹ๆ–‡ใ€‚้”‹ๅˆฉ็š„ๅˆ€ใ€‚ +completion.workflow.event.push=ๆไบคๆˆ–ๆ ‡่ฎฐๆŽจ้€ +completion.workflow.event.registry_package=ๅŒ…ๅทฒๅ‘ๅธƒๆˆ–ๆ›ดๆ–ฐ +completion.workflow.event.release=็‰ˆๆœฌๅทฒๆ›ดๆ”น +completion.workflow.event.repository_dispatch=่‡ชๅฎšไน‰APIไบ‹ไปถ +completion.workflow.event.schedule=ๅ…‹ๆœ—ๅ‹พ้€‰ใ€‚ๅ‘ๆกใ€‚ +completion.workflow.event.status=ๆไบค็Šถๆ€ๅทฒๆ›ดๆ”น +completion.workflow.event.watch=ๅญ˜ๅ‚จๅบ“ๅทฒๅŠ ๆ˜Ÿๆ ‡ +completion.workflow.event.workflow_call=ๅฏ้‡็”จ็š„ๅทฅไฝœๆต็จ‹่ฐƒ็”จ +completion.workflow.event.workflow_dispatch=ๆ‰‹ๅŠจ่ฟ่กŒๆŒ‰้’ฎ +completion.workflow.event.workflow_run=ๅทฅไฝœๆต็จ‹่ฟ่กŒๅทฒๆ›ดๆ”น +completion.workflow.eventFilter.types=้™ๅˆถๆดปๅŠจ็ฑปๅž‹ +completion.workflow.eventFilter.branches=ๅชๆœ‰่ฟ™ไบ›ๅˆ†ๆ”ฏ +completion.workflow.eventFilter.branches-ignore=่ทณ่ฟ‡่ฟ™ไบ›ๅˆ†ๆ”ฏ +completion.workflow.eventFilter.tags=ๅชๆœ‰่ฟ™ไบ›ๆ ‡็ญพ +completion.workflow.eventFilter.tags-ignore=่ทณ่ฟ‡่ฟ™ไบ›ๆ ‡็ญพ +completion.workflow.eventFilter.paths=ๅชๆœ‰่ฟ™ไบ›่ทฏๅพ„ +completion.workflow.eventFilter.paths-ignore=่ทณ่ฟ‡่ฟ™ไบ›่ทฏๅพ„ +completion.workflow.eventFilter.workflows=่ฆ่ง‚็œ‹็š„ๅทฅไฝœๆต็จ‹ๅ็งฐ +completion.workflow.eventFilter.cron=ๅ…‹ๆœ—ๆ—ถ้—ด่กจใ€‚ๅฐๅ‘ๆกใ€‚ +completion.workflow.permission.actions=ๅทฅไฝœๆต็จ‹่ฟ่กŒๅ’Œๆ“ไฝœๅทฅไปถ +completion.workflow.permission.artifact-metadata=ๅทฅไปถๅ…ƒๆ•ฐๆฎ่ฎฐๅฝ• +completion.workflow.permission.attestations=ๅทฅไปถ่ฏๆ˜Ž +completion.workflow.permission.checks=ๆฃ€ๆŸฅ่ฟ่กŒๅ’Œๅฅ—ๆˆฟ +completion.workflow.permission.code-quality=ไปฃ็ ่ดจ้‡ๆŠฅๅ‘Š +completion.workflow.permission.contents=ๅญ˜ๅ‚จๅบ“ๅ†…ๅฎน +completion.workflow.permission.deployments=้ƒจ็ฝฒ +completion.workflow.permission.discussions=่ฎจ่ฎบ +completion.workflow.permission.id-token=OpenID Connect ไปฃๅธ +completion.workflow.permission.issues=้—ฎ้ข˜ +completion.workflow.permission.models=GitHub ๅž‹ๅท +completion.workflow.permission.packages=GitHub ๅฐ่ฃ… +completion.workflow.permission.pages=GitHub ้กต +completion.workflow.permission.pull-requests=ๆ‹‰ๅ–่ฏทๆฑ‚ +completion.workflow.permission.security-events=ไปฃ็ ๆ‰ซๆๅ’Œๅฎ‰ๅ…จไบ‹ไปถ +completion.workflow.permission.statuses=ๆไบค็Šถๆ€ +completion.workflow.permission.vulnerability-alerts=Dependabot ่ญฆๆŠฅ +completion.workflow.permission.value.read=่ฏปๅ–ๆƒ้™ +completion.workflow.permission.value.write=ๅ†™่ฎฟ้—ฎ๏ผŒๅŒ…ๆ‹ฌ่ฏป +completion.workflow.permission.value.none=ๆ— ๆณ•่ฎฟ้—ฎ +completion.workflow.permission.shorthand.read-all=ๆ‰€ๆœ‰ๆƒ้™ๅ‡ๅทฒ่ฏปๅ–ใ€‚ๅคงๆฏฏๅญใ€‚ +completion.workflow.permission.shorthand.write-all=ๆ‰€ๆœ‰ๆƒ้™้ƒฝๅฏไปฅๅ†™ใ€‚ๅคง้”คๅญใ€‚ +completion.workflow.permission.shorthand.empty=็ฆ็”จไปค็‰Œๆƒ้™ +completion.workflow.job.name=ไฝœไธšๆ˜พ็คบๅ็งฐ +completion.workflow.job.permissions=ๅทฅไฝœไปค็‰Œๆƒ้™ +completion.workflow.job.needs=็ญ‰ๅพ…็š„่Œไฝ +completion.workflow.job.if=ๅทฅไฝœๆกไปถ +completion.workflow.job.runs-on=่ท‘ๆญฅ่€…ๆ ‡็ญพๆˆ–็ป„ +completion.workflow.job.snapshot=่ท‘ๆญฅ่€…ๅฟซ็…ง +completion.workflow.job.environment=้ƒจ็ฝฒ็Žฏๅขƒ +completion.workflow.job.concurrency=ไฝœไธšๅนถๅ‘้” +completion.workflow.job.outputs=่พ“ๅ‡บๅ…ถไป–ไฝœไธšๅฏไปฅ่ฏปๅ– +completion.workflow.job.env=ไฝœไธš็Žฏๅขƒๅ˜้‡ +completion.workflow.job.defaults=ไฝœไธš้ป˜่ฎค่ฎพ็ฝฎ +completion.workflow.job.steps=ๆญฅ้ชคๅˆ—่กจใ€‚ๅฎž้™…ๅทฅไฝœใ€‚ +completion.workflow.job.timeout-minutes=ไฝœไธš่ถ…ๆ—ถ๏ผˆไปฅๅˆ†้’Ÿไธบๅ•ไฝ๏ผ‰ +completion.workflow.job.strategy=็Ÿฉ้˜ตๅ’Œ่ฐƒๅบฆ็ญ–็•ฅ +completion.workflow.job.continue-on-error=่ฎฉ่ฟ™ไปฝๅทฅไฝœ่ฝป่ฝปๅคฑ่ดฅ +completion.workflow.job.container=ๆญคไฝœไธš็š„ๅฎนๅ™จ +completion.workflow.job.services=่พน่ฝฆๆœๅŠกๅฎนๅ™จ +completion.workflow.job.uses=ๅฏ้‡็”จ็š„ๅทฅไฝœๆต็จ‹่ฐƒ็”จ +completion.workflow.job.with=่ขซ่ฐƒ็”จๅทฅไฝœๆต็จ‹็š„่พ“ๅ…ฅ +completion.workflow.job.secrets=่ขซ่ฐƒ็”จๅทฅไฝœๆต็จ‹็š„็ง˜ๅฏ† +completion.workflow.defaultsRun.shell=่ฟ่กŒๆญฅ้ชค็š„้ป˜่ฎค shell +completion.workflow.defaultsRun.working-directory=้ป˜่ฎคๅทฅไฝœ็›ฎๅฝ• +completion.workflow.concurrency.group=ๆŽ’้˜Ÿ่ฟ่กŒ็š„้”ๅฎšๅ็งฐ +completion.workflow.concurrency.cancel-in-progress=ๅ–ๆถˆๆ—ง็š„ๅŒน้…่ฟ่กŒ +completion.workflow.environment.name=็Žฏๅขƒๅ็งฐ +completion.workflow.environment.url=็Žฏๅขƒ URL +completion.workflow.strategy.matrix=็Ÿฉ้˜ต่ฝดๅ’Œๅ˜ไฝ“ +completion.workflow.strategy.fail-fast=ๅคฑ่ดฅๆ—ถๅ–ๆถˆ็Ÿฉ้˜ตๅŒ็บง +completion.workflow.strategy.max-parallel=็Ÿฉ้˜ตๅนถ่กŒๅบฆไธŠ้™ +completion.workflow.matrix.include=ๆทปๅŠ ็Ÿฉ้˜ต็ป„ๅˆ +completion.workflow.matrix.exclude=ๅˆ ้™ค็Ÿฉ้˜ต็ป„ๅˆ +completion.workflow.step.id=ๅ‚่€ƒๆญฅ้ชค ID +completion.workflow.step.if=ๆญฅ้ชคๆกไปถ +completion.workflow.step.name=ๆญฅ้ชคๆ˜พ็คบๅ็งฐ +completion.workflow.step.uses=่ฟ่กŒ็š„ๅŠจไฝœ +completion.workflow.step.run=่ฆ่ฟ่กŒ็š„ shell ่„šๆœฌ +completion.workflow.step.shell=ๆญค่ฟ่กŒๆญฅ้ชค็š„ shell +completion.workflow.step.with=ๅŠจไฝœ่พ“ๅ…ฅ +completion.workflow.step.env=ๆญฅ้ชค็Žฏๅขƒๅ˜้‡ +completion.workflow.step.continue-on-error=่ฎฉ่ฟ™ไธ€ๆญฅ่ฝป่ฝปๅคฑ่ดฅ +completion.workflow.step.timeout-minutes=ๆญฅ้ชค่ถ…ๆ—ถ๏ผˆไปฅๅˆ†้’Ÿไธบๅ•ไฝ๏ผ‰ +completion.workflow.step.working-directory=ๆญฅ้ชคๅทฅไฝœ็›ฎๅฝ• +completion.workflow.container.image=ๅฎนๅ™จ้•œๅƒ +completion.workflow.container.credentials=ๆณจๅ†Œๅ‡ญ่ฏ +completion.workflow.container.env=ๅฎนๅ™จ็Žฏๅขƒๅ˜้‡ +completion.workflow.container.ports=่ฆๅ…ฌๅผ€็š„็ซฏๅฃ +completion.workflow.container.volumes=่ฆๅฎ‰่ฃ…็š„ๅท +completion.workflow.container.options=Docker ๅˆ›ๅปบ้€‰้กน +completion.workflow.service.image=ๆœๅŠกๅฎนๅ™จ้•œๅƒ +completion.workflow.service.credentials=ๆณจๅ†Œๅ‡ญ่ฏ +completion.workflow.service.env=ๆœๅŠก็Žฏๅขƒๅ˜้‡ +completion.workflow.service.ports=ๆœๅŠก็ซฏๅฃ +completion.workflow.service.volumes=ๆœๅŠก้‡ +completion.workflow.service.options=Docker ๅˆ›ๅปบ้€‰้กน +completion.workflow.credentials.username=ๆณจๅ†Œ่กจ็”จๆˆทๅ +completion.workflow.credentials.password=ๆณจๅ†Œ่กจๅฏ†็ ๆˆ–ไปค็‰Œ +completion.workflow.inputType.string=ๆ–‡ๅญ—่พ“ๅ…ฅ +completion.workflow.inputType.boolean=่พ“ๅ…ฅ็œŸๆˆ–ๅ‡ +completion.workflow.inputType.choice=ไธ‹ๆ‹‰้€‰ๆ‹ฉ่พ“ๅ…ฅ +completion.workflow.inputType.number=ๆ•ฐๅญ—่พ“ๅ…ฅ +completion.workflow.inputType.environment=็Žฏๅขƒ้€‰ๆ‹ฉๅ™จ่พ“ๅ…ฅ +completion.workflow.boolean.true=ๆ˜ฏ็š„ใ€‚ๆ‰“ๅผ€ๅฎƒใ€‚ +completion.workflow.boolean.false=ไธ๏ผŒไฟๆŒ้ป‘ๆš—ใ€‚ +completion.workflow.runner.ubuntu-latest=ๆœ€ๆ–ฐ็š„ Ubuntu ่ท‘ๆญฅ่€… +completion.workflow.runner.ubuntu-24.04=Ubuntu 24.04 ่ท‘ๆญฅ่€… +completion.workflow.runner.ubuntu-22.04=Ubuntu 22.04 ่ท‘ๆญฅ่€… +completion.workflow.runner.windows-latest=ๆœ€ๆ–ฐ็š„ Windows ่ท‘ๆญฅ่€… +completion.workflow.runner.windows-2025=Windows ๆœๅŠกๅ™จ 2025 ่ฟ่กŒ่€… +completion.workflow.runner.windows-2022=Windows ๆœๅŠกๅ™จ 2022 ่ฟ่กŒ่€… +completion.workflow.runner.macos-latest=ๆœ€ๆ–ฐ็š„ macOS ่ท‘ๆญฅ่€… +completion.workflow.runner.macos-15=macOS 15 ่ท‘ๆญฅ่€… +completion.workflow.runner.macos-14=macOS 14 ่ท‘ๆญฅ่€… +completion.workflow.runner.self-hosted=ไฝ ่‡ชๅทฑ็š„่ท‘ๆญฅ่€…ใ€‚ไฝ ็š„้ฉฌๆˆๅ›ขใ€‚ +completion.steps.outputs=ไธบ่ฏฅๆญฅ้ชคๅฎšไน‰็š„่พ“ๅ‡บ้›†ใ€‚ +completion.steps.conclusion=ๅบ”็”จ้”™่ฏฏ็ปง็ปญๅŽๅทฒๅฎŒๆˆๆญฅ้ชค็š„็ป“ๆžœใ€‚ +completion.steps.outcome=ๅบ”็”จ้”™่ฏฏ็ปง็ปญไน‹ๅ‰ๅทฒๅฎŒๆˆๆญฅ้ชค็š„็ป“ๆžœใ€‚ +completion.jobs.outputs=ไธบไฝœไธšๅฎšไน‰็š„่พ“ๅ‡บ้›†ใ€‚ +completion.jobs.result=ๅทฅไฝœ็š„็ป“ๆžœใ€‚ +settings.displayName=GitHub ๅทฅไฝœๆต็จ‹ +settings.language.label=่ฏญ่จ€๏ผš +settings.language.system=IDE/็ณป็ปŸ้ป˜่ฎค +settings.cache.title=ๅŠจไฝœ็ผ“ๅญ˜ +settings.cache.column.key=็ผ“ๅญ˜้”ฎ +settings.cache.column.name=ๅ็งฐ +settings.cache.column.kind=็ง็ฑป +settings.cache.column.state=็Šถๆ€ +settings.cache.column.expires=่ฟ‡ๆœŸ +settings.cache.kind.local=ๆœฌๅœฐ็š„ +settings.cache.kind.remote=่ฟœ็จ‹ +settings.cache.state.resolved=ๅทฒ่งฃๅ†ณ +settings.cache.state.pending=ๅพ…ๅฎš +settings.cache.state.expired=้™ˆๆ—ง็š„ +settings.cache.state.suppressed=ๅŽ‹ๅˆถ +settings.cache.refresh=Refresh่กจ +settings.cache.deleteSelected=ๅˆ ้™คๆ‰€้€‰ๅ†…ๅฎน +settings.cache.deleteAll=ๅ…จ้ƒจๅˆ ้™ค +settings.cache.export=ๅ‡บๅฃ +settings.cache.import=่ฟ›ๅฃ +settings.cache.summary=็ผ“ๅญ˜๏ผš{0} ๆก็›ฎใ€{1} ๅทฒ่งฃๆžใ€{2} ่ฟœ็จ‹ใ€{3} ่ฟ‡ๆ—ถใ€{4} ้™้Ÿณใ€‚็ผ“ๅญ˜๏ผš{5} KBใ€‚ +settings.cache.noneSelected=้ฆ–ๅ…ˆ้€‰ๆ‹ฉ็ผ“ๅญ˜่กŒใ€‚ๆ‰ซๅธšๆ‹’็ป็Œœๆต‹ใ€‚ +settings.cache.deleteSelected.done=ๅทฒๅˆ ้™ค {0} ็ผ“ๅญ˜ๆก็›ฎใ€‚ๅซๆœ‰ๅพฎๅฐ็š„ๅฐ˜ๅŸƒไบ‘ใ€‚ +settings.cache.deleteAll.confirm=ๅˆ ้™คๆ‰€ๆœ‰ GitHub ๅทฅไฝœๆต็ผ“ๅญ˜ๆก็›ฎๅ—๏ผŸ +settings.cache.deleteAll.done=ๅˆ ้™คไบ†ๆ‰€ๆœ‰็ผ“ๅญ˜ๆก็›ฎใ€‚็ผ“ๅญ˜็Žฐๅœจๅฎ‰้™ๅพ—ไปคไบบๆ€€็–‘ใ€‚ +settings.cache.export.done=ๅฏผๅ‡บ็š„ {0} ็ผ“ๅญ˜ๆก็›ฎใ€‚ๅ…ณๅœจ็ฌผๅญ้‡Œ็š„ๅฐๆกฃๆกˆๅ…ฝใ€‚ +settings.cache.import.done=ๅฏผๅ…ฅ็š„็ผ“ๅญ˜ๆก็›ฎใ€‚ๆกฃๆกˆ้ฆ†็š„้‡Žๅ…ฝ่กจ็Žฐๅพ—ๅพˆๅฅฝใ€‚ +settings.cache.import.unsupported=ไธๅ—ๆ”ฏๆŒ็š„ GitHub ๅทฅไฝœๆต็ผ“ๅญ˜ๆ–‡ไปถใ€‚ +settings.cache.import.brokenLine=GitHub ๅทฅไฝœๆต็ผ“ๅญ˜็บฟๆŸๅใ€‚ +settings.cache.import.brokenKey=GitHub ๅทฅไฝœๆต็ผ“ๅญ˜ๅฏ†้’ฅๆŸๅใ€‚ +settings.support.button=ๆ”ฏๆŒ่ฟ™ไธชๆ’ไปถ +settings.support.tooltip=ๆ‰“ๅผ€ๆ”ฏๆŒ้กต้ข +settings.support.line.0=ๅ‘ๆž„ๅปบ็‚‰ไพ›ๆ–™ +settings.support.line.1=่ดญไนฐ่งฃๆžๅ™จๅ’–ๅ•ก +settings.support.line.2=่ตžๅŠฉๆ›ดๅฐ‘็š„้—น้ฌผๅทฅไฝœๆต็จ‹ +workflow.run.jobs.title=ๅทฅไฝœๆต็จ‹่Œไฝ +workflow.run.jobs.root=ๅทฅไฝœๆต็จ‹่ฟ่กŒ +workflow.run.jobs.description=GitHub ๆ“ไฝœไฝœไธšๆ ‘ๅ’Œ้€‰ๅฎš็š„ไฝœไธšๆ—ฅๅฟ— +workflow.run.tree.done=ๅฎŒๆˆ +workflow.run.tree.failed=ๅคฑ่ดฅไบ† +workflow.run.tree.skipped=่ทณ่ฟ‡ +workflow.run.tree.warn=่ญฆๅ‘Š +workflow.run.tree.err=็Šฏ้”™ +workflow.run.delete.tooltip=ๅˆ ้™ค่ฟ่กŒ +workflow.run.delete.noRun=่ฟ˜ๆฒกๆœ‰่ฟ่กŒ IDใ€‚ +workflow.run.delete.requested=ๅˆ ้™ค่ฟ่กŒ {0}ใ€‚ +workflow.run.delete.done=่ฟ่กŒ{0}ๅˆ ้™คใ€‚ +workflow.run.delete.http=ๅˆ ้™ค HTTP {0}ใ€‚้’ฑๅธใ€‚ +workflow.run.delete.failed=ๅˆ ้™คๅคฑ่ดฅ๏ผš{0} +workflow.run.rerun.all.tooltip=้‡ๆ–ฐ่ฟ่กŒๅทฅไฝœๆต็จ‹ +workflow.run.rerun.failed.tooltip=้‡ๆ–ฐ่ฟ่กŒๅคฑ่ดฅ็š„ไฝœไธš +workflow.run.rerun.noRun=่ฟ˜ๆฒกๆœ‰่ฟ่กŒ IDใ€‚ +workflow.run.rerun.all.requested=่ฏทๆฑ‚้‡ๆ–ฐ่ฟ่กŒ๏ผš{0}ใ€‚ +workflow.run.rerun.failed.requested=่ฏทๆฑ‚็š„ไฝœไธšๅคฑ่ดฅ๏ผš{0}ใ€‚ +workflow.run.rerun.all.done=้‡ๆ–ฐ่ฟ่กŒๅทฒๆŽ’้˜Ÿ๏ผš{0}ใ€‚ +workflow.run.rerun.failed.done=ๆŽ’้˜Ÿ็š„ๅคฑ่ดฅไฝœไธš๏ผš{0}ใ€‚ +workflow.run.rerun.http=้‡ๆ–ฐ่ฟ่กŒ HTTP {0}ใ€‚้’ฑๅธใ€‚ +workflow.run.rerun.failed=้‡ๆ–ฐ่ฟ่กŒๅคฑ่ดฅ๏ผš{0} +workflow.run.download.log.tooltip=ไฟๅญ˜ไฝœไธšๆ—ฅๅฟ— +workflow.run.download.artifacts.tooltip=ไธ‹่ฝฝๅทฅไปถ +workflow.run.download.noRun=่ฟ˜ๆฒกๆœ‰่ฟ่กŒ IDใ€‚ +workflow.run.download.log.requested=ๆญฃๅœจ่Žทๅ– {0} ็š„ๆ—ฅๅฟ—ใ€‚ +workflow.run.download.log.done=ไฟๅญ˜็š„ๆ—ฅๅฟ—๏ผš{0}ใ€‚ +workflow.run.download.artifacts.requested=่Žทๅ–ๆ–‡็‰ฉใ€‚ +workflow.run.download.artifacts.empty=ๆฒกๆœ‰ๆ–‡็‰ฉใ€‚ๅพฎๅฐ็š„่™š็ฉบใ€‚ +workflow.run.download.artifact.expired=็ฅžๅ™จๅทฒ่ฟ‡ๆœŸ๏ผš{0}ใ€‚ +workflow.run.download.artifact.done=ๅทฒไฟๅญ˜ๅทฅไปถ๏ผš{0} -> {1}ใ€‚ +workflow.run.download.failed=ไธ‹่ฝฝๅคฑ่ดฅ๏ผš{0} diff --git a/src/main/resources/messages/MyBundle.properties b/src/main/resources/messages/MyBundle.properties deleted file mode 100644 index 06fac1e..0000000 --- a/src/main/resources/messages/MyBundle.properties +++ /dev/null @@ -1,4 +0,0 @@ -name=HitHub Workflow Plugin -projectService=Project service: {0} -randomLabel=The random number is: {0} -shuffle=Shuffle diff --git a/src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java b/src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java new file mode 100644 index 0000000..7bb3f13 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java @@ -0,0 +1,85 @@ +package com.github.yunabraska.githubworkflow.helper; + +import com.sun.net.httpserver.HttpServer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FileDownloaderTest { + + private HttpServer server; + private String baseUrl; + + @Before + public void startServer() throws IOException { + server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + baseUrl = "http://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort(); + server.start(); + } + + @After + public void stopServer() { + if (server != null) { + server.stop(0); + } + } + + @Test + public void downloadSyncReadsSuccessfulResponseAndSendsHeaders() { + final AtomicReference userAgent = new AtomicReference<>(); + final AtomicReference clientName = new AtomicReference<>(); + server.createContext("/ok", exchange -> { + userAgent.set(exchange.getRequestHeaders().getFirst("User-Agent")); + clientName.set(exchange.getRequestHeaders().getFirst("Client-Name")); + final byte[] bytes = "hello".getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(200, bytes.length); + exchange.getResponseBody().write(bytes); + exchange.close(); + }); + + final String result = FileDownloader.downloadSync(baseUrl + "/ok", "JUnitAgent/1"); + + assertThat(result).isEqualTo("hello" + System.lineSeparator()); + assertThat(userAgent).hasValue("JUnitAgent/1"); + assertThat(clientName).hasValue("GitHub Workflow Plugin"); + } + + @Test + public void downloadSyncReturnsEmptyStringForHttpFailures() { + server.createContext("/missing", exchange -> { + exchange.sendResponseHeaders(404, -1); + exchange.close(); + }); + + assertThat(FileDownloader.downloadSync(baseUrl + "/missing", "JUnitAgent/1")).isEmpty(); + } + + @Test + public void downloadSyncReturnsEmptyStringForSlowResponses() { + server.createContext("/slow", exchange -> { + try { + Thread.sleep(1500); + exchange.sendResponseHeaders(200, -1); + } catch (final InterruptedException interrupted) { + Thread.currentThread().interrupt(); + } finally { + exchange.close(); + } + }); + + assertThat(FileDownloader.downloadSync(baseUrl + "/slow", "JUnitAgent/1")).isEmpty(); + } + + @Test + public void downloadSyncReturnsEmptyStringForInvalidUrls() { + assertThat(FileDownloader.downloadSync("://not-a-url", "JUnitAgent/1")).isEmpty(); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfigTest.java b/src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfigTest.java new file mode 100644 index 0000000..302adc5 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfigTest.java @@ -0,0 +1,158 @@ +package com.github.yunabraska.githubworkflow.helper; + +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.DEFAULT_VALUE_MAP; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_ENVS; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_GITHUB; +import static com.github.yunabraska.githubworkflow.helper.GitHubWorkflowConfig.FIELD_RUNNER; +import static org.assertj.core.api.Assertions.assertThat; + +public class GitHubWorkflowConfigTest { + + @Test + public void githubContextContainsCurrentDocumentedKeys() { + assertThat(DEFAULT_VALUE_MAP.get(FIELD_GITHUB).get().keySet()) + .containsAll(List.of( + "action", + "action_path", + "action_ref", + "action_repository", + "action_status", + "actor", + "actor_id", + "api_url", + "base_ref", + "env", + "event", + "event_name", + "event_path", + "graphql_url", + "head_ref", + "job", + "path", + "ref", + "ref_name", + "ref_protected", + "ref_type", + "repository", + "repository_id", + "repository_owner", + "repository_owner_id", + "repositoryUrl", + "retention_days", + "run_id", + "run_number", + "run_attempt", + "secret_source", + "server_url", + "sha", + "token", + "triggering_actor", + "workflow", + "workflow_ref", + "workflow_sha", + "workspace" + )); + } + + @Test + public void githubContextMatchesGeneratedDocsSnapshot() throws Exception { + assertThat(DEFAULT_VALUE_MAP.get(FIELD_GITHUB).get().keySet()) + .containsExactlyElementsOf(resourceKeys("/github-docs/github-context.tsv")); + } + + @Test + public void defaultEnvironmentVariablesContainCurrentDocumentedKeys() { + assertThat(DEFAULT_VALUE_MAP.get(FIELD_ENVS).get().keySet()) + .containsAll(List.of( + "CI", + "GITHUB_ACTION", + "GITHUB_ACTION_PATH", + "GITHUB_ACTION_REPOSITORY", + "GITHUB_ACTIONS", + "GITHUB_ACTOR", + "GITHUB_ACTOR_ID", + "GITHUB_API_URL", + "GITHUB_BASE_REF", + "GITHUB_ENV", + "GITHUB_EVENT_NAME", + "GITHUB_EVENT_PATH", + "GITHUB_GRAPHQL_URL", + "GITHUB_HEAD_REF", + "GITHUB_JOB", + "GITHUB_OUTPUT", + "GITHUB_PATH", + "GITHUB_REF", + "GITHUB_REF_NAME", + "GITHUB_REF_PROTECTED", + "GITHUB_REF_TYPE", + "GITHUB_REPOSITORY", + "GITHUB_REPOSITORY_ID", + "GITHUB_REPOSITORY_OWNER", + "GITHUB_REPOSITORY_OWNER_ID", + "GITHUB_RETENTION_DAYS", + "GITHUB_RUN_ATTEMPT", + "GITHUB_RUN_ID", + "GITHUB_RUN_NUMBER", + "GITHUB_SERVER_URL", + "GITHUB_SHA", + "GITHUB_STEP_SUMMARY", + "GITHUB_TRIGGERING_ACTOR", + "GITHUB_WORKFLOW", + "GITHUB_WORKFLOW_REF", + "GITHUB_WORKFLOW_SHA", + "GITHUB_WORKSPACE", + "RUNNER_ARCH", + "RUNNER_DEBUG", + "RUNNER_ENVIRONMENT", + "RUNNER_NAME", + "RUNNER_OS", + "RUNNER_TEMP", + "RUNNER_TOOL_CACHE" + )); + } + + @Test + public void defaultEnvironmentVariablesMatchGeneratedDocsSnapshot() throws Exception { + assertThat(DEFAULT_VALUE_MAP.get(FIELD_ENVS).get().keySet()) + .containsExactlyElementsOf(resourceKeys("/github-docs/default-env.tsv")); + } + + @Test + public void refProtectionDescriptionsMentionRulesets() { + assertThat(DEFAULT_VALUE_MAP.get(FIELD_GITHUB).get().get("ref_protected")) + .contains("rulesets"); + assertThat(DEFAULT_VALUE_MAP.get(FIELD_ENVS).get().get("GITHUB_REF_PROTECTED")) + .contains("rulesets"); + } + + @Test + public void runnerDebugDescriptionMatchesDocumentedMeaning() { + final Map runnerItems = DEFAULT_VALUE_MAP.get(FIELD_RUNNER).get(); + + assertThat(runnerItems.get("debug")) + .contains("debug logging") + .contains("1") + .doesNotContain("preinstalled tools"); + } + + private static List resourceKeys(final String path) throws Exception { + try (InputStream stream = Objects.requireNonNull(GitHubWorkflowConfigTest.class.getResourceAsStream(path)); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + return reader.lines() + .filter(line -> !line.isBlank()) + .filter(line -> !line.startsWith("#")) + .map(line -> line.split("\t", 2)[0]) + .toList(); + } + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelperTest.java b/src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelperTest.java new file mode 100644 index 0000000..7b90002 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelperTest.java @@ -0,0 +1,50 @@ +package com.github.yunabraska.githubworkflow.helper; + +import org.junit.Test; + +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GitHubWorkflowHelperTest { + + @Test + public void detectsWorkflowFilesOnlyUnderGithubWorkflowsDirectory() { + assertThat(GitHubWorkflowHelper.isWorkflowFile(Path.of("repo", ".github", "workflows", "build.yml"))).isTrue(); + assertThat(GitHubWorkflowHelper.isWorkflowFile(Path.of("repo", ".github", "workflows", "build.yaml"))).isTrue(); + assertThat(GitHubWorkflowHelper.isWorkflowFile(Path.of("repo", ".gitea", "workflows", "build.yml"))).isTrue(); + assertThat(GitHubWorkflowHelper.isWorkflowFile(Path.of("repo", ".github", "not-workflows", "build.yml"))).isFalse(); + assertThat(GitHubWorkflowHelper.isWorkflowFile(Path.of("repo", "workflows", "build.yml"))).isFalse(); + } + + @Test + public void invalidVirtualFilePathTextIsRejectedWithoutThrowing() { + assertThat(PsiElementHelper.toPath("<36ba1c43-b8f1-4f54-ace0-cef443d1e8f0>/etc/php/8.1/apache2/php.ini")).isEmpty(); + } + + @Test + public void serializedVirtualFilePathTextIsRejectedWithoutThrowing() { + assertThat(PsiElementHelper.toPath("{\"sessionId\":\"2cc03ab1-37d6-47cd-980d-1bb135073b4d\"}")).isEmpty(); + } + + @Test + public void detectsActionMetadataFilesByName() { + assertThat(GitHubWorkflowHelper.isActionFile(Path.of("repo", "action.yml"))).isTrue(); + assertThat(GitHubWorkflowHelper.isActionFile(Path.of("repo", "nested", "ACTION.YAML"))).isTrue(); + assertThat(GitHubWorkflowHelper.isActionFile(Path.of("repo", "workflow.yml"))).isFalse(); + } + + @Test + public void detectsSchemaTargetFiles() { + assertThat(GitHubWorkflowHelper.isDependabotFile(Path.of("repo", ".github", "dependabot.yml"))).isTrue(); + assertThat(GitHubWorkflowHelper.isFoundingFile(Path.of("repo", ".github", "FUNDING.yml"))).isTrue(); + assertThat(GitHubWorkflowHelper.isIssueForms(Path.of("repo", ".github", "ISSUE_TEMPLATE", "bug.yml"))).isTrue(); + assertThat(GitHubWorkflowHelper.isDiscussionFile(Path.of("repo", ".github", "DISCUSSION_TEMPLATE", "question.yaml"))).isTrue(); + } + + @Test + public void rejectsIssueConfigOutsideIssueTemplateDirectory() { + assertThat(GitHubWorkflowHelper.isIssueConfigFile(Path.of("repo", ".github", "ISSUE_TEMPLATE", "config.yml"))).isTrue(); + assertThat(GitHubWorkflowHelper.isIssueConfigFile(Path.of("repo", ".github", "workflow-templates", "config.yml"))).isFalse(); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java new file mode 100644 index 0000000..eeef4a8 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java @@ -0,0 +1,104 @@ +package com.github.yunabraska.githubworkflow.model; + +import org.junit.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GitHubActionTest { + + @Test + public void createGithubActionBuildsRemoteActionUrls() { + final GitHubAction action = GitHubAction.createGithubAction(false, "actions/setup-java@v4", "actions/setup-java@v4"); + + assertThat(action.name()).isEqualTo("actions/setup-java"); + assertThat(action.usesValue()).isEqualTo("actions/setup-java@v4"); + assertThat(action.downloadUrl()).isEqualTo("https://raw.githubusercontent.com/actions/setup-java/v4/action.yml"); + assertThat(action.githubUrl()).isEqualTo("https://github.com/actions/setup-java/tree/v4#readme"); + assertThat(action.isLocal()).isFalse(); + assertThat(action.isAction()).isTrue(); + assertThat(action.isResolved()).isFalse(); + } + + @Test + public void createGithubActionBuildsNestedRemoteActionUrls() { + final GitHubAction action = GitHubAction.createGithubAction(false, "owner/repo/path/to/action@main", "owner/repo/path/to/action@main"); + + assertThat(action.name()).isEqualTo("owner/repo/path/to/action"); + assertThat(action.downloadUrl()).isEqualTo("https://raw.githubusercontent.com/owner/repo/main/path/to/action/action.yml"); + assertThat(action.githubUrl()).isEqualTo("https://github.com/owner/repo/tree/main/path/to/action#readme"); + assertThat(action.isAction()).isTrue(); + } + + @Test + public void createGithubActionKeepsNestedRemoteActionNameForIssue48() { + final GitHubAction action = GitHubAction.createGithubAction(false, "github/codeql-action/init@v2", "github/codeql-action/init@v2"); + + assertThat(action.name()).isEqualTo("github/codeql-action/init"); + assertThat(action.downloadUrl()).isEqualTo("https://raw.githubusercontent.com/github/codeql-action/v2/init/action.yml"); + assertThat(action.githubUrl()).isEqualTo("https://github.com/github/codeql-action/tree/v2/init#readme"); + assertThat(action.isAction()).isTrue(); + } + + @Test + public void createGithubActionBuildsReusableWorkflowUrls() { + final GitHubAction action = GitHubAction.createGithubAction(false, "owner/repo/.github/workflows/reuse.yml@main", "owner/repo/.github/workflows/reuse.yml@main"); + + assertThat(action.name()).isEqualTo("owner/repo"); + assertThat(action.downloadUrl()).isEqualTo("https://raw.githubusercontent.com/owner/repo/main/.github/workflows/reuse.yml"); + assertThat(action.githubUrl()).isEqualTo("https://github.com/owner/repo/blob/main/.github/workflows/reuse.yml"); + assertThat(action.isAction()).isFalse(); + } + + @Test + public void createGithubActionTreatsLocalWorkflowFileAsReusableWorkflow() { + final GitHubAction action = GitHubAction.createGithubAction(true, "./.github/workflows/reusable.yml", "/tmp/project/.github/workflows/reusable.yml"); + + assertThat(action.isLocal()).isTrue(); + assertThat(action.isAction()).isFalse(); + } + + @Test + public void createGithubActionTreatsLocalActionDirectoryAsAction() { + final GitHubAction action = GitHubAction.createGithubAction(true, "./.github/actions/local", "/tmp/project/.github/actions/local/action.yml"); + + assertThat(action.isLocal()).isTrue(); + assertThat(action.isAction()).isTrue(); + } + + @Test + public void settersIgnoreNullMapsAndKeepFluentApi() { + final GitHubAction action = new GitHubAction() + .setInputs(null) + .setOutputs(null) + .setSecrets(null) + .setMetaData(null) + .setInputs(Map.of("input", "description")) + .setOutputs(Map.of("output", "description")) + .setSecrets(Map.of("secret", "description")) + .setMetaData(Map.of("name", "demo", "ignoredInputs", "manual-input", "ignoredOutputs", "manual-output")); + + assertThat(action.getInputs()).containsEntry("input", "description"); + assertThat(action.getOutputs()).containsEntry("output", "description"); + assertThat(action.getSecrets()).containsEntry("secret", "description"); + assertThat(action.freshSecrets()).containsEntry("secret", "description"); + assertThat(action.name()).isEqualTo("demo"); + assertThat(action.ignoredInputs()).contains("manual-input"); + assertThat(action.ignoredOutputs()).contains("manual-output"); + } + + @Test + public void suppressedItemsCanBeRemovedAgain() { + final GitHubAction action = new GitHubAction() + .suppressInput("manual-input", true) + .suppressOutput("manual-output", true); + + action.suppressInput("manual-input", false); + action.suppressOutput("manual-output", false); + + assertThat(action.ignoredInputs()).doesNotContain("manual-input"); + assertThat(action.ignoredOutputs()).doesNotContain("manual-output"); + assertThat(action.getMetaData()).containsEntry("ignoredInputs", "").containsEntry("ignoredOutputs", ""); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/DownloadSchemasTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/DownloadSchemasTest.java deleted file mode 100644 index efe2aac..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/DownloadSchemasTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.github.yunabraska.githubworkflow.services; - -import org.junit.Test; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.List; - -import static com.github.yunabraska.githubworkflow.helper.FileDownloader.downloadSync; - -public class DownloadSchemasTest { - - @Test - public void downloadSchemas() throws IOException { - final List schemaNames = Arrays.asList( - "dependabot-2.0", - "github-action", - "github-funding", - "github-workflow", - "github-discussion", - "github-issue-forms", - "github-issue-config", - "github-workflow-template-properties" - ); - - final Path directory = Path.of(System.getProperty("user.dir"), "src", "main", "resources", "schemas"); - if (!Files.exists(directory) || !Files.isDirectory(directory)) { - Files.createDirectories(directory); - } - - for (final String schemaName : schemaNames) { - final String schemaContent = downloadSync("https://json.schemastore.org/" + schemaName, "JetBrains GithubWorkflow"); - Files.writeString(Path.of(directory.toFile().getAbsolutePath(), schemaName + ".json"), schemaContent, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - System.out.println("Saved " + schemaName); - System.out.println("Failed to fetch " + schemaName); - } - } -} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java b/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java new file mode 100644 index 0000000..f1566fd --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java @@ -0,0 +1,240 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.ActionUiKind; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.editor.colors.TextAttributesKey; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; +import org.jetbrains.yaml.YAMLFileType; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static com.github.yunabraska.githubworkflow.services.GitHubActionCache.getActionCache; + +abstract class EditorFeatureTestCase extends BasePlatformTestCase { + + @Override + protected void setUp() throws Exception { + super.setUp(); + getActionCache().getState().actions.clear(); + RemoteServerSettings.getInstance().setCustomServers(List.of()); + ((CodeInsightTestFixtureImpl) myFixture).canChangeDocumentDuringHighlighting(true); + } + + @Override + protected void tearDown() throws Exception { + try { + getActionCache().getState().actions.clear(); + RemoteServerSettings.getInstance().setCustomServers(List.of()); + } finally { + super.tearDown(); + } + } + + protected static GitHubAction seedRemoteAction(final String usesValue, final Map inputs, final Map outputs) { + return seedRemoteAction(usesValue, inputs, outputs, Map.of()); + } + + protected static GitHubAction seedRemoteAction(final String usesValue, final Map inputs, final Map outputs, final Map secrets) { + final GitHubAction action = GitHubAction.createGithubAction(false, usesValue, usesValue) + .isResolved(true) + .setInputs(inputs) + .setOutputs(outputs) + .setSecrets(secrets); + getActionCache().getState().actions.put(usesValue, action); + return action; + } + + protected static GitHubAction seedLocalAction(final String usesValue, final PsiFile actionFile) { + final GitHubAction action = GitHubAction.createGithubAction(true, usesValue, actionFile.getVirtualFile().getPath()) + .isResolved(true); + getActionCache().getState().actions.put(usesValue, action); + getActionCache().getState().actions.put(actionFile.getVirtualFile().getPath(), action); + return action; + } + + protected final void assertWorkflowHighlights(final String text) { + configureWorkflow(text); + myFixture.checkHighlighting(true, false, true); + } + + protected final void configureWorkflow(final String text) { + myFixture.configureByText(YAMLFileType.YML, text); + } + + protected final void configureWorkflowProjectFile(final String text) { + final int caretOffset = text.indexOf(""); + final String fileText = text.replace("", ""); + myFixture.addFileToProject(".github/workflows/workflow.yml", fileText); + myFixture.configureFromTempProjectFile(".github/workflows/workflow.yml"); + if (caretOffset >= 0) { + myFixture.getEditor().getCaretModel().moveToOffset(caretOffset); + } + } + + protected final PsiReference referenceAtCaret() { + return myFixture.getReferenceAtCaretPositionWithAssertion(); + } + + protected final List completeWorkflow(final String text) { + configureWorkflow(text); + return completeBasicLookupStrings(); + } + + protected final List completeBasicLookupStrings() { + final LookupElement[] elements = myFixture.completeBasic(); + return elements == null ? List.of() : Arrays.stream(elements) + .map(LookupElement::getLookupString) + .toList(); + } + + protected final void assertHighlightedReferenceAtCurrentCaret() { + assertTextAttributeAtCurrentCaret(DefaultLanguageHighlighterColors.HIGHLIGHTED_REFERENCE); + } + + protected final void assertTextAttributeAtCurrentCaret(final TextAttributesKey key) { + final int offset = myFixture.getCaretOffset(); + final List highlights = myFixture.doHighlighting(); + final boolean found = highlights.stream() + .anyMatch(info -> containsOffset(info, offset) + && key.equals(info.forcedTextAttributesKey)); + if (!found) { + final String nearby = highlights.stream() + .filter(info -> info.forcedTextAttributesKey != null) + .filter(info -> Math.abs(info.startOffset - offset) < 80 || Math.abs(info.endOffset - offset) < 80) + .map(info -> info.forcedTextAttributesKey + "@" + info.startOffset + ".." + info.endOffset) + .distinct() + .toList() + .toString(); + throw new AssertionError("Missing text attribute [" + key + "] at caret offset [" + offset + "], nearby " + nearby); + } + } + + protected final void assertNoTextAttributeAtCurrentCaret(final TextAttributesKey key) { + final int offset = myFixture.getCaretOffset(); + final List highlights = myFixture.doHighlighting(); + final List found = highlights.stream() + .filter(info -> containsOffset(info, offset)) + .filter(info -> key.equals(info.forcedTextAttributesKey)) + .map(info -> info.forcedTextAttributesKey + "@" + info.startOffset + ".." + info.endOffset) + .toList(); + if (!found.isEmpty()) { + throw new AssertionError("Unexpected text attribute [" + key + "] at caret offset [" + offset + "], found " + found); + } + } + + protected final void clickGutterActionContaining(final String text) { + final List tooltips = myFixture.doHighlighting().stream() + .map(HighlightInfo::getGutterIconRenderer) + .filter(GutterIconRenderer.class::isInstance) + .map(GutterIconRenderer.class::cast) + .map(GutterIconRenderer::getTooltipText) + .filter(tooltip -> tooltip != null) + .toList(); + final GutterIconRenderer renderer = myFixture.doHighlighting().stream() + .map(HighlightInfo::getGutterIconRenderer) + .filter(GutterIconRenderer.class::isInstance) + .map(GutterIconRenderer.class::cast) + .filter(gutter -> gutter.getTooltipText() != null) + .filter(gutter -> gutter.getTooltipText().contains(text)) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing gutter action containing [" + text + "], found " + tooltips)); + final AnAction action = renderer.getClickAction(); + final AnAction resolvedAction = action == null ? popupActionContaining(renderer, text) : action; + if (resolvedAction == null) { + throw new AssertionError("Gutter action has no click action [" + text + "]"); + } + resolvedAction.actionPerformed(AnActionEvent.createEvent( + resolvedAction, + DataContext.EMPTY_CONTEXT, + new Presentation(), + "GithubWorkflowPluginTest", + ActionUiKind.NONE, + null + )); + } + + protected final List gutterIcons() { + return myFixture.doHighlighting().stream() + .map(HighlightInfo::getGutterIconRenderer) + .filter(GutterIconRenderer.class::isInstance) + .map(GutterIconRenderer.class::cast) + .toList(); + } + + private static AnAction popupActionContaining(final GutterIconRenderer renderer, final String text) { + final ActionGroup group = renderer.getPopupMenuActions(); + if (group == null) { + return null; + } + return Arrays.stream(group.getChildren(null)) + .filter(action -> action.getTemplatePresentation().getText() != null) + .filter(action -> action.getTemplatePresentation().getText().contains(text)) + .findFirst() + .orElse(null); + } + + protected final void invokeHighlightFixContaining(final String text) { + final IntentionAction action = myFixture.doHighlighting().stream() + .map(info -> info.findRegisteredQuickFix((descriptor, range) -> descriptor.getAction().getText().contains(text) ? descriptor.getAction() : null)) + .filter(fix -> fix != null) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing highlight fix containing [" + text + "]")); + try { + action.invoke(getProject(), myFixture.getEditor(), myFixture.getFile()); + } catch (final Exception exception) { + throw new AssertionError("Highlight fix failed [" + text + "]", exception); + } + } + + protected final List quickFixTexts(final String text) { + configureWorkflow(text); + myFixture.doHighlighting(); + return allIntentions().stream() + .map(IntentionAction::getText) + .distinct() + .toList(); + } + + protected final String applyQuickFix(final String text, final String actionText) { + myFixture.addFileToProject(".github/workflows/quickfix.yml", text); + myFixture.configureFromTempProjectFile(".github/workflows/quickfix.yml"); + myFixture.doHighlighting(); + final IntentionAction action = allIntentions().stream() + .filter(quickFix -> actionText.equals(quickFix.getText())) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing quick fix [" + actionText + "]")); + try { + action.invoke(getProject(), myFixture.getEditor(), myFixture.getFile()); + } catch (final Exception exception) { + throw new AssertionError("Quick fix failed [" + actionText + "]", exception); + } + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + return myFixture.getEditor().getDocument().getText(); + } + + protected final List allIntentions() { + return Stream.concat(myFixture.getAllQuickFixes().stream(), myFixture.getAvailableIntentions().stream()) + .toList(); + } + + private static boolean containsOffset(final HighlightInfo info, final int offset) { + return info.startOffset <= offset && offset < info.endOffset; + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/FakeRemoteServer.java b/src/test/java/com/github/yunabraska/githubworkflow/services/FakeRemoteServer.java new file mode 100644 index 0000000..73ffd19 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/FakeRemoteServer.java @@ -0,0 +1,162 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.sun.net.httpserver.HttpServer; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +final class FakeRemoteServer implements AutoCloseable { + + private final HttpServer server; + private final Map contents = new HashMap<>(); + private final Map> branches = new HashMap<>(); + private final Map> tags = new HashMap<>(); + private final Map> repositories = new HashMap<>(); + private final List requests = new ArrayList<>(); + + FakeRemoteServer() throws IOException { + server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + server.createContext("/", exchange -> { + final URI uri = exchange.getRequestURI(); + final String request = uri.getPath() + (uri.getQuery() == null ? "" : "?" + uri.getQuery()); + requests.add(request); + final Response response = responseFor(uri); + final byte[] bytes = response.body().getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().set("Content-Type", "application/json"); + exchange.sendResponseHeaders(response.status(), bytes.length); + exchange.getResponseBody().write(bytes); + exchange.close(); + }); + server.start(); + } + + String webUrl() { + return "http://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort(); + } + + String apiUrl(final String prefix) { + return webUrl() + prefix; + } + + List requests() { + return List.copyOf(requests); + } + + void addContent(final String owner, final String repo, final String path, final String ref, final String content) { + contents.put(key(owner, repo, path, ref), content); + } + + void setBranches(final String owner, final String repo, final List values) { + branches.put(owner + "/" + repo, values); + } + + void setTags(final String owner, final String repo, final List values) { + tags.put(owner + "/" + repo, values); + } + + void setRepositories(final String owner, final Map values) { + repositories.put(owner, values); + } + + @Override + public void close() { + server.stop(0); + } + + private Response responseFor(final URI uri) { + final String[] parts = uri.getPath().split("/"); + final int reposIndex = indexOf(parts, "repos"); + final int usersIndex = indexOf(parts, "users"); + final int orgsIndex = indexOf(parts, "orgs"); + if (usersIndex >= 0 && parts.length > usersIndex + 2 && "repos".equals(parts[usersIndex + 2])) { + return new Response(200, repositories(parts[usersIndex + 1], repositories.getOrDefault(parts[usersIndex + 1], Map.of()))); + } + if (orgsIndex >= 0 && parts.length > orgsIndex + 2 && "repos".equals(parts[orgsIndex + 2])) { + return new Response(200, repositories(parts[orgsIndex + 1], repositories.getOrDefault(parts[orgsIndex + 1], Map.of()))); + } + if (reposIndex < 0 || parts.length <= reposIndex + 3) { + return new Response(404, "{}"); + } + final String owner = parts[reposIndex + 1]; + final String repo = parts[reposIndex + 2]; + final String endpoint = parts[reposIndex + 3]; + if ("contents".equals(endpoint)) { + final String path = tail(parts, reposIndex + 4); + final String ref = ref(uri); + final String content = contents.get(key(owner, repo, path, ref)); + if (content == null) { + return new Response(404, "{}"); + } + final String encoded = Base64.getMimeEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8)); + return new Response(200, "{\"content\":\"" + encoded + "\",\"encoding\":\"base64\",\"download_url\":\"" + webUrl() + "/raw/" + path + "\"}"); + } + if ("branches".equals(endpoint)) { + return new Response(200, names(branches.getOrDefault(owner + "/" + repo, List.of()))); + } + if ("tags".equals(endpoint)) { + return new Response(200, names(tags.getOrDefault(owner + "/" + repo, List.of()))); + } + return new Response(404, "{}"); + } + + private static int indexOf(final String[] parts, final String value) { + for (int index = 0; index < parts.length; index++) { + if (value.equals(parts[index])) { + return index; + } + } + return -1; + } + + private static String tail(final String[] parts, final int start) { + final List result = new ArrayList<>(); + for (int index = start; index < parts.length; index++) { + result.add(parts[index]); + } + return String.join("/", result); + } + + private static String ref(final URI uri) { + final String query = uri.getQuery(); + if (query == null) { + return ""; + } + for (final String part : query.split("&")) { + if (part.startsWith("ref=")) { + return part.substring("ref=".length()); + } + } + return ""; + } + + private static String key(final String owner, final String repo, final String path, final String ref) { + return owner + "/" + repo + "/" + path + "@" + ref; + } + + private static String names(final List names) { + return names.stream() + .map(name -> "{\"name\":\"" + name + "\"}") + .reduce((left, right) -> left + "," + right) + .map(value -> "[" + value + "]") + .orElse("[]"); + } + + private static String repositories(final String owner, final Map repositories) { + return repositories.entrySet().stream() + .map(entry -> "{\"name\":\"" + entry.getKey() + "\",\"full_name\":\"" + owner + "/" + entry.getKey() + "\",\"description\":\"" + entry.getValue() + "\"}") + .reduce((left, right) -> left + "," + right) + .map(value -> "[" + value + "]") + .orElse("[]"); + } + + private record Response(int status, String body) { + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubActionCacheTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubActionCacheTest.java index 5a733be..1ea9629 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubActionCacheTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubActionCacheTest.java @@ -1,64 +1,219 @@ package com.github.yunabraska.githubworkflow.services; import com.github.yunabraska.githubworkflow.model.GitHubAction; -import com.intellij.openapi.project.Project; import com.intellij.testFramework.fixtures.BasePlatformTestCase; -import org.junit.Test; -import static com.github.yunabraska.githubworkflow.services.GitHubActionCache.getActionCache; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + import static org.assertj.core.api.Assertions.assertThat; public class GitHubActionCacheTest extends BasePlatformTestCase { - @Test - public void testSerializationAndDeserialization() throws InterruptedException { + public void testLocalActionSerializationAndDeserialization() throws IOException { // GIVEN - final Project project = getProject(); - final GitHubActionCache originalCache = getActionCache(); - originalCache.get(project, "actions/checkout@main"); - originalCache.get(project, "actions/setup-python@main"); - final GitHubAction javaAction = originalCache.get(project, "actions/setup-java@main").resolve(); + final String actionPath = localActionPath(); + final GitHubActionCache originalCache = new GitHubActionCache(); + final GitHubAction javaAction = originalCache.get(null, actionPath).resolve(); // THEN EXPECT - validateResolvedJavaAction(javaAction); - assertThat(javaAction.expiryTime()).isEqualTo(getActionCache().get(project, "actions/setup-java@main").expiryTime()); + validateResolvedLocalAction(javaAction, actionPath); + assertThat(originalCache.get(null, actionPath).expiryTime()).isEqualTo(javaAction.expiryTime()); // WHEN SERIALIZATION - Thread.sleep(25); final GitHubActionCache.State serializedState = originalCache.getState(); // WHEN DESERIALIZATION - Thread.sleep(25); final GitHubActionCache newCache = new GitHubActionCache(); assertThat(newCache.getState().actions).isEmpty(); newCache.loadState(serializedState); assertThat(newCache.getState().actions).isNotEmpty(); - // THEN EXPECT assertThat(newCache).isNotEqualTo(originalCache); - final GitHubAction newJavaAction = newCache.get(project, "actions/setup-java@main"); - validateResolvedJavaAction(newJavaAction); - assertThat(newJavaAction.expiryTime()).isEqualTo(originalCache.get(project, "actions/setup-java@main").expiryTime()); - assertThat(newJavaAction.isResolved()).isEqualTo(originalCache.get(project, "actions/setup-java@main").isResolved()); - - // WHEN RELOAD - Thread.sleep(25); - final GitHubAction reloadedAction = originalCache.reloadAsync(project, "actions/setup-java@main"); - Thread.sleep(1000); - validateResolvedJavaAction(reloadedAction); - assertThat(reloadedAction.expiryTime()).isNotEqualTo(javaAction.expiryTime()); + final GitHubAction newJavaAction = newCache.get(null, actionPath); + validateResolvedLocalAction(newJavaAction, actionPath); + assertThat(newJavaAction.expiryTime()).isEqualTo(originalCache.get(null, actionPath).expiryTime()); + assertThat(newJavaAction.isResolved()).isEqualTo(originalCache.get(null, actionPath).isResolved()); + } + + public void testExpiredLocalActionKeepsManualSuppressions() throws IOException { + // GIVEN + final String actionPath = localActionPath(); + final GitHubActionCache cache = new GitHubActionCache(); + final GitHubAction action = cache.get(null, actionPath).resolve(); + action.suppressInput("manual-input", true); + action.suppressOutput("manual-output", true); + action.expiryTime(0); + + // WHEN + final GitHubAction refreshed = cache.get(null, actionPath); + + // THEN + assertThat(refreshed).isNotSameAs(action); + assertThat(refreshed.ignoredInputs()).contains("manual-input"); + assertThat(refreshed.ignoredOutputs()).contains("manual-output"); + assertThat(refreshed.expiryTime()).isGreaterThan(System.currentTimeMillis()); + } + + public void testSummaryCountsDistinctResolvedRemoteAndExpiredActions() throws IOException { + final GitHubActionCache cache = new GitHubActionCache(); + final GitHubAction remoteAction = GitHubAction.createGithubAction(false, "actions/checkout@v4", "actions/checkout@v4") + .isResolved(true) + .expiryTime(0); + final GitHubAction localAction = GitHubAction.createGithubAction(true, localActionPath(), localActionPath()) + .isResolved(false) + .expiryTime(System.currentTimeMillis() + 60_000); + + cache.getState().actions.put("actions/checkout@v4", remoteAction); + cache.getState().actions.put("duplicate-actions/checkout@v4", remoteAction); + cache.getState().actions.put("local", localAction); + + final GitHubActionCache.CacheSummary summary = cache.summary(); + + assertThat(summary.total()).isEqualTo(2); + assertThat(summary.resolved()).isEqualTo(1); + assertThat(summary.remote()).isEqualTo(1); + assertThat(summary.expired()).isEqualTo(1); + assertThat(summary.suppressed()).isZero(); + } + + public void testClearRemovesCachedActionsAndReturnsEmptySummary() throws IOException { + final GitHubActionCache cache = new GitHubActionCache(); + final GitHubAction action = GitHubAction.createGithubAction(true, localActionPath(), localActionPath()) + .isResolved(true); + cache.getState().actions.put(action.usesValue(), action); + + final GitHubActionCache.CacheSummary summary = cache.clear(); + + assertThat(summary.total()).isZero(); + assertThat(summary.resolved()).isZero(); + assertThat(summary.remote()).isZero(); + assertThat(summary.expired()).isZero(); + assertThat(summary.suppressed()).isZero(); + assertThat(cache.getState().actions).isEmpty(); + } + + public void testEntriesRemoveAllAndPortableExportImportRoundTrip() throws IOException { + final GitHubActionCache cache = new GitHubActionCache(); + final GitHubAction action = GitHubAction.createGithubAction(false, "actions/checkout@v4", "actions/checkout@v4") + .displayName("Checkout") + .description("Checkout action") + .isResolved(true) + .setInputs(java.util.Map.of("fetch-depth", "Fetch depth")) + .setOutputs(java.util.Map.of("ref", "Checked out ref")); + cache.getState().actions.put("actions/checkout@v4", action); + + assertThat(cache.entries()) + .singleElement() + .satisfies(entry -> { + assertThat(entry.key()).isEqualTo("actions/checkout@v4"); + assertThat(entry.name()).isEqualTo("Checkout"); + assertThat(entry.local()).isFalse(); + assertThat(entry.resolved()).isTrue(); + }); + assertThat(cache.estimatedSizeBytes()).isPositive(); + + final Path export = Files.createTempFile("github-workflow-cache", ".txt"); + cache.exportCache(export); + + final GitHubActionCache imported = new GitHubActionCache(); + imported.importCache(export); + + assertThat(imported.getState().actions).containsKey("actions/checkout@v4"); + assertThat(imported.getState().actions.get("actions/checkout@v4").displayName()).isEqualTo("Checkout"); + assertThat(imported.getState().actions.get("actions/checkout@v4").getInputs()).containsEntry("fetch-depth", "Fetch depth"); + assertThat(imported.removeAll(java.util.List.of("actions/checkout@v4")).total()).isZero(); + } + + public void testRestoreWarningsClearsActionInputAndOutputSuppressions() throws IOException { + final GitHubActionCache cache = new GitHubActionCache(); + final GitHubAction action = GitHubAction.createGithubAction(true, localActionPath(), localActionPath()) + .isResolved(true) + .isSuppressed(true) + .suppressInput("manual-input", true) + .suppressOutput("manual-output", true); + cache.getState().actions.put(action.usesValue(), action); + + final long restored = cache.restoreWarnings(); + + assertThat(restored).isEqualTo(1); + assertThat(action.isSuppressed()).isFalse(); + assertThat(action.ignoredInputs()).isEmpty(); + assertThat(action.ignoredOutputs()).isEmpty(); + assertThat(cache.summary().suppressed()).isZero(); + } + + public void testLoadedEmptySuppressionMetadataDoesNotMarkActionSuppressed() { + final GitHubAction action = new GitHubAction().setMetaData(Map.of( + "usesValue", "actions/checkout@v4", + "isSuppressed", "false", + "ignoredInputs", "", + "ignoredOutputs", "" + )); + + assertThat(action.hasSuppressedWarnings()).isFalse(); + assertThat(action.ignoredInputs()).isEmpty(); + assertThat(action.ignoredOutputs()).isEmpty(); + } + + public void testRemoteActionWithoutRefUsesMainAndQueuesResolution() throws Exception { + final GitHubActionCache cache = new GitHubActionCache(); + final CountDownLatch resolved = new CountDownLatch(1); + final GitHubActionCache.ActionResolver previous = cache.useActionResolverForTests(action -> { + action.displayName("Checkout").isResolved(true); + resolved.countDown(); + return action; + }); + try { + final GitHubAction action = cache.get(getProject(), "actions/checkout"); + + assertThat(action.isLocal()).isFalse(); + assertThat(action.usesValue()).isEqualTo("actions/checkout@main"); + assertThat(action.isSuppressed()).isFalse(); + assertThat(cache.getState().actions).containsKey("actions/checkout@main"); + assertThat(cache.getState().actions).doesNotContainKey("actions/checkout"); + assertThat(resolved.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(action.isResolved()).isTrue(); + } finally { + cache.useActionResolverForTests(previous); + } + } + + private static String localActionPath() throws IOException { + final Path actionPath = Files.createTempDirectory("github-workflow-action").resolve("action.yml"); + Files.writeString(actionPath, """ + name: Local Action + inputs: + deep: + description: Deep input + outputs: + java_version: + description: Java version + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + return actionPath.toString(); } - private static void validateResolvedJavaAction(final GitHubAction javaAction) { + private static void validateResolvedLocalAction(final GitHubAction javaAction, final String actionPath) { assertThat(javaAction.getInputs()).isNotEmpty(); assertThat(javaAction.getOutputs()).isNotEmpty(); - assertThat(javaAction.isLocal()).isFalse(); + assertThat(javaAction.getInputs()).containsKey("deep"); + assertThat(javaAction.getOutputs()).containsKey("java_version"); + assertThat(javaAction.isLocal()).isTrue(); assertThat(javaAction.isAction()).isTrue(); assertThat(javaAction.isResolved()).isTrue(); - assertThat(javaAction.githubUrl()).isNotNull(); - assertThat(javaAction.name()).isEqualTo("actions/setup-java"); - assertThat(javaAction.downloadUrl()).isEqualTo("https://raw.githubusercontent.com/actions/setup-java/main/action.yml"); + assertThat(javaAction.githubUrl()).isEmpty(); + assertThat(javaAction.name()).isEqualTo(actionPath); + assertThat(javaAction.downloadUrl()).isEqualTo(actionPath); assertThat(javaAction.expiryTime()).isGreaterThan(System.currentTimeMillis()); } } diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizationsTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizationsTest.java new file mode 100644 index 0000000..c8a71f7 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizationsTest.java @@ -0,0 +1,53 @@ +package com.github.yunabraska.githubworkflow.services; + +import junit.framework.TestCase; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GitHubRequestAuthorizationsTest extends TestCase { + + public void testStandardEnvironmentTokensAreTriedBeforeAnonymous() { + final List authorizations = GitHubRequestAuthorizations.forApiUrl( + "https://api.example.test", + "", + null, + Map.of("GITHUB_TOKEN", "env-token") + ); + + assertThat(authorizations) + .extracting(GitHubRequestAuthorizations.Authorization::source) + .containsSubsequence("GITHUB_TOKEN", "anonymous"); + assertThat(authorizations) + .extracting(GitHubRequestAuthorizations.Authorization::authorizationHeader) + .contains("Bearer env-token"); + } + + public void testExplicitEnvironmentTokenIsTriedBeforeStandardEnvironmentTokens() { + final List authorizations = GitHubRequestAuthorizations.forApiUrl( + "https://github.acme.test/api/v3", + "ACME_GITHUB_TOKEN", + null, + Map.of("ACME_GITHUB_TOKEN", "enterprise-token", "GITHUB_TOKEN", "default-token") + ); + + assertThat(authorizations) + .extracting(GitHubRequestAuthorizations.Authorization::source) + .containsSubsequence("ACME_GITHUB_TOKEN", "GITHUB_TOKEN", "anonymous"); + } + + public void testMissingEnvironmentTokensFallBackToAnonymous() { + final List authorizations = GitHubRequestAuthorizations.forApiUrl( + "https://github.acme.test/api/v3", + "ACME_GITHUB_TOKEN", + null, + Map.of() + ); + + assertThat(authorizations) + .extracting(GitHubRequestAuthorizations.Authorization::source) + .containsExactly("anonymous"); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotatorTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotatorTest.java deleted file mode 100644 index 973bf3d..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotatorTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.yunabraska.githubworkflow.services; - -import com.intellij.testFramework.TestDataPath; -import com.intellij.testFramework.fixtures.BasePlatformTestCase; -import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; - -@TestDataPath("\\$CONTENT_ROOT/src/test/resources/testdata/.github") -public class HighlightAnnotatorTest extends BasePlatformTestCase { - @Override - protected String getBasePath() { - return "testdata/.github"; - } - - public void testHighlighting() throws InterruptedException { - // Not testable as it relies on valid project files - // myFixture.configureByText("action.yml", "my_action/action.yml"); - // myFixture.configureByFile("local_references.yml"); - // System.out.println("!!!! TestDwataPath" + LocalFileSystem.getInstance().findFileByIoFile(new File(myFixture.getTestDataPath() + "/local_references.yml"))); - // myFixture.testHighlighting(true, false, true, "local_references.yml"); - // final List availableIntentions = myFixture.getAvailableIntentions(); - // System.out.println("!!!! AvailableIntentions" + availableIntentions.size()); - ((CodeInsightTestFixtureImpl) myFixture).canChangeDocumentDuringHighlighting(true); - //FIXME: tests are not working anymore after more async processing -// myFixture.testHighlighting(true, false, true, "show_case.yml"); -// myFixture.testHighlighting(true, false, true, "issue_10.yml"); -// myFixture.testHighlighting(true, false, true, "issue_24.yml"); -// myFixture.testHighlighting(true, false, true, "issue_25.yml"); -// myFixture.testHighlighting(true, false, true, "issue_29.yml"); - } -} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java new file mode 100644 index 0000000..5ca16a9 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java @@ -0,0 +1,324 @@ +package com.github.yunabraska.githubworkflow.services; + +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LocalizationResourcesTest { + + private static final String BUNDLE_PATH = "messages/GitHubWorkflowBundle"; + private static final List LOCALE_SUFFIXES = List.of( + "ar", + "cs", + "de", + "es", + "fr", + "hi", + "id", + "it", + "ja", + "ko", + "nl", + "pl", + "pt_BR", + "ru", + "sv", + "th", + "tr", + "uk", + "vi", + "zh_CN" + ); + + @Test + public void testDefaultBundleReturnsActionCacheMessages() { + assertThat(GitHubWorkflowBundle.message("action.GitHubWorkflow.ClearActionCache.text")) + .isEqualTo("Clear Action Cache"); + assertThat(GitHubWorkflowBundle.message("notification.cache.cleared", 3)) + .isEqualTo("Cleared 3 cached GitHub Workflow entries."); + } + + @Test + public void testTopTwentyLocaleBundlesHaveTheSameKeysAsDefaultBundle() throws IOException { + final Properties defaultBundle = loadBundle(""); + + assertThat(LOCALE_SUFFIXES).hasSize(20); + for (final String suffix : LOCALE_SUFFIXES) { + final Properties localizedBundle = loadBundle("_" + suffix); + assertThat(localizedBundle.keySet()) + .as("Locale suffix [%s] has the same keys", suffix) + .containsExactlyInAnyOrderElementsOf(defaultBundle.keySet()); + } + } + + @Test + public void testLocaleBundleValuesAreNotBlank() throws IOException { + for (final String suffix : LOCALE_SUFFIXES) { + final Properties bundle = loadBundle("_" + suffix); + assertThat(bundle.stringPropertyNames()) + .as("Locale suffix [%s] contains keys", suffix) + .isNotEmpty() + .allSatisfy(key -> assertThat(bundle.getProperty(key)) + .as("Locale suffix [%s] key [%s]", suffix, key) + .isNotBlank()); + } + } + + @Test + public void testLocaleBundleValuesAreTranslatedAndKeepPlaceholders() throws IOException { + final Properties defaultBundle = loadBundle(""); + final Set technicalKeysAllowedToMatchEnglish = Set.of( + "workflow.run.field.apiUrl", + "workflow.run.field.ref", + "workflow.run.auth.settings", + "workflow.log.error", + "workflow.run.status", + "workflow.run.job.status", + "workflow.run.job.url", + "workflow.run.state.ok", + "error.report.ide", + "error.report.os", + "error.report.stacktrace", + "completion.shell.powershell" + ); + final Pattern placeholder = Pattern.compile("\\{\\d+\\}"); + final Pattern leakedTranslationToken = Pattern.compile("XG[A-Z]*[HT]\\d+X|QZQARG\\d+QZQ", Pattern.CASE_INSENSITIVE); + for (final String suffix : LOCALE_SUFFIXES) { + final Properties localizedBundle = loadBundle("_" + suffix); + for (final String key : defaultBundle.stringPropertyNames()) { + final String localized = localizedBundle.getProperty(key); + assertThat(localized) + .as("Locale suffix [%s] key [%s] is real text", suffix, key) + .doesNotContain("รƒ") + .doesNotContain("ร‚") + .doesNotContain("๏ฟฝ"); + assertThat(leakedTranslationToken.matcher(localized).find()) + .as("Locale suffix [%s] key [%s] has no leaked placeholder token", suffix, key) + .isFalse(); + assertThat(placeholder.matcher(localized).results().map(match -> match.group()).toList()) + .as("Locale suffix [%s] key [%s] keeps placeholders", suffix, key) + .containsExactlyInAnyOrderElementsOf(placeholder.matcher(defaultBundle.getProperty(key)).results().map(match -> match.group()).toList()); + if (!technicalKeysAllowedToMatchEnglish.contains(key)) { + assertThat(localized) + .as("Locale suffix [%s] key [%s] is not the default English fallback", suffix, key) + .isNotEqualTo(defaultBundle.getProperty(key)); + } + } + } + } + + @Test + public void testEveryConfiguredLocaleResolvesSettingsAndInspectionMessages() { + for (final String suffix : LOCALE_SUFFIXES) { + final Locale locale = Locale.forLanguageTag(suffix.replace('_', '-')); + assertThat(GitHubWorkflowBundle.messageFor(locale, "settings.displayName")).isNotBlank(); + assertThat(GitHubWorkflowBundle.messageFor(locale, "inspection.action.delete.invalid", "input", "bad")) + .contains("bad"); + assertThat(GitHubWorkflowBundle.messageFor(locale, "inspection.output.unused", "artifact")) + .contains("artifact"); + assertThat(GitHubWorkflowBundle.messageFor(locale, "workflow.run.log.failed", "boom")) + .contains("boom"); + assertThat(GitHubWorkflowBundle.messageFor(locale, "workflow.log.command")).isNotBlank(); + assertThat(GitHubWorkflowBundle.messageFor(locale, "error.report.pluginVersion", "2026.05.22")) + .contains("2026.05.22"); + assertThat(GitHubWorkflowBundle.messageFor(locale, "completion.runner.name")).isNotBlank(); + assertThat(GitHubWorkflowBundle.messageFor(locale, "completion.uses.local.action")).isNotBlank(); + assertThat(GitHubWorkflowBundle.messageFor(locale, "documentation.context.github.description")).isNotBlank(); + assertThat(GitHubWorkflowBundle.messageFor(locale, "documentation.step.title", "build")) + .contains("build"); + assertThat(GitHubWorkflowBundle.messageFor(locale, "settings.cache.summary", 1, 2, 3, 4, 5, 6)) + .contains("1"); + assertThat(GitHubWorkflowBundle.messageFor(locale, "workflow.run.tree.done")).isNotBlank(); + assertThat(GitHubWorkflowBundle.messageFor(locale, "workflow.run.tree.failed")).isNotBlank(); + assertThat(GitHubWorkflowBundle.messageFor(locale, "workflow.run.tree.warn")).isNotBlank(); + } + } + + @Test + public void testWorkflowRunMessagesStayShortAndResolvedForEveryLocale() throws IOException { + assertThat(loadBundle("").stringPropertyNames()) + .doesNotContain( + "workflow.run.delete.button", + "workflow.run.download.log.button", + "workflow.run.download.artifacts.button" + ); + final List keys = List.of( + "workflow.run.gutter.stop", + "workflow.run.gutter.stop.description", + "workflow.run.cancel.requested", + "workflow.run.stop.before.id", + "workflow.run.cancel.http", + "workflow.run.cancel.failed", + "workflow.run.discovery", + "workflow.run.discovery.none", + "workflow.run.delete.tooltip", + "workflow.run.delete.noRun", + "workflow.run.delete.requested", + "workflow.run.delete.done", + "workflow.run.delete.http", + "workflow.run.delete.failed", + "workflow.run.rerun.all.tooltip", + "workflow.run.rerun.failed.tooltip", + "workflow.run.rerun.noRun", + "workflow.run.rerun.all.requested", + "workflow.run.rerun.failed.requested", + "workflow.run.rerun.all.done", + "workflow.run.rerun.failed.done", + "workflow.run.rerun.http", + "workflow.run.rerun.failed", + "workflow.run.download.log.tooltip", + "workflow.run.download.artifacts.tooltip", + "workflow.run.download.noRun", + "workflow.run.download.log.requested", + "workflow.run.download.log.done", + "workflow.run.download.artifacts.requested", + "workflow.run.download.artifacts.empty", + "workflow.run.download.artifact.expired", + "workflow.run.download.artifact.done", + "workflow.run.download.failed" + ); + for (final String suffix : LOCALE_SUFFIXES) { + final Locale locale = Locale.forLanguageTag(suffix.replace('_', '-')); + for (final String key : keys) { + final String message = GitHubWorkflowBundle.messageFor(locale, key, 42, "artifact.zip"); + assertThat(message) + .as("Locale suffix [%s] key [%s]", suffix, key) + .isNotBlank() + .doesNotContain("GitHub accepted failed-job re-run for workflow run") + .doesNotContain("there is nothing") + .hasSizeLessThanOrEqualTo(80); + } + } + } + + @Test + public void testWorkflowSyntaxCompletionDescriptionsResolveForEveryLocale() { + final List keys = List.of( + "completion.workflow.top.on", + "completion.workflow.top.permissions", + "completion.workflow.event.push", + "completion.workflow.event.workflow_dispatch", + "completion.workflow.event.workflow_call", + "completion.workflow.event.image_version", + "completion.workflow.eventFilter.types", + "completion.workflow.eventFilter.branches", + "completion.workflow.permission.contents", + "completion.workflow.permission.artifact-metadata", + "completion.workflow.permission.code-quality", + "completion.workflow.permission.id-token", + "completion.workflow.permission.models", + "completion.workflow.permission.vulnerability-alerts", + "completion.workflow.permission.value.read", + "completion.workflow.permission.value.write", + "completion.workflow.permission.value.none", + "completion.workflow.permission.shorthand.read-all", + "completion.workflow.permission.shorthand.write-all", + "completion.workflow.permission.shorthand.empty", + "completion.workflow.job.runs-on", + "completion.workflow.job.steps", + "completion.workflow.step.uses", + "completion.workflow.step.run", + "completion.workflow.defaultsRun.shell", + "completion.workflow.concurrency.group", + "completion.workflow.concurrency.cancel-in-progress", + "completion.workflow.environment.name", + "completion.workflow.environment.url", + "completion.workflow.strategy.matrix", + "completion.workflow.matrix.include", + "completion.workflow.container.image", + "completion.workflow.service.image", + "completion.workflow.credentials.username", + "completion.workflow.credentials.password", + "completion.workflow.inputType.choice", + "completion.workflow.boolean.true", + "completion.workflow.runner.ubuntu-latest" + ); + for (final String suffix : LOCALE_SUFFIXES) { + final Locale locale = Locale.forLanguageTag(suffix.replace('_', '-')); + for (final String key : keys) { + assertThat(GitHubWorkflowBundle.messageFor(locale, key)) + .as("Locale suffix [%s] key [%s]", suffix, key) + .isNotBlank() + .isNotEqualTo(GitHubWorkflowBundle.messageFor(locale, "completion.workflow.syntax")); + } + } + } + + @Test + public void testGermanInspectionAndCacheMessagesAreNotEnglishFallbacks() { + final Locale locale = Locale.forLanguageTag("de"); + + assertThat(GitHubWorkflowBundle.messageFor(locale, "inspection.action.reload", "YunaBraska/YunaBraska")) + .contains("Neu laden") + .doesNotContain("Reload"); + assertThat(GitHubWorkflowBundle.messageFor(locale, "inspection.warning.toggle", "aus", "YunaBraska/YunaBraska")) + .contains("Warnungen") + .doesNotContain("Toggle warnings"); + assertThat(GitHubWorkflowBundle.messageFor(locale, "settings.cache.summary", 1, 1, 1, 0, 0, 60)) + .contains("Cache: 60 KB") + .doesNotContain("IDE-Heap") + .doesNotContain("Heap"); + assertThat(GitHubWorkflowBundle.messageFor(locale, "workflow.run.tree.failed")) + .contains("fehlgeschlagen") + .doesNotContain("failed"); + } + + @Test + public void testCoreInspectionMessagesAreLocalizedForEveryLocale() throws IOException { + final Properties defaultBundle = loadBundle(""); + final List keys = List.of( + "inspection.action.reload", + "inspection.warning.toggle", + "inspection.action.update.major", + "inspection.statement.incomplete", + "inspection.invalid.suffix.remove", + "inspection.replace.with", + "inspection.invalid.remove", + "inspection.action.unresolved", + "inspection.action.jump", + "inspection.secret.invalid.if", + "inspection.secret.replace.runtime", + "inspection.needs.invalid.job", + "inspection.workflow.syntax.unknownTopLevelKey", + "inspection.workflow.syntax.unknownEventKey", + "inspection.workflow.syntax.unknownTriggerKey", + "inspection.workflow.syntax.unknownTriggerFilter", + "inspection.workflow.syntax.unknownTriggerValue", + "inspection.workflow.syntax.unknownPermission", + "inspection.workflow.syntax.unknownPermissionValue", + "inspection.workflow.syntax.unknownJobKey", + "inspection.workflow.syntax.unknownStepKey", + "workflow.run.gutter.stop", + "workflow.run.gutter.stop.description", + "workflow.cache.progress.title" + ); + for (final String suffix : LOCALE_SUFFIXES) { + final Properties localizedBundle = loadBundle("_" + suffix); + for (final String key : keys) { + assertThat(localizedBundle.getProperty(key)) + .as("Locale suffix [%s] key [%s] is not the default English fallback", suffix, key) + .isNotEqualTo(defaultBundle.getProperty(key)); + } + } + } + + private static Properties loadBundle(final String suffix) throws IOException { + final String path = BUNDLE_PATH + suffix + ".properties"; + try (InputStream stream = LocalizationResourcesTest.class.getClassLoader().getResourceAsStream(path)) { + assertThat(stream).as("Bundle [%s] exists", path).isNotNull(); + final Properties properties = new Properties(); + properties.load(new InputStreamReader(stream, StandardCharsets.UTF_8)); + return properties; + } + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitterTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitterTest.java new file mode 100644 index 0000000..37b357a --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitterTest.java @@ -0,0 +1,25 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.diagnostic.IdeaLoggingEvent; +import com.intellij.openapi.diagnostic.SubmittedReportInfo; +import org.junit.Test; + +import javax.swing.*; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PluginErrorReportSubmitterTest { + + @Test + public void submitEmptyEventArrayFailsExplicitly() { + final PluginErrorReportSubmitter submitter = new PluginErrorReportSubmitter(); + final AtomicReference reportInfo = new AtomicReference<>(); + + final boolean submitted = submitter.submit(new IdeaLoggingEvent[0], "", new JPanel(), reportInfo::set); + + assertThat(submitted).isFalse(); + assertThat(reportInfo.get()).isNotNull(); + assertThat(reportInfo.get().getStatus()).isEqualTo(SubmittedReportInfo.SubmissionStatus.FAILED); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/RemoteActionProvidersTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/RemoteActionProvidersTest.java new file mode 100644 index 0000000..ee6c324 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/RemoteActionProvidersTest.java @@ -0,0 +1,224 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.intellij.testFramework.fixtures.BasePlatformTestCase; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RemoteActionProvidersTest extends BasePlatformTestCase { + + @Override + protected void tearDown() throws Exception { + try { + RemoteServerSettings.getInstance().setCustomServers(List.of()); + } finally { + super.tearDown(); + } + } + + public void testConfiguredGithubEnterpriseServerResolvesActionMetadataFromFakeApi() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.addContent("acme", "tool", "action.yml", "v1", """ + name: Enterprise Tool + inputs: + token: + description: Token + outputs: + artifact: + description: Artifact + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + server.setBranches("acme", "tool", List.of("main")); + server.setTags("acme", "tool", List.of("v1")); + useServer(server, "/api/v3"); + + final GitHubAction action = GitHubAction.createGithubAction(false, "acme/tool@v1", "acme/tool@v1").resolve(); + + assertThat(action.isResolved()).isTrue(); + assertThat(action.githubUrl()).isEqualTo(server.webUrl() + "/acme/tool/tree/v1#readme"); + assertThat(action.freshInputs()).containsKey("token"); + assertThat(action.freshOutputs()).containsKey("artifact"); + assertThat(action.remoteRefs()).containsExactly("main", "v1"); + assertThat(server.requests()).contains("/api/v3/repos/acme/tool/contents/action.yml?ref=v1"); + } + } + + public void testGithubEnterpriseAbsoluteUrlResolvesActionMetadataFromFakeApi() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.addContent("acme", "tool", "action.yml", "main", """ + name: Enterprise Tool + inputs: + path: + description: Path + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + useServer(server, "/api/v3"); + + final GitHubAction action = GitHubAction.createGithubAction(false, server.webUrl() + "/acme/tool@main", server.webUrl() + "/acme/tool@main").resolve(); + + assertThat(action.isResolved()).isTrue(); + assertThat(action.githubUrl()).isEqualTo(server.webUrl() + "/acme/tool/tree/main#readme"); + assertThat(action.freshInputs()).containsKey("path"); + } + } + + public void testResolveTriesActionYamlWhenActionYmlIsMissing() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.addContent("JetBrains", "qodana-action", "action.yaml", "v2023.3.1", """ + name: Qodana + inputs: + args: + description: Args + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + useServer(server, "/api/v3"); + + final GitHubAction action = GitHubAction.createGithubAction(false, "JetBrains/qodana-action@v2023.3.1", "JetBrains/qodana-action@v2023.3.1").resolve(); + + assertThat(action.isResolved()).isTrue(); + assertThat(action.freshInputs()).containsKey("args"); + assertThat(server.requests()).contains( + "/api/v3/repos/JetBrains/qodana-action/contents/action.yml?ref=v2023.3.1", + "/api/v3/repos/JetBrains/qodana-action/contents/action.yaml?ref=v2023.3.1" + ); + } + } + + public void testApiNotFoundKeepsActionUnresolvedWithoutThrowing() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + useServer(server, "/api/v3"); + + final String usesValue = server.webUrl() + "/missing/tool@main"; + final GitHubAction action = GitHubAction.createGithubAction(false, usesValue, usesValue).resolve(); + + assertThat(action.isResolved()).isFalse(); + assertThat(action.freshInputs()).isEmpty(); + assertThat(action.freshOutputs()).isEmpty(); + } + } + + public void testResolvedRemoteActionKeepsCachedMetadataWhenServerIsOffline() throws Exception { + final String usesValue; + final GitHubAction action; + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.addContent("acme", "tool", "action.yml", "v1", """ + name: Enterprise Tool + inputs: + token: + description: Token + outputs: + artifact: + description: Artifact + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + useServer(server, "/api/v3"); + usesValue = server.webUrl() + "/acme/tool@v1"; + action = GitHubAction.createGithubAction(false, usesValue, usesValue).resolve(); + } + + action.expiryTime(0); + final GitHubAction refreshed = action.resolve(); + + assertThat(refreshed).isSameAs(action); + assertThat(refreshed.isResolved()).isTrue(); + assertThat(refreshed.freshInputs()).containsKey("token"); + assertThat(refreshed.freshOutputs()).containsKey("artifact"); + assertThat(refreshed.expiryTime()).isGreaterThan(System.currentTimeMillis()); + } + + public void testGithubReusableWorkflowPathIsDetectedAsWorkflowMetadata() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.addContent("acme", "automation", ".github/workflows/reuse.yml", "main", """ + name: Reuse + on: + workflow_call: + inputs: + config: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + useServer(server, "/api/v3"); + + final GitHubAction action = GitHubAction.createGithubAction(false, server.webUrl() + "/acme/automation/.github/workflows/reuse.yml@main", server.webUrl()).resolve(); + + assertThat(action.isResolved()).isTrue(); + assertThat(action.isAction()).isFalse(); + assertThat(action.freshInputs()).containsKey("config"); + assertThat(action.githubUrl()).isEqualTo(server.webUrl() + "/acme/automation/blob/main/.github/workflows/reuse.yml"); + } + } + + public void testLatestRefsReturnsLatestTenTagsBeforeBranches() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.setTags("acme", "tool", List.of("v10", "v9", "v8", "v7", "v6", "v5", "v4", "v3", "v2", "v1", "v0")); + server.setBranches("acme", "tool", List.of("main")); + useServer(server, "/api/v3"); + + final List refs = RemoteActionProviders.latestRefs("acme/tool", 10); + + assertThat(refs).containsExactly("v10", "v9", "v8", "v7", "v6", "v5", "v4", "v3", "v2", "v1"); + assertThat(server.requests()).contains("/api/v3/repos/acme/tool/tags?per_page=10"); + } + } + + public void testLatestRefsFallsBackToBranchesWhenTagsAreEmpty() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.setBranches("acme", "tool", List.of("main", "release")); + useServer(server, "/api/v3"); + + final List refs = RemoteActionProviders.latestRefs("acme/tool", 10); + + assertThat(refs).containsExactly("main", "release"); + } + } + + public void testSearchUsesReturnsMatchingRepositoriesFromConfiguredServer() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.setRepositories("actions", Map.of( + "checkout", "Checkout repository", + "setup-java", "Set up Java", + "cache", "Cache dependencies" + )); + useServer(server, "/api/v3"); + + final Map completions = RemoteActionProviders.searchUses("actions/set", 10); + + assertThat(completions).containsEntry("actions/setup-java", "Set up Java"); + assertThat(completions).doesNotContainKey("actions/checkout"); + assertThat(server.requests()).contains("/api/v3/users/actions/repos?per_page=10"); + } + } + + private static void useServer(final FakeRemoteServer server, final String apiPrefix) { + RemoteServerSettings.getInstance().setCustomServers(List.of(new RemoteServerSettings.Server( + "Fake", + server.webUrl(), + server.apiUrl(apiPrefix), + "", + true + ))); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/SchemaResourcesTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/SchemaResourcesTest.java new file mode 100644 index 0000000..9e11681 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/SchemaResourcesTest.java @@ -0,0 +1,38 @@ +package com.github.yunabraska.githubworkflow.services; + +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SchemaResourcesTest { + + private static final List SCHEMA_NAMES = List.of( + "dependabot-2.0", + "github-action", + "github-funding", + "github-workflow", + "github-discussion", + "github-issue-forms", + "github-issue-config", + "github-workflow-template-properties" + ); + + @Test + public void packagedSchemasArePresentAndNonEmpty() throws IOException { + final Path directory = Path.of(System.getProperty("user.dir"), "src", "main", "resources", "schemas"); + + for (final String schemaName : SCHEMA_NAMES) { + final Path schema = directory.resolve(schemaName + ".json"); + assertThat(schema).exists().isRegularFile(); + assertThat(Files.readString(schema)) + .startsWith("{") + .contains("\"$schema\"") + .contains("\"$id\""); + } + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java new file mode 100644 index 0000000..590364a --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java @@ -0,0 +1,82 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionUiKind; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.execution.configurations.ConfigurationTypeUtil; +import com.intellij.testFramework.fixtures.BasePlatformTestCase; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowActionRegistrationTest extends BasePlatformTestCase { + + public void testCacheActionGroupIsRegisteredFromPluginXml() { + final AnAction action = ActionManager.getInstance().getAction("GitHubWorkflow.Tools"); + + assertThat(action).isInstanceOf(ActionGroup.class); + assertThat(action.getTemplatePresentation().getText()).isEqualTo("GitHub Workflow"); + assertThat(action.getTemplatePresentation().getDescription()).isEqualTo("GitHub Workflow plugin tools"); + } + + public void testRefreshActionCacheActionIsRegisteredAndLocalized() { + final AnAction action = ActionManager.getInstance().getAction("GitHubWorkflow.RefreshActionCache"); + + assertThat(action).isInstanceOf(RefreshActionCacheAction.class); + assertThat(action.getTemplatePresentation().getText()).isEqualTo("Refresh Action Cache"); + assertThat(action.getTemplatePresentation().getDescription()) + .isEqualTo("Refresh resolved remote GitHub Actions and reusable workflow metadata"); + } + + public void testClearActionCacheActionIsRegisteredAndLocalized() { + final AnAction action = ActionManager.getInstance().getAction("GitHubWorkflow.ClearActionCache"); + + assertThat(action).isInstanceOf(ClearActionCacheAction.class); + assertThat(action.getTemplatePresentation().getText()).isEqualTo("Clear Action Cache"); + assertThat(action.getTemplatePresentation().getDescription()) + .isEqualTo("Clear cached GitHub Actions and reusable workflow metadata"); + } + + public void testRestoreActionWarningsActionIsRegisteredAndLocalized() { + final AnAction action = ActionManager.getInstance().getAction("GitHubWorkflow.RestoreActionWarnings"); + + assertThat(action).isInstanceOf(RestoreActionWarningsAction.class); + assertThat(action.getTemplatePresentation().getText()).isEqualTo("Restore Action Warnings"); + assertThat(action.getTemplatePresentation().getDescription()) + .isEqualTo("Restore suppressed action, input, and output validation warnings"); + } + + public void testActionUpdateUsesConfiguredPluginLanguageOverride() { + final PluginSettings settings = PluginSettings.getInstance(); + final String previousLanguage = settings.languageTag(); + try { + settings.languageTag("de"); + final AnAction action = ActionManager.getInstance().getAction("GitHubWorkflow.RefreshActionCache"); + final Presentation presentation = new Presentation(); + + action.update(AnActionEvent.createEvent( + action, + DataContext.EMPTY_CONTEXT, + presentation, + "GithubWorkflowPluginTest", + ActionUiKind.NONE, + null + )); + + assertThat(presentation.getText()).isEqualTo("Action-Cache aktualisieren"); + assertThat(presentation.getDescription()).contains("entfernter GitHub Actions"); + } finally { + settings.languageTag(previousLanguage); + } + } + + public void testWorkflowRunConfigurationTypeIsRegistered() { + final WorkflowRunConfigurationType type = ConfigurationTypeUtil.findConfigurationType(WorkflowRunConfigurationType.class); + + assertThat(type.getId()).isEqualTo(WorkflowRunConfigurationType.ID); + assertThat(type.getConfigurationFactories()).hasSize(1); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java new file mode 100644 index 0000000..7ee94f9 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java @@ -0,0 +1,1988 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.intellij.codeInsight.editorActions.TypedHandlerDelegate; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementPresentation; +import com.intellij.util.ThreeState; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.github.yunabraska.githubworkflow.services.GitHubActionCache.getActionCache; +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowCompletionTest extends EditorFeatureTestCase { + + public void testAutoPopupTriggersAfterYamlNewLine() { + configureWorkflow(""" + name: Completion + on: + + """); + + assertThat(invokeAutoPopup('\n')).isTrue(); + } + + public void testLineBeforeCaretHandlesInjectedZeroOffset() { + assertThat(CodeCompletion.lineBeforeCaret(""" + + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: + """, 0)).isEmpty(); + } + + public void testRunFieldCompletionDoesNotCrash() { + assertThat(completeWorkflow(""" + + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: + """)).isNotNull(); + } + + public void testAutoPopupTypedHandlerSchedulesYamlNewLine() { + configureWorkflow(""" + name: Completion + on: + + """); + final WorkflowAutoPopupTypedHandler handler = new WorkflowAutoPopupTypedHandler(); + + assertThat(handler.checkAutoPopup('\n', getProject(), myFixture.getEditor(), myFixture.getFile())) + .isEqualTo(TypedHandlerDelegate.Result.CONTINUE); + assertThat(handler.charTyped('\n', getProject(), myFixture.getEditor(), myFixture.getFile())) + .isEqualTo(TypedHandlerDelegate.Result.CONTINUE); + } + + public void testEnterHandlerTriggersAfterYamlMappingKey() { + configureWorkflow(""" + name: Completion + on: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertThat(WorkflowAutoPopupEnterHandler.shouldAutoPopupAfterEnter(myFixture.getEditor(), myFixture.getFile())) + .isTrue(); + } + + public void testEnterHandlerIgnoresPlainYamlValueLine() { + configureWorkflow(""" + name: Completion + on: workflow_dispatch + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertThat(WorkflowAutoPopupEnterHandler.shouldAutoPopupAfterEnter(myFixture.getEditor(), myFixture.getFile())) + .isFalse(); + } + + public void testWorkflowCompletionConfidenceKeepsAutoPopupAvailable() { + configureWorkflow(""" + name: Completion + on: + + """); + + assertThat(new WorkflowCompletionConfidence().shouldSkipAutopopup( + myFixture.getEditor(), + myFixture.getFile().findElementAt(myFixture.getCaretOffset()), + myFixture.getFile(), + myFixture.getCaretOffset() + )).isEqualTo(ThreeState.NO); + } + + public void testAutoPopupTriggersAfterYamlKeySeparator() { + configureWorkflow(""" + name: Completion + on: + """); + + assertThat(invokeAutoPopup(':')).isTrue(); + } + + public void testAutoPopupTriggersAfterExpressionDot() { + configureWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github. }}" + """); + + assertThat(invokeAutoPopup('.')).isTrue(); + } + + public void testAutoPopupIgnoresPlainLetters() { + configureWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertThat(invokeAutoPopup('x')).isFalse(); + } + + public void testRootCompletionSuggestsAvailableContexts() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + inputs: + deploy-target: + type: string + secrets: + SECRET_TOKEN: + required: false + env: + TOP_LEVEL: top + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - id: package + run: echo ok + - run: echo "${{ }}" + """)).contains("github", "job", "runner", "strategy", "matrix", "env", "inputs", "secrets", "steps"); + } + + private boolean invokeAutoPopup(final char typeChar) { + return WorkflowAutoPopupTypedHandler.shouldAutoPopup(typeChar, myFixture.getEditor(), myFixture.getFile()); + } + + public void testGithubCompletionSuggestsRefName() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github. }}" + """)).contains("ref_name"); + } + + public void testGithubCompletionSuggestsCurrentDocumentedIds() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github. }}" + """)).contains("actor_id", "ref_protected", "repository_owner_id"); + } + + public void testIfExpressionCompletionAfterDotWithoutBraces() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + if: github. + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("ref_name"); + } + + public void testRunnerCompletionSuggestsEnvironment() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ runner. }}" + """)).contains("environment"); + } + + public void testRunnerCompletionSuggestsDebug() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ runner. }}" + """)).contains("debug"); + } + + public void testJobCompletionSuggestsStatus() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ job. }}" + """)).contains("status"); + } + + public void testJobContainerCompletionSuggestsContainerMembers() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + container: postgres:16 + steps: + - run: echo "${{ job.container. }}" + """)).contains("id", "network"); + } + + public void testJobServicesCompletionSuggestsServiceIds() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + steps: + - run: echo "${{ job.services. }}" + """)).contains("postgres"); + } + + public void testJobServiceCompletionSuggestsServiceMembers() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + steps: + - run: echo "${{ job.services.postgres. }}" + """)).contains("id", "network", "ports"); + } + + public void testJobServicePortsCompletionSuggestsMappedPorts() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + ports: + - 5432/tcp + steps: + - run: echo "${{ job.services.postgres.ports[] }}" + """)).contains("5432"); + } + + public void testStrategyCompletionSuggestsJobIndex() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - run: echo "${{ strategy. }}" + """)).contains("job-index", "job-total", "max-parallel"); + } + + public void testMatrixCompletionUsesCurrentJobMatrixKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + node: [24, 25] + steps: + - run: echo "${{ matrix. }}" + """)).contains("os", "node"); + } + + public void testMatrixCompletionUsesIncludeKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + node: 25 + steps: + - run: echo "${{ matrix. }}" + """)).contains("os", "node"); + } + + public void testEnvCompletionIncludesWorkflowJobStepAndRunEnvironmentValues() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + env: + WORKFLOW_LEVEL: workflow + jobs: + build: + runs-on: ubuntu-latest + env: + JOB_LEVEL: job + steps: + - run: echo "RUN_LEVEL=run" >> "$GITHUB_ENV" + - env: + STEP_LEVEL: step + run: echo "${{ env. }}" + """)).contains("WORKFLOW_LEVEL", "JOB_LEVEL", "STEP_LEVEL", "RUN_LEVEL"); + } + + public void testEnvCompletionIncludesJobEnvMapAliasValues() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + define: + runs-on: ubuntu-latest + env: &env_vars + NODE_ENV: production + steps: + - run: echo ok + reuse: + runs-on: ubuntu-latest + env: *env_vars + steps: + - run: echo "${{ env. }}" + """)).contains("NODE_ENV"); + } + + public void testInputsCompletionUsesWorkflowCallInputs() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + inputs: + deploy-target: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs. }}" + """)).contains("deploy-target"); + } + + public void testInputsCompletionUsesWorkflowDispatchInputs() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_dispatch: + inputs: + release-tag: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs. }}" + """)).contains("release-tag"); + } + + public void testWorkflowCallInputDefaultCompletionSuggestsInputs() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + inputs: + deploy-target: + type: string + target: + type: string + default: ${{ inputs. }} + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("deploy-target"); + } + + public void testJobContainerCredentialsCompletionSuggestsInputs() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + inputs: + registry-user: + type: string + jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/owner/image + credentials: + username: ${{ inputs. }} + steps: + - run: echo ok + """)).contains("registry-user"); + } + + public void testJobStrategyFailFastCompletionSuggestsInputs() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + inputs: + fail-fast: + type: boolean + jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: ${{ inputs. }} + matrix: + os: [ubuntu-latest] + steps: + - run: echo ok + """)).contains("fail-fast"); + } + + public void testWorkflowDispatchTriggerCompletionSuggestsInputs() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_dispatch: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("inputs"); + } + + public void testTopLevelSyntaxCompletionSuggestsCurrentWorkflowKeys() { + assertThat(completeWorkflow(""" + name: Completion + + """)).contains("run-name", "permissions", "defaults", "concurrency", "jobs"); + } + + public void testOnSyntaxCompletionSuggestsCurrentEvents() { + assertThat(completeWorkflow(""" + name: Completion + on: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("push", "pull_request", "workflow_call", "workflow_dispatch", "workflow_run", "merge_group", "image_version"); + } + + public void testOnSyntaxCompletionShowsTriggerDescriptions() { + assertThat(completeWorkflowTypeTexts(""" + name: Completion + on: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).containsEntry("push", "Commit or tag pushed") + .containsEntry("workflow_dispatch", "Manual run button") + .containsEntry("workflow_call", "Reusable workflow call"); + } + + public void testTriggerTypesValueCompletionSuggestsActivityTypesInline() { + assertThat(completeWorkflow(""" + name: Completion + on: + pull_request: + types: [] + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("opened", "synchronize", "ready_for_review", "auto_merge_enabled"); + } + + public void testTriggerTypesValueCompletionSuggestsActivityTypesInList() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_run: + types: + - + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("completed", "requested", "in_progress"); + } + + public void testBranchFilterCompletionSuggestsLocalGitBranches() throws Exception { + writeProjectFile(".git/refs/heads/main", "abc123"); + writeProjectFile(".git/refs/heads/feature/one", "abc123"); + + configureWorkflowProjectFile(""" + name: Completion + on: + push: + branches: [] + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertThat(completeBasicLookupStrings()).contains("main", "feature/one"); + } + + public void testTagFilterCompletionSuggestsLocalGitTags() throws Exception { + writeProjectFile(".git/refs/tags/v1.2.3", "abc123"); + + configureWorkflowProjectFile(""" + name: Completion + on: + push: + tags: + - + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertThat(completeBasicLookupStrings()).contains("v1.2.3"); + } + + public void testPathFilterCompletionSuggestsProjectFiles() { + myFixture.addFileToProject("src/main/App.java", "class App {}"); + + configureWorkflowProjectFile(""" + name: Completion + on: + pull_request: + paths: [] + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertThat(completeBasicLookupStrings()).contains("src/main/App.java"); + } + + public void testPushFilterCompletionSuggestsBranchTagAndPathKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: + push: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("branches", "branches-ignore", "tags", "paths", "paths-ignore") + .doesNotContain("types"); + } + + public void testScheduleFilterCompletionSuggestsOnlyCron() { + assertThat(completeWorkflow(""" + name: Completion + on: + schedule: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("cron") + .doesNotContain("branches", "paths", "types"); + } + + public void testWorkflowCallTriggerCompletionSuggestsInputsOutputsAndSecrets() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("inputs", "outputs", "secrets"); + } + + public void testWorkflowCallOutputCompletionSuggestsValueAndDescription() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + outputs: + artifact: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("value", "description"); + } + + public void testWorkflowDispatchInputDefinitionCompletionSuggestsInputProperties() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_dispatch: + inputs: + release-tag: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("description", "type", "required", "default", "options"); + } + + public void testWorkflowCallInputDefinitionCompletionSuggestsInputProperties() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + inputs: + release-tag: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("description", "type", "required", "default"); + } + + public void testWorkflowCallSecretDefinitionCompletionSuggestsSecretProperties() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + secrets: + token: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("description", "required"); + } + + public void testWorkflowDispatchInputTypeCompletionSuggestsDispatchTypes() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_dispatch: + inputs: + target: + type: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("string", "boolean", "choice", "number", "environment"); + } + + public void testWorkflowCallInputTypeCompletionSuggestsReusableWorkflowTypes() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + inputs: + target: + type: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("string", "boolean", "number").doesNotContain("choice", "environment"); + } + + public void testPermissionScopeCompletionSuggestsCurrentScopes() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + permissions: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("contents", "pull-requests", "id-token", "attestations", "models", "artifact-metadata", "code-quality", "vulnerability-alerts"); + } + + public void testPermissionScopeCompletionShowsDescriptions() { + assertThat(completeWorkflowTypeTexts(""" + name: Completion + on: workflow_dispatch + permissions: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).containsEntry("contents", "Repository contents") + .containsEntry("id-token", "OpenID Connect tokens") + .containsEntry("models", "GitHub Models"); + } + + public void testPermissionValueCompletionSuggestsReadWriteNone() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + permissions: + contents: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("read", "write", "none"); + } + + public void testPermissionShorthandCompletionSuggestsReadAllWriteAllAndEmpty() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + permissions: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("read-all", "write-all", "{}"); + } + + public void testIdTokenPermissionCompletionSuggestsOnlyWriteOrNone() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + permissions: + id-token: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("write", "none").doesNotContain("read"); + } + + public void testModelsPermissionCompletionSuggestsOnlyReadOrNone() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + permissions: + models: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("read", "none").doesNotContain("write"); + } + + public void testJobSyntaxCompletionSuggestsDocumentedJobKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + + """)).contains("runs-on", "permissions", "environment", "strategy", "container", "services", "uses"); + } + + public void testDefaultsRunCompletionSuggestsShellAndWorkingDirectory() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + defaults: + run: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("shell", "working-directory"); + } + + public void testStrategyCompletionSuggestsMatrixFailFastAndMaxParallelKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + strategy: + + steps: + - run: echo ok + """)).contains("matrix", "fail-fast", "max-parallel"); + } + + public void testMatrixCompletionSuggestsIncludeAndExcludeKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + + steps: + - run: echo ok + """)).contains("include", "exclude"); + } + + public void testContainerCompletionSuggestsContainerShapeKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + container: + + steps: + - run: echo ok + """)).contains("image", "credentials", "env", "ports", "volumes", "options"); + } + + public void testContainerCredentialsCompletionSuggestsUsernameAndPassword() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/example/app:latest + credentials: + + steps: + - run: echo ok + """)).contains("username", "password"); + } + + public void testServiceCompletionSuggestsServiceShapeKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + + steps: + - run: echo ok + """)).contains("image", "credentials", "env", "ports", "volumes", "options"); + } + + public void testServiceCredentialsCompletionSuggestsUsernameAndPassword() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + credentials: + + steps: + - run: echo ok + """)).contains("username", "password"); + } + + public void testConcurrencyCompletionSuggestsGroupAndCancelInProgress() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + concurrency: + + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("group", "cancel-in-progress"); + } + + public void testEnvironmentCompletionSuggestsNameAndUrl() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + deploy: + runs-on: ubuntu-latest + environment: + + steps: + - run: echo ok + """)).contains("name", "url"); + } + + public void testStepSyntaxCompletionSuggestsDocumentedStepKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - + """)).contains("id", "name", "uses", "run", "shell", "with", "continue-on-error", "timeout-minutes"); + } + + public void testRunsOnCompletionSuggestsCommonHostedRunnerLabels() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: + steps: + - run: echo ok + """)).contains("ubuntu-latest", "windows-latest", "macos-latest", "self-hosted"); + } + + public void testBooleanFieldCompletionSuggestsTrueFalse() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + continue-on-error: + steps: + - run: echo ok + """)).contains("true", "false"); + } + + public void testBracketInputCompletionUsesWorkflowCallInputs() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + inputs: + deploy-target: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs[''] }}" + """)).contains("deploy-target"); + } + + public void testBracketInputCompletionHonorsPrefix() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + inputs: + deploy-target: + type: string + package-name: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs['dep'] }}" + """)).contains("deploy-target").doesNotContain("package-name"); + } + + public void testSecretsCompletionUsesWorkflowCallSecrets() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + secrets: + SECRET_TOKEN: + required: false + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ secrets. }}" + """)).contains("SECRET_TOKEN"); + } + + public void testSecretsCompletionIncludesAutomaticGithubToken() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ secrets. }}" + """)).contains("GITHUB_TOKEN"); + } + + public void testBracketGithubCompletionSuggestsRefName() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github[''] }}" + """)).contains("ref_name"); + } + + public void testNeedsFieldCompletionSuggestsPreviousJobs() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + lint: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("build"); + } + + public void testNeedsContextCompletionSuggestsDirectNeeds() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs. }}" + """)).contains("build"); + } + + public void testBracketNeedsCompletionSuggestsDirectNeeds() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs[''] }}" + """)).contains("build"); + } + + public void testNeedsContextMemberCompletionSuggestsOutputsAndResult() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build. }}" + """)).contains("outputs", "result"); + } + + public void testNeedsOutputCompletionSuggestsJobOutputs() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build.outputs. }}" + """)).contains("artifact"); + } + + public void testRunScriptExpressionCompletionSuggestsNeedsOutputs() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + github_release: + runs-on: ubuntu-latest + outputs: + has_pom: ${{ steps.pom.outputs.has_pom }} + steps: + - id: pom + run: echo "has_pom=true" >> "$GITHUB_OUTPUT" + show: + needs: github_release + runs-on: ubuntu-latest + steps: + - run: echo "has_pom [${{ needs.github_release.outputs. }}]" + """)).contains("has_pom"); + } + + public void testBracketNeedsOutputCompletionSuggestsJobOutputs() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs['build'].outputs[''] }}" + """)).contains("artifact"); + } + + public void testJobsWorkflowOutputCompletionSuggestsJobs() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + outputs: + artifact: + value: ${{ jobs. }} + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """)).contains("build"); + } + + public void testJobsContextMemberCompletionSuggestsOutputsAndResult() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + outputs: + artifact: + value: ${{ jobs.build. }} + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)).contains("outputs", "result"); + } + + public void testJobsOutputCompletionSuggestsJobOutputs() { + assertThat(completeWorkflow(""" + name: Completion + on: + workflow_call: + outputs: + artifact: + value: ${{ jobs.build.outputs. }} + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """)).contains("artifact"); + } + + public void testStepsCompletionSuggestsPreviousStepIds() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo ok + - run: echo "${{ steps. }}" + """)).contains("package"); + } + + public void testBracketStepsCompletionSuggestsPreviousStepIds() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo ok + - run: echo "${{ steps[''] }}" + """)).contains("package"); + } + + public void testStepsMemberCompletionSuggestsOutputsOutcomeConclusion() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo ok + - run: echo "${{ steps.package. }}" + """)).contains("outputs", "outcome", "conclusion"); + } + + public void testStepsOutputCompletionSuggestsRunOutput() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.package.outputs. }}" + """)).contains("artifact"); + } + + public void testBracketStepsOutputCompletionSuggestsRunOutput() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps['package'].outputs[''] }}" + """)).contains("artifact"); + } + + public void testStepsOutputCompletionSuggestsActionOutput() { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of("artifact", "Artifact path")); + + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + uses: owner/tool@v1 + - run: echo "${{ steps.package.outputs. }}" + """)).contains("artifact"); + } + + public void testCompositeActionOutputStepCompletionSuggestsRunStep() { + assertThat(completeWorkflow(""" + name: Composite Action + outputs: + artifact: + value: ${{ steps. }} + runs: + using: composite + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + shell: sh + """)).contains("package"); + } + + public void testActionInputCompletionUsesResolvedActionMetadata() { + seedRemoteAction("owner/tool@v1", Map.of("known-input", "Known input", "other-input", "Other input"), Map.of()); + + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + + """)).contains("known-input"); + } + + public void testReusableWorkflowInputCompletionUsesResolvedWorkflowMetadata() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of("config-path", "Config path"), Map.of()); + + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + with: + + """)).contains("config-path"); + } + + public void testReusableWorkflowOutputCompletionSuggestsWorkflowOutputs() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of(), Map.of("artifact", "Artifact path")); + + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + consume: + needs: call + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.call.outputs. }}" + """)).contains("artifact"); + } + + public void testReusableWorkflowSecretCompletionSuggestsWorkflowSecrets() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of(), Map.of(), Map.of("access-token", "Access token")); + + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + secrets: + + """)).contains("access-token"); + } + + public void testLocalActionInputCompletionUsesActionYamlMetadata() throws Exception { + final Path actionYaml = Files.createTempFile("github-workflow-local-action", ".yaml"); + actionYaml.toFile().deleteOnExit(); + Files.writeString(actionYaml, """ + name: Local Action + inputs: + local-input: + description: Local input + other-local-input: + description: Other local input + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + final GitHubAction action = GitHubAction.createGithubAction(true, "./.github/actions/local", actionYaml.toString()) + .isResolved(true); + getActionCache().getState().actions.put("./.github/actions/local", action); + assertThat(action.freshInputs()).containsKey("local-input"); + + configureWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: ./.github/actions/local + with: + + """); + + assertThat(completeBasicLookupStrings()).contains("local-input"); + } + + public void testProjectLocalActionInputCompletionUsesActionYamlMetadata() { + final GitHubAction action = seedLocalAction("./.github/actions/local", myFixture.addFileToProject(".github/actions/local/action.yml", """ + name: Local Action + inputs: + local-input: + description: Local input + runs: + using: composite + steps: + - run: echo ok + shell: sh + """)); + assertThat(action.freshInputs()).containsKey("local-input"); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: ./.github/actions/local + with: + + """); + + assertThat(completeBasicLookupStrings()).contains("local-input"); + } + + public void testLocalReusableWorkflowInputCompletionUsesWorkflowCallMetadata() { + final GitHubAction action = seedLocalAction("./.github/workflows/reusable.yml", myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Local Reusable + on: + workflow_call: + inputs: + config-path: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)); + assertThat(action.freshInputs()).containsKey("config-path"); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + call: + uses: ./.github/workflows/reusable.yml + with: + + """); + + assertThat(completeBasicLookupStrings()).contains("config-path"); + } + + public void testLocalReusableWorkflowOutputCompletionUsesWorkflowCallMetadata() { + final GitHubAction action = seedLocalAction("./.github/workflows/reusable.yml", myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Local Reusable + on: + workflow_call: + outputs: + artifact: + value: ${{ jobs.build.outputs.artifact }} + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """)); + assertThat(action.freshOutputs()).containsKey("artifact"); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + call: + uses: ./.github/workflows/reusable.yml + consume: + needs: call + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.call.outputs. }}" + """); + + assertThat(completeBasicLookupStrings()).contains("artifact"); + } + + public void testLocalReusableWorkflowSecretCompletionUsesWorkflowCallMetadata() { + final GitHubAction action = seedLocalAction("./.github/workflows/reusable.yml", myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Local Reusable + on: + workflow_call: + secrets: + access-token: + required: true + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)); + assertThat(action.freshSecrets()).containsKey("access-token"); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + call: + uses: ./.github/workflows/reusable.yml + secrets: + + """); + + assertThat(completeBasicLookupStrings()).contains("access-token"); + } + + public void testStepUsesCompletionSuggestsLocalActionDirectories() { + myFixture.addFileToProject(".github/actions/local/action.yml", """ + name: Local Action + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: + """); + + assertThat(completeBasicLookupStrings()).contains("./.github/actions/local"); + } + + public void testUsesCompletionSuggestsKnownRemoteCallableTargets() { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of()); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/ + """); + + assertThat(completeBasicLookupStrings()).contains("owner/tool"); + } + + public void testUsesCompletionDiscoversRemoteCallableTargetsBeforeResolution() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.setRepositories("actions", Map.of( + "checkout", "Checkout repository", + "setup-java", "Set up Java" + )); + RemoteServerSettings.getInstance().setCustomServers(List.of(new RemoteServerSettings.Server( + "Fake Enterprise", + server.webUrl(), + server.apiUrl("/api/v3"), + "", + true + ))); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/ + """); + + assertThat(completeBasicLookupStrings()).contains("actions/checkout", "actions/setup-java"); + } + } + + public void testUsesRefCompletionSuggestsKnownRemoteRefsFromCache() { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of()); + seedRemoteAction("owner/tool@main", Map.of(), Map.of()); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@ + """); + + assertThat(completeBasicLookupStrings()).contains("v1", "main"); + } + + public void testUsesRefCompletionDiscoversLatestRemoteRefsBeforeActionIsResolved() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.setTags("acme", "tool", List.of("v10", "v9", "v8", "v7", "v6", "v5", "v4", "v3", "v2", "v1", "v0")); + RemoteServerSettings.getInstance().setCustomServers(List.of(new RemoteServerSettings.Server( + "Fake Enterprise", + server.webUrl(), + server.apiUrl("/api/v3"), + "", + true + ))); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: acme/tool@ + """); + + assertThat(completeBasicLookupStrings()) + .contains("v10", "v9", "v8", "v7", "v6", "v5", "v4", "v3", "v2", "v1") + .doesNotContain("v0"); + } + } + + public void testEmptyWithBlockCompletionSuggestsResolvedActionInputs() { + seedRemoteAction("actions/checkout@v4", Map.of( + "fetch-depth", "Description: Number of commits to fetch\nDefault: 1", + "ref", "Description: Branch, tag, or SHA to checkout" + ), Map.of()); + + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + + """)).contains("fetch-depth", "ref"); + } + + public void testUsesRefCompletionSuggestsRefsAlreadyPresentInWorkflow() { + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + - uses: owner/tool@ + """); + + assertThat(completeBasicLookupStrings()).contains("v1"); + } + + public void testUsesRefCompletionSuggestsRefsResolvedFromConfiguredServer() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.addContent("acme", "tool", "action.yml", "v1", """ + name: Enterprise Tool + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + server.setBranches("acme", "tool", List.of("main")); + server.setTags("acme", "tool", List.of("v1")); + RemoteServerSettings.getInstance().setCustomServers(List.of(new RemoteServerSettings.Server("Fake", + server.webUrl(), + server.apiUrl("/api/v3"), + "", + true + ))); + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: acme/tool@v1 + - uses: acme/tool@ + """); + GitHubActionCache.getActionCache().get(getProject(), "acme/tool@v1").resolve(); + + assertThat(completeBasicLookupStrings()).contains("main", "v1"); + } + } + + public void testAbsoluteGithubServerUrlCompletionSuggestsRefsResolvedFromConfiguredServer() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.addContent("acme", "tool", "action.yml", "main", """ + name: Enterprise Tool + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + server.setBranches("acme", "tool", List.of("main")); + server.setTags("acme", "tool", List.of("v2")); + RemoteServerSettings.getInstance().setCustomServers(List.of(new RemoteServerSettings.Server("Fake Enterprise", + server.webUrl(), + server.apiUrl("/api/v3"), + "", + true + ))); + final String usesValue = server.webUrl() + "/acme/tool@main"; + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: %s + - uses: %s/acme/tool@ + """.formatted(usesValue, server.webUrl())); + GitHubActionCache.getActionCache().get(getProject(), usesValue).resolve(); + + assertThat(completeBasicLookupStrings()).contains("main", "v2"); + } + } + + public void testStepUsesCompletionDoesNotSuggestReusableWorkflows() { + myFixture.addFileToProject(".github/actions/local/action.yml", """ + name: Local Action + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Reusable + on: workflow_call + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: + """); + + assertThat(completeBasicLookupStrings()) + .contains("./.github/actions/local") + .doesNotContain("./.github/workflows/reusable.yml"); + } + + public void testJobUsesCompletionSuggestsLocalReusableWorkflowFiles() { + myFixture.addFileToProject(".github/actions/local/action.yml", """ + name: Local Action + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Reusable + on: workflow_call + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + call: + uses: + """); + + assertThat(completeBasicLookupStrings()) + .contains("./.github/workflows/reusable.yml") + .doesNotContain("./.github/actions/local"); + } + + public void testRootActionUsesCompletionSuggestsRepositoryAction() { + myFixture.addFileToProject("action.yml", """ + name: Root Action + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + + configureWorkflowProjectFile(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: + """); + + assertThat(completeBasicLookupStrings()).contains("./"); + } + + public void testRunCompletionSuggestsGithubEnvironmentFiles() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "x=y" >> "$" + """)).contains("GITHUB_ENV", "GITHUB_OUTPUT"); + } + + public void testRunCompletionSuggestsDefaultEnvironmentVariables() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "$" + """)).contains("GITHUB_TRIGGERING_ACTOR", "GITHUB_REPOSITORY_OWNER_ID", "RUNNER_ENVIRONMENT"); + } + + public void testPlainRunFieldDoesNotSuggestStepKeys() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: + """)).doesNotContain("uses", "with", "shell", "id"); + } + + public void testShellCompletionSuggestsSupportedGithubShells() { + assertThat(completeWorkflow(""" + name: Completion + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - shell: + run: echo ok + """)).contains("bash", "sh", "pwsh", "powershell", "cmd", "python"); + } + + private Map completeWorkflowTypeTexts(final String text) { + configureWorkflow(text); + final LookupElement[] elements = myFixture.completeBasic(); + assertThat(elements).isNotNull(); + return java.util.Arrays.stream(elements) + .collect(Collectors.toMap( + LookupElement::getLookupString, + WorkflowCompletionTest::typeText, + (left, right) -> left + )); + } + + private void writeProjectFile(final String relativePath, final String content) throws Exception { + final Path path = Path.of(getProject().getBasePath(), relativePath); + Files.createDirectories(path.getParent()); + Files.writeString(path, content); + } + + private static String typeText(final LookupElement element) { + final LookupElementPresentation presentation = new LookupElementPresentation(); + element.renderElement(presentation); + return presentation.getTypeText(); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolverTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolverTest.java new file mode 100644 index 0000000..58696d4 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolverTest.java @@ -0,0 +1,40 @@ +package com.github.yunabraska.githubworkflow.services; + +import junit.framework.TestCase; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowCurrentBranchResolverTest extends TestCase { + + public void testBranchNameReadsRefsHeadsBranch() { + assertThat(WorkflowCurrentBranchResolver.branchName("ref: refs/heads/feature/logs\n")) + .contains("feature/logs"); + } + + public void testBranchNameIgnoresDetachedHead() { + assertThat(WorkflowCurrentBranchResolver.branchName("e1a9e573f4d0838b3a7c1b07401aeb29ed3635a9")) + .isEmpty(); + } + + public void testResolveReadsCurrentBranchFromGitHead() throws Exception { + final Path dir = Files.createTempDirectory("workflow-branch"); + Files.createDirectories(dir.resolve(".git")); + Files.writeString(dir.resolve(".git").resolve("HEAD"), "ref: refs/heads/feature/current\n"); + + assertThat(new WorkflowCurrentBranchResolver().resolve(dir)) + .contains("feature/current"); + } + + public void testResolveReadsCurrentBranchFromWorktreeGitFile() throws Exception { + final Path dir = Files.createTempDirectory("workflow-worktree"); + final Path gitDir = Files.createDirectories(dir.resolve("real-git-dir")); + Files.writeString(dir.resolve(".git"), "gitdir: real-git-dir\n"); + Files.writeString(gitDir.resolve("HEAD"), "ref: refs/heads/worktree/current\n"); + + assertThat(new WorkflowCurrentBranchResolver().resolve(dir)) + .contains("worktree/current"); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java new file mode 100644 index 0000000..d9ca479 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java @@ -0,0 +1,66 @@ +package com.github.yunabraska.githubworkflow.services; + +import junit.framework.TestCase; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowDispatchInputsTest extends TestCase { + + public void testParseWorkflowDispatchInputsWithDefaults() { + final WorkflowDispatchInputs inputs = new WorkflowDispatchInputs(); + + assertThat(inputs.parse(""" + name: Dispatch + on: + workflow_dispatch: + inputs: + ref: + description: Branch + type: string + required: true + default: main + dry_run: + type: boolean + default: "true" + environment: + description: Target + type: choice + options: + - dev + - prod + jobs: + build: + runs-on: ubuntu-latest + """)).containsExactly( + new WorkflowDispatchInputs.Input("ref", "string", true, "main", "Branch"), + new WorkflowDispatchInputs.Input("dry_run", "boolean", false, "true", ""), + new WorkflowDispatchInputs.Input("environment", "choice", false, "", "Target", java.util.List.of("dev", "prod")) + ); + } + + public void testDefaultsTextBuildsPlainKeyValueLines() { + final WorkflowDispatchInputs inputs = new WorkflowDispatchInputs(); + + assertThat(inputs.defaultsText(""" + on: + workflow_dispatch: + inputs: + ref: + description: Branch + type: choice + required: true + default: main + options: [main, "release, candidate"] + """)).isEqualTo("ref=main\n"); + } + + public void testKeyValueInputTextIgnoresCommentsAndBlankLines() { + assertThat(WorkflowDispatchInputs.parseKeyValueText(""" + # ignored + ref=main + + dry_run=true + """)).containsEntry("ref", "main").containsEntry("dry_run", "true"); + } + +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationTest.java new file mode 100644 index 0000000..18088cd --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationTest.java @@ -0,0 +1,288 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.intellij.psi.PsiElement; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowDocumentationTest extends EditorFeatureTestCase { + + public void testUsesDocumentationShowsResolvedActionMetadata() { + final GitHubAction action = seedRemoteAction( + "actions/setup-java@v4", + Map.of("distribution", "Description: Java distribution\nRequired: true\nDefault: temurin"), + Map.of("cache-hit", "Description: Whether cache was restored") + ); + action.displayName("Setup Java") + .description("Set up a specific version of Java and add it to PATH."); + + configureWorkflowProjectFile(""" + name: Docs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-java@v4 + """); + + assertThat(documentationHintAtCaret()) + .contains("Setup Java") + .contains("actions/setup-java@v4"); + } + + public void testInputVariableDocumentationShowsMetadata() { + configureWorkflowProjectFile(""" + name: Docs + on: + workflow_dispatch: + inputs: + tag: + description: Release tag + required: true + type: string + default: v1 + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.tag }}" + """); + + assertThat(documentationHintAtCaret()) + .contains("Input tag") + .contains("Description: Release tag") + .contains("Type: string") + .contains("Required: true") + .contains("Default: v1"); + } + + public void testActionInputDocumentationShowsResolvedActionParameter() { + seedRemoteAction( + "actions/setup-java@v4", + Map.of("distribution", "Description: Java distribution\nRequired: true\nDefault: temurin"), + Map.of() + ); + + configureWorkflowProjectFile(""" + name: Docs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-java@v4 + with: + distribution: temurin + """); + + assertThat(documentationHintAtCaret()) + .contains("Input distribution") + .contains("Java distribution") + .contains("Required: true") + .contains("Default: temurin"); + } + + public void testActionInputHoverDocumentationShowsResolvedActionParameter() { + seedRemoteAction( + "actions/checkout@v4", + Map.of("fetch-depth", "Description: Number of commits to fetch\nDefault: 1"), + Map.of() + ); + + configureWorkflowProjectFile(""" + name: Docs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 500 + """); + + assertThat(documentationHtmlAtCaret()) + .contains("Input") + .contains("fetch-depth") + .contains("Number of commits to fetch") + .contains("Default"); + } + + public void testStepOutputDocumentationShowsOutputName() { + configureWorkflowProjectFile(""" + name: Docs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: java_info + run: echo "is_gradle=true" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.java_info.outputs.is_gradle }}" + """); + + assertThat(documentationHintAtCaret()).contains("Step output is_gradle"); + } + + public void testActionOutputDocumentationShowsResolvedDescription() { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of("artifact", "Description: Artifact path")); + + configureWorkflowProjectFile(""" + name: Docs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + uses: owner/tool@v1 + - run: echo "${{ steps.package.outputs.artifact }}" + """); + + assertThat(documentationHintAtCaret()) + .contains("Step output artifact") + .contains("Description: Artifact path"); + } + + public void testActionOutputDocumentationShowsSourceStepAndActionLink() { + final GitHubAction action = seedRemoteAction( + "YunaBraska/java-info-action@main", + Map.of(), + Map.of("project_version", "Description: Project version\nType: string") + ); + action.displayName("Java Info").description("Reads Java metadata."); + + configureWorkflowProjectFile(""" + name: Docs + on: workflow_dispatch + jobs: + tag: + runs-on: ubuntu-latest + steps: + - name: Read Java Info + id: java_info + uses: YunaBraska/java-info-action@main + - run: echo "${{ steps.java_info.outputs.project_version }}" + """); + + assertThat(documentationHintAtCaret()) + .contains("Step output project_version") + .contains("Description: Project version") + .contains("Step: Read Java Info (java_info)") + .contains("Uses: YunaBraska/java-info-action@main") + .contains("External action: Java Info - Reads Java metadata."); + assertThat(documentationHtmlAtCaret()) + .contains("href=\"https://github.com/YunaBraska/java-info-action/tree/main#readme\"") + .contains(">YunaBraska/java-info-action@main"); + } + + public void testJobOutputDocumentationShowsMappedStepActionOutput() { + final GitHubAction action = seedRemoteAction( + "YunaBraska/java-info-action@main", + Map.of(), + Map.of("java_version", "Description: Java version\nType: string") + ); + action.displayName("Java Info").description("Reads Java metadata."); + + configureWorkflowProjectFile(""" + name: Docs + on: + workflow_call: + outputs: + java_version: + description: "[String] java version from pom file" + value: ${{ jobs.tag.outputs.java_version }} + jobs: + tag: + runs-on: ubuntu-latest + outputs: + java_version: ${{ steps.java_info.outputs.java_version }} + steps: + - name: Read Java Info + id: java_info + uses: YunaBraska/java-info-action@main + """); + + assertThat(documentationHintAtCaret()) + .contains("Reusable workflow job output java_version") + .contains("Java Info") + .contains("Reads Java metadata"); + } + + public void testStepDocumentationShowsResolvedActionNameAndDescription() { + final GitHubAction action = seedRemoteAction("YunaBraska/java-info-action@main", Map.of(), Map.of()); + action.displayName("Java Info").description("Reads Java metadata."); + + configureWorkflowProjectFile(""" + name: Docs + on: workflow_dispatch + jobs: + tag: + runs-on: ubuntu-latest + steps: + - name: Read Java Info + id: java_info + uses: YunaBraska/java-info-action@main + - run: echo "${{ steps.java_info.outputs.java_version }}" + """); + + assertThat(documentationHtmlAtCaret()) + .contains("Step") + .contains("Read Java Info") + .contains("Java Info") + .contains("Reads Java metadata"); + } + + public void testExpressionContextDocumentationShowsCollectionMeaning() { + configureWorkflowProjectFile(""" + name: Docs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: java_info + run: echo "is_gradle=true" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.java_info.outputs.is_gradle }}" + """); + + assertThat(documentationHintAtCaret()).contains("Output values exposed"); + } + + private String documentationHintAtCaret() { + final WorkflowDocumentationProvider provider = new WorkflowDocumentationProvider(); + final PsiElement context = elementAtCaret(); + final PsiElement target = provider.getCustomDocumentationElement( + myFixture.getEditor(), + myFixture.getFile(), + context, + myFixture.getCaretOffset() + ); + assertThat(target).isNotNull(); + return provider.getQuickNavigateInfo(target, context); + } + + private String documentationHtmlAtCaret() { + final WorkflowDocumentationProvider provider = new WorkflowDocumentationProvider(); + final PsiElement context = elementAtCaret(); + final PsiElement target = provider.getCustomDocumentationElement( + myFixture.getEditor(), + myFixture.getFile(), + context, + myFixture.getCaretOffset() + ); + assertThat(target).isNotNull(); + return provider.generateHoverDoc(target, context); + } + + private PsiElement elementAtCaret() { + final PsiElement element = myFixture.getFile().findElementAt(myFixture.getCaretOffset()); + if (element != null) { + return element; + } + return myFixture.getFile().findElementAt(Math.max(0, myFixture.getCaretOffset() - 1)); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java new file mode 100644 index 0000000..897f7cc --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java @@ -0,0 +1,260 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.intellij.execution.lineMarker.RunLineMarkerContributor; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.icons.AllIcons; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.psi.YAMLKeyValue; + +import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.assertj.core.api.Assertions.assertThat; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_ENV; +import static com.github.yunabraska.githubworkflow.model.NodeIcon.ICON_TEXT_VARIABLE; +import static com.github.yunabraska.githubworkflow.services.GitHubActionCache.getActionCache; + +public class WorkflowGutterActionTest extends EditorFeatureTestCase { + + public void testSuppressActionQuickFixTogglesResolvedAction() { + final GitHubAction action = seedRemoteAction("owner/tool@v1", Map.of(), Map.of()); + configureWorkflowProjectFile(""" + name: Gutter + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + """); + + invokeHighlightFixContaining("Toggle warnings [off]"); + + assertThat(action.isSuppressed()).isTrue(); + } + + public void testSuppressInputQuickFixTogglesResolvedInput() { + final GitHubAction action = seedRemoteAction("owner/tool@v1", Map.of("known-input", "Known input"), Map.of()); + configureWorkflowProjectFile(""" + name: Gutter + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + known-input: ok + """); + + invokeHighlightFixContaining("known-input"); + + assertThat(action.ignoredInputs()).contains("known-input"); + } + + public void testJumpToFileQuickFixOpensLocalActionFile() { + final PsiFile actionFile = myFixture.addFileToProject(".github/actions/local/action.yml", """ + name: Local Action + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + seedLocalAction("./.github/actions/local", actionFile); + configureWorkflowProjectFile(""" + name: Gutter + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: ./.github/actions/local + """); + + assertThat(gutterIcons()).anySatisfy(gutter -> assertThat(gutter.getTooltipText()).contains("Jump to file")); + invokeHighlightFixContaining("Jump to file"); + } + + public void testRunOutputAndEnvDeclarationsKeepReferenceGutterIndicators() { + configureWorkflowProjectFile(""" + name: Gutter + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "ACTION_STATE=yellow" >> "$GITHUB_ENV" + - run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """); + + assertThat(gutterIcons().stream().map(gutter -> gutter.getIcon()).toList()) + .contains(ICON_ENV.icon(), ICON_TEXT_VARIABLE.icon()); + } + + public void testReloadRemoteActionQuickFixUsesResolverBoundaryWithoutSleeping() throws Exception { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of()); + final CountDownLatch resolved = new CountDownLatch(1); + final AtomicInteger calls = new AtomicInteger(0); + final GitHubActionCache.ActionResolver previous = getActionCache().useActionResolverForTests(action -> { + calls.incrementAndGet(); + action.displayName("Reloaded Tool").isResolved(true); + resolved.countDown(); + return action; + }); + try { + configureWorkflowProjectFile(""" + name: Gutter + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + """); + + invokeHighlightFixContaining("Reload [owner/tool]"); + + assertThat(resolved.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(calls).hasValue(1); + assertThat(getActionCache().getState().actions.get("owner/tool@v1").displayName()).isEqualTo("Reloaded Tool"); + } finally { + getActionCache().useActionResolverForTests(previous); + } + } + + public void testResolvedActionFixesStayOutOfTheEditorGutter() { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of()).remoteRefs(List.of("v1", "v2")); + configureWorkflowProjectFile(""" + name: Gutter + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + """); + + final List tooltips = gutterIcons().stream() + .map(gutter -> gutter.getTooltipText() == null ? "" : gutter.getTooltipText()) + .filter(tooltip -> tooltip.contains("owner/tool")) + .toList(); + + assertThat(tooltips).isEmpty(); + } + + public void testWorkflowDispatchShowsRunLineMarker() throws Exception { + configureWorkflowProjectFile(""" + name: Gutter + on: + workflow_dispatch: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + final WorkflowRunLineMarkerContributor.RepositoryAvailability previous = + WorkflowRunLineMarkerContributor.useRepositoryAvailabilityForTests((project, file) -> true); + try { + final YAMLKeyValue dispatch = PsiTreeUtil.findChildrenOfType(myFixture.getFile(), YAMLKeyValue.class) + .stream() + .filter(keyValue -> "workflow_dispatch".equals(keyValue.getKeyText())) + .findFirst() + .orElseThrow(); + + final RunLineMarkerContributor.Info info = new WorkflowRunLineMarkerContributor().getInfo(dispatch.getKey()); + + assertThat(info).isNotNull(); + assertThat(info.actions).isNotEmpty(); + } finally { + WorkflowRunLineMarkerContributor.useRepositoryAvailabilityForTests(previous); + } + } + + public void testWorkflowDispatchDoesNotShowRunLineMarkerWithoutGitRepository() { + configureWorkflowProjectFile(""" + name: Gutter + on: + workflow_dispatch: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + final YAMLKeyValue dispatch = PsiTreeUtil.findChildrenOfType(myFixture.getFile(), YAMLKeyValue.class) + .stream() + .filter(keyValue -> "workflow_dispatch".equals(keyValue.getKeyText())) + .findFirst() + .orElseThrow(); + + final RunLineMarkerContributor.Info info = new WorkflowRunLineMarkerContributor().getInfo(dispatch.getKey()); + + assertThat(info).isNull(); + } + + public void testWorkflowDispatchLineMarkerSwitchesToStopWhenRunIsTracked() { + configureWorkflowProjectFile(""" + name: Gutter + on: + workflow_dispatch: + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + final String workflowPath = WorkflowRunConfigurationProducer.workflowPath(getProject(), myFixture.getFile().getVirtualFile()) + .orElseThrow(); + final DummyProcessHandler processHandler = new DummyProcessHandler(); + WorkflowRunTracker.getInstance(getProject()).register(workflowPath, processHandler); + try { + final YAMLKeyValue dispatch = PsiTreeUtil.findChildrenOfType(myFixture.getFile(), YAMLKeyValue.class) + .stream() + .filter(keyValue -> "workflow_dispatch".equals(keyValue.getKeyText())) + .findFirst() + .orElseThrow(); + + final RunLineMarkerContributor.Info info = new WorkflowRunLineMarkerContributor().getInfo(dispatch.getKey()); + + assertThat(info).isNotNull(); + assertThat(info.icon).isEqualTo(AllIcons.Actions.Suspend); + assertThat(info.actions).singleElement() + .satisfies(action -> assertThat(action.getTemplatePresentation().getText()).isEqualTo("Stop workflow run")); + } finally { + WorkflowRunTracker.getInstance(getProject()).unregister(workflowPath, processHandler); + } + } + + private static final class DummyProcessHandler extends ProcessHandler { + + @Override + protected void destroyProcessImpl() { + notifyProcessTerminated(1); + } + + @Override + protected void detachProcessImpl() { + notifyProcessDetached(); + } + + @Override + public boolean detachIsDefault() { + return false; + } + + @Override + public @Nullable OutputStream getProcessInput() { + return null; + } + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java new file mode 100644 index 0000000..04d9c07 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java @@ -0,0 +1,2124 @@ +package com.github.yunabraska.githubworkflow.services; + +import java.util.Map; + +public class WorkflowHighlightingTest extends EditorFeatureTestCase { + + public void testUnknownTopLevelWorkflowKeyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + jobz: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnknownWorkflowEventKeyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: + pull_requestz: + branches: [main] + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnknownTriggerFilterKeyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: + push: + branchz: [main] + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnknownTriggerActivityTypeIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: + workflow_run: + types: teleported + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testScheduleRejectsBranchFilters() { + assertWorkflowHighlights(""" + name: Syntax + on: + schedule: + - cron: '0 0 * * *' + - branches: [main] + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnknownJobKeyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + stepz: + - run: echo ok + """); + } + + public void testUnknownStepKeyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - shellz: sh + run: echo ok + """); + } + + public void testUnknownConcurrencyKeyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: workflow_dispatch + concurrency: + group: release + cancelled-by-committee: true + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnknownEnvironmentKeyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: workflow_dispatch + jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: production + portal: https://example.com + steps: + - run: echo ok + """); + } + + public void testUnknownContainerCredentialKeyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/example/app:latest + credentials: + username: octo + token: hush + steps: + - run: echo ok + """); + } + + public void testUnknownServiceCredentialKeyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + database: + image: postgres:16 + credentials: + username: octo + token: hush + steps: + - run: echo ok + """); + } + + public void testUnknownPermissionScopeIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: workflow_dispatch + permissions: + contentz: read + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnknownWorkflowDispatchInputPropertyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: + workflow_dispatch: + inputs: + target: + type: choice + wizardry: quiet + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnknownWorkflowDispatchInputTypeIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: + workflow_dispatch: + inputs: + target: + type: potato + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testWorkflowCallInputRejectsDispatchOnlyTypes() { + assertWorkflowHighlights(""" + name: Syntax + on: + workflow_call: + inputs: + target: + type: choice + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testWorkflowInputRequiredRejectsNonBooleanValue() { + assertWorkflowHighlights(""" + name: Syntax + on: + workflow_dispatch: + inputs: + target: + required: maybe + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnknownWorkflowCallOutputPropertyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: + workflow_call: + outputs: + image: + value: ${{ jobs.build.outputs.image }} + artifact: docker + jobs: + build: + runs-on: ubuntu-latest + outputs: + image: ${{ steps.meta.outputs.image }} + steps: + - id: meta + run: echo "image=demo" >> "$GITHUB_OUTPUT" + """); + } + + public void testUnknownWorkflowCallSecretPropertyIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: + workflow_call: + secrets: + token: + required: true + vault: no + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnknownPermissionValueIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: workflow_dispatch + permissions: + contents: writer + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testRestrictedPermissionValueIsHighlighted() { + assertWorkflowHighlights(""" + name: Syntax + on: workflow_dispatch + permissions: + id-token: read + models: write + vulnerability-alerts: write + artifact-metadata: write + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testResolvedActionInputIsAccepted() { + seedRemoteAction("owner/tool@v1", Map.of("known-input", "Known input"), Map.of()); + + assertWorkflowHighlights(""" + name: Action Input + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + known-input: ok + """); + } + + public void testUnknownActionInputIsHighlighted() { + seedRemoteAction("owner/tool@v1", Map.of("known-input", "Known input"), Map.of()); + + assertWorkflowHighlights(""" + name: Action Input + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + wrong-input: no + """); + } + + public void testUnresolvedRemoteActionMessageMentionsCommonFailureModes() { + assertWorkflowHighlights(""" + name: Remote Action + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: missing/tool@v1 + """); + } + + public void testReusableWorkflowInputIsAccepted() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of("config-path", "Config path"), Map.of()); + + assertWorkflowHighlights(""" + name: Reusable Workflow + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + with: + config-path: .github/labeler.yml + """); + } + + public void testUnknownReusableWorkflowInputIsHighlighted() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of("config-path", "Config path"), Map.of()); + + assertWorkflowHighlights(""" + name: Reusable Workflow + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + with: + wrong-input: value + """); + } + + public void testReusableWorkflowSecretIsAccepted() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of(), Map.of(), Map.of("access-token", "Access token")); + + assertWorkflowHighlights(""" + name: Reusable Workflow + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + secrets: + access-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + """); + } + + public void testUnknownReusableWorkflowSecretIsHighlighted() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of(), Map.of(), Map.of("access-token", "Access token")); + + assertWorkflowHighlights(""" + name: Reusable Workflow + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + secrets: + wrong-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + """); + } + + public void testReusableWorkflowInheritSecretsIsAccepted() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of(), Map.of(), Map.of("access-token", "Access token")); + + assertWorkflowHighlights(""" + name: Reusable Workflow + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + secrets: inherit + """); + } + + public void testLocalReusableWorkflowSecretIsAccepted() { + seedLocalAction("./.github/workflows/reusable.yml", myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Local Reusable + on: + workflow_call: + secrets: + access-token: + required: true + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)); + + configureWorkflowProjectFile(""" + name: Reusable Workflow + on: workflow_dispatch + jobs: + call: + uses: ./.github/workflows/reusable.yml + secrets: + access-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + """); + myFixture.checkHighlighting(true, false, true); + } + + public void testReusableWorkflowOutputReferenceIsAccepted() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of(), Map.of("artifact", "Artifact path")); + + assertWorkflowHighlights(""" + name: Reusable Workflow + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + consume: + needs: call + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.call.outputs.artifact }}" + """); + } + + public void testLocalReusableWorkflowOutputReferenceIsAccepted() { + seedLocalAction("./.github/workflows/reusable.yml", myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Local Reusable + on: + workflow_call: + outputs: + artifact: + value: ${{ jobs.build.outputs.artifact }} + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """)); + + configureWorkflowProjectFile(""" + name: Reusable Workflow + on: workflow_dispatch + jobs: + call: + uses: ./.github/workflows/reusable.yml + consume: + needs: call + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.call.outputs.artifact }}" + """); + myFixture.checkHighlighting(true, false, true); + } + + public void testUnknownReusableWorkflowOutputIsHighlighted() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of(), Map.of("artifact", "Artifact path")); + + assertWorkflowHighlights(""" + name: Reusable Workflow + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + consume: + needs: call + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.call.outputs.missing }}" + """); + } + + public void testWorkflowCallInputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Inputs + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.known-input }}" + """); + } + + public void testWorkflowDispatchInputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Inputs + on: + workflow_dispatch: + inputs: + deploy-target: + type: choice + options: + - staging + - prod + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.deploy-target }}" + """); + } + + public void testUnknownInputReferenceIsHighlighted() { + assertWorkflowHighlights(""" + name: Inputs + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.missing }}" + """); + } + + public void testRunNameInputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Inputs + run-name: Deploy ${{ inputs.environment }} + on: + workflow_dispatch: + inputs: + environment: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testRunNameUnknownInputReferenceIsHighlighted() { + assertWorkflowHighlights(""" + name: Inputs + run-name: Deploy ${{ inputs.missing }} + on: + workflow_dispatch: + inputs: + environment: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testIncompleteInputExpressionIsHighlighted() { + assertWorkflowHighlights(""" + name: Inputs + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs. }}" + """); + } + + public void testFoldedScalarUnknownInputReferenceIsHighlighted() { + assertWorkflowHighlights(""" + name: Inputs + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: > + echo "${{ inputs.missing }}" + """); + } + + public void testMultilineExpressionInputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Inputs + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: | + echo "${{ + inputs.known-input + }}" + """); + } + + public void testWorkflowCallSecretReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Secrets + on: + workflow_call: + secrets: + SECRET_TOKEN: + required: false + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ secrets.SECRET_TOKEN }}" + """); + } + + public void testAutomaticGithubTokenSecretReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Secrets + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ secrets.GITHUB_TOKEN }}" + """); + } + + public void testUnknownWorkflowCallSecretIsWeakWarning() { + assertWorkflowHighlights(""" + name: Secrets + on: + workflow_call: + secrets: + SECRET_TOKEN: + required: false + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ secrets.MISSING_SECRET }}" + """); + } + + public void testSecretInIfConditionIsError() { + assertWorkflowHighlights(""" + name: Secrets + on: + workflow_call: + secrets: + SECRET_TOKEN: + required: false + jobs: + build: + runs-on: ubuntu-latest + steps: + - if: secrets.SECRET_TOKEN != '' + run: echo ok + """); + } + + public void testWorkflowEnvReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Env + on: workflow_dispatch + env: + TOP_LEVEL: top + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ env.TOP_LEVEL }}" + """); + } + + public void testJobEnvReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Env + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + env: + JOB_LEVEL: job + steps: + - run: echo "${{ env.JOB_LEVEL }}" + """); + } + + public void testStepEnvReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Env + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - env: + STEP_LEVEL: step + run: echo "${{ env.STEP_LEVEL }}" + """); + } + + public void testAliasBackedEnvReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Env + on: workflow_dispatch + env: + SHARED: &shared shared-value + COPIED: *shared + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ env.COPIED }}" + """); + } + + public void testJobEnvMapAliasReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Env + on: workflow_dispatch + jobs: + define: + runs-on: ubuntu-latest + env: &env_vars + NODE_ENV: production + steps: + - run: echo ok + reuse: + runs-on: ubuntu-latest + env: *env_vars + steps: + - run: echo "${{ env.NODE_ENV }}" + """); + } + + public void testRunEnvFromBashFileCommandIsAcceptedInLaterStep() { + assertWorkflowHighlights(""" + name: Env + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "ACTION_STATE=yellow" >> "$GITHUB_ENV" + - run: echo "${{ env.ACTION_STATE }}" + """); + } + + public void testRunEnvFromPowerShellFileCommandIsAcceptedInLaterStep() { + assertWorkflowHighlights(""" + name: Env + on: workflow_dispatch + jobs: + build: + runs-on: windows-latest + steps: + - shell: pwsh + run: | + "ACTION_STATE=yellow" >> $env:GITHUB_ENV + - run: echo "${{ env.ACTION_STATE }}" + """); + } + + public void testRunEnvFromMultilineFileCommandIsAcceptedInLaterStep() { + assertWorkflowHighlights(""" + name: Env + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: | + { + echo 'JSON_RESPONSE<> "$GITHUB_ENV" + - run: echo "${{ env.JSON_RESPONSE }}" + """); + } + + public void testGithubContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Github Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.ref_name }}" + """); + } + + public void testGithubActorIdContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Github Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.actor_id }}" + """); + } + + public void testGithubRefProtectedContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Github Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.ref_protected }}" + """); + } + + public void testGithubRepositoryOwnerIdContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Github Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.repository_owner_id }}" + """); + } + + public void testRunnerDebugContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Runner Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ runner.debug }}" + """); + } + + public void testGiteaContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Gitea Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ gitea.ref_name }}" + """); + } + + public void testGithubEventNestedContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Github Context + on: pull_request + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.event.pull_request.number }}" + """); + } + + public void testUnknownGithubContextIsHighlighted() { + assertWorkflowHighlights(""" + name: Github Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github.missing }}" + """); + } + + public void testLiteralGithubUrlInsideRunBlockIsNotTreatedAsExpression() { + assertWorkflowHighlights(""" + name: Github Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: | + echo "https://github.com/YunaBraska/github-workflow-plugin" + printf -- '- https://github.com/%s/compare/%s...%s\\n' "$REPOSITORY" "$OLD_TAG" "$TAG_NAME" + """); + } + + public void testJobContainerNetworkContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Job Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + container: postgres:16 + steps: + - run: echo "${{ job.container.network }}" + """); + } + + public void testJobServicePortContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Job Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + ports: + - 5432/tcp + steps: + - run: echo "${{ job.services.postgres.ports[5432] }}" + """); + } + + public void testUnknownJobContainerMemberIsHighlighted() { + assertWorkflowHighlights(""" + name: Job Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + container: postgres:16 + steps: + - run: echo "${{ job.container.bad }}" + """); + } + + public void testUnknownJobServiceIdIsHighlighted() { + assertWorkflowHighlights(""" + name: Job Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + steps: + - run: echo "${{ job.services.redis.network }}" + """); + } + + public void testUnknownJobServiceMemberIsHighlighted() { + assertWorkflowHighlights(""" + name: Job Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + steps: + - run: echo "${{ job.services.postgres.bad }}" + """); + } + + public void testUnknownJobServicePortIsHighlighted() { + assertWorkflowHighlights(""" + name: Job Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + ports: + - 5432/tcp + steps: + - run: echo "${{ job.services.postgres.ports[6379] }}" + """); + } + + public void testRunnerContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Runner Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ runner.os }}" + """); + } + + public void testRunnerEnvironmentContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Runner Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ runner.environment }}" + """); + } + + public void testUnknownRunnerContextIsHighlighted() { + assertWorkflowHighlights(""" + name: Runner Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ runner.missing }}" + """); + } + + public void testRunnerExpressionWithInvalidSuffixIsHighlighted() { + assertWorkflowHighlights(""" + name: Runner Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ runner.os.extra }}" + """); + } + + public void testStepOutputFromBashFileCommandIsAccepted() { + assertWorkflowHighlights(""" + name: Step Outputs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.package.outputs.artifact }}" + """); + } + + public void testHyphenatedStepOutputFromBashFileCommandIsAccepted() { + assertWorkflowHighlights(""" + name: Step Outputs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact-path=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.package.outputs.artifact-path }}" + """); + } + + public void testStepOutputFromPowerShellFileCommandIsAccepted() { + assertWorkflowHighlights(""" + name: Step Outputs + on: workflow_dispatch + jobs: + build: + runs-on: windows-latest + steps: + - id: package + shell: pwsh + run: | + "artifact=dist" >> $env:GITHUB_OUTPUT + - run: echo "${{ steps.package.outputs.artifact }}" + """); + } + + public void testStepOutputFromMultilineFileCommandIsAccepted() { + assertWorkflowHighlights(""" + name: Step Outputs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: | + { + echo 'artifact-json<> "$GITHUB_OUTPUT" + - run: echo "${{ steps.package.outputs.artifact-json }}" + """); + } + + public void testIssue79MultilineStepOutputFromGroupedEchoIsAccepted() { + seedRemoteAction("azure/login@v2", Map.of("creds", "Azure credentials"), Map.of()); + + assertWorkflowHighlights(""" + name: Issue 79 + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: creds + run: | + CREDS=$(cat <> "$GITHUB_OUTPUT" + - uses: azure/login@v2 + with: + creds: ${{ steps.creds.outputs.CREDS }} + """); + } + + public void testIssue73StepOutputFromTeePipeIsAccepted() { + assertWorkflowHighlights(""" + name: Issue 73 + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: version + run: echo "dev_version=$DEV_VERSION" | tee -a $GITHUB_OUTPUT $GITHUB_STEP_SUMMARY + - run: echo "${{ steps.version.outputs.dev_version }}" + """); + } + + public void testStepOutputFromActionMetadataIsAccepted() { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of("artifact", "Artifact path")); + + assertWorkflowHighlights(""" + name: Step Outputs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + uses: owner/tool@v1 + - run: echo "${{ steps.package.outputs.artifact }}" + """); + } + + public void testIssue76CompositeActionCanReferencePreviousActionOutputs() { + seedRemoteAction( + "actions/cache@v4", + Map.of("path", "Cache paths", "key", "Cache key"), + Map.of("cache-hit", "Whether the cache was restored") + ); + + assertWorkflowHighlights(""" + name: Build application + description: Build application + inputs: + working_dir: + description: Working directory + required: false + default: ./ + runs: + using: composite + steps: + - uses: actions/cache@v4 + id: cache-gradle + with: + path: ${{ inputs.working_dir }}/.gradle + key: gradle-${{ runner.os }} + - if: steps.cache-gradle.outputs.cache-hit != 'true' + run: echo build + shell: sh + """); + } + + public void testCompositeActionInputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Composite Action + inputs: + local-input: + description: Local input + runs: + using: composite + steps: + - run: echo "${{ inputs.local-input }}" + shell: sh + """); + } + + public void testCompositeActionOutputStepReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Composite Action + outputs: + artifact: + value: ${{ steps.package.outputs.artifact }} + runs: + using: composite + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + shell: sh + """); + } + + public void testCompositeActionUnknownOutputStepReferenceIsHighlighted() { + assertWorkflowHighlights(""" + name: Composite Action + outputs: + artifact: + value: ${{ steps.missing.outputs.artifact }} + runs: + using: composite + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + shell: sh + """); + } + + public void testUnknownStepIdIsHighlighted() { + assertWorkflowHighlights(""" + name: Step Outputs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.missing.outputs.artifact }}" + """); + } + + public void testUnknownStepOutputIsHighlighted() { + assertWorkflowHighlights(""" + name: Step Outputs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.package.outputs.missing }}" + """); + } + + public void testInvalidStepMemberIsHighlighted() { + assertWorkflowHighlights(""" + name: Step Outputs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.package.bad.artifact }}" + """); + } + + public void testStepOutcomeIsAccepted() { + assertWorkflowHighlights(""" + name: Step State + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo ok + - run: echo "${{ steps.package.outcome }}" + """); + } + + public void testStepConclusionIsAccepted() { + assertWorkflowHighlights(""" + name: Step State + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo ok + - run: echo "${{ steps.package.conclusion }}" + """); + } + + public void testNeedsScalarAcceptsPreviousJob() { + assertWorkflowHighlights(""" + name: Needs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testNeedsArrayHighlightsUnknownJob() { + assertWorkflowHighlights(""" + name: Needs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: [ build, missing_job ] + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testNeedsOutputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Needs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build.outputs.artifact }}" + """); + } + + public void testNeedsResultReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Needs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build.result }}" + """); + } + + public void testIssue77NeedsResultInsideFunctionConditionIsAccepted() { + assertWorkflowHighlights(""" + name: Issue 77 + on: push + jobs: + ci-passed: + runs-on: ubuntu-latest + steps: + - run: echo ok + release: + needs: ci-passed + if: always() && startsWith(github.ref, 'refs/tags/') && needs.ci-passed.result == 'success' + runs-on: ubuntu-latest + steps: + - run: echo release + """); + } + + public void testUnknownNeedsOutputIsHighlighted() { + assertWorkflowHighlights(""" + name: Needs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build.outputs.artifact }}" + - run: echo "${{ needs.build.outputs.missing }}" + """); + } + + public void testInvalidNeedsMemberIsHighlighted() { + assertWorkflowHighlights(""" + name: Needs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build.bad }}" + """); + } + + public void testJobsWorkflowOutputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Reusable Outputs + on: + workflow_call: + outputs: + artifact: + value: ${{ jobs.build.outputs.artifact }} + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """); + } + + public void testJobsResultReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Reusable Outputs + on: + workflow_call: + outputs: + build-result: + value: ${{ jobs.build.result }} + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testInvalidJobsMemberIsHighlighted() { + assertWorkflowHighlights(""" + name: Reusable Outputs + on: + workflow_call: + outputs: + artifact: + value: ${{ jobs.build.bad }} + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testUnusedJobOutputIsWeakWarning() { + assertWorkflowHighlights(""" + name: Job Outputs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """); + } + + public void testUsedJobOutputIsAccepted() { + assertWorkflowHighlights(""" + name: Job Outputs + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build.outputs.artifact }}" + """); + } + + public void testIssue46JobOutputUsedInsideFromJsonIsAccepted() { + assertWorkflowHighlights(""" + name: Issue 46 + on: workflow_dispatch + jobs: + list-folders: + runs-on: ubuntu-latest + outputs: + folders: ${{ steps.list.outputs.folders }} + steps: + - id: list + run: echo "folders=[]" >> "$GITHUB_OUTPUT" + update-all: + runs-on: ubuntu-latest + needs: list-folders + strategy: + matrix: + folder: ${{ fromJson(needs.list-folders.outputs.folders) }} + steps: + - run: echo "${{ matrix.folder }}" + """); + } + + public void testJobStatusContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Job Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ job.status }}" + """); + } + + public void testStrategyContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Strategy Context + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - run: echo "${{ strategy.job-index }}" + """); + } + + public void testMatrixContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Matrix Context + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - run: echo "${{ matrix.os }}" + """); + } + + public void testRunsOnMatrixContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Matrix Context + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - run: echo ok + """); + } + + public void testRunsOnSequenceMatrixContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Matrix Context + on: workflow_dispatch + jobs: + build: + runs-on: [self-hosted, "${{ matrix.os }}"] + strategy: + matrix: + os: [linux] + steps: + - run: echo ok + """); + } + + public void testRunsOnUnknownMatrixContextIsHighlighted() { + assertWorkflowHighlights(""" + name: Matrix Context + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.missing }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - run: echo ok + """); + } + + public void testMatrixIncludeContextReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Matrix Context + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + node: 25 + steps: + - run: echo "${{ matrix.node }}" + """); + } + + public void testTopLevelConcurrencyInputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Concurrency + on: + workflow_dispatch: + inputs: + environment: + type: string + concurrency: deploy-${{ inputs.environment }} + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testJobConcurrencyGroupInputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Concurrency + on: + workflow_dispatch: + inputs: + environment: + type: string + jobs: + build: + concurrency: + group: deploy-${{ inputs.environment }} + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testEnvironmentUrlStepOutputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Environment + on: workflow_dispatch + jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: production + url: ${{ steps.deploy.outputs.url }} + steps: + - id: deploy + run: echo "url=https://example.com" >> "$GITHUB_OUTPUT" + """); + } + + public void testStepContinueOnErrorMatrixReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Step Controls + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + experimental: [true] + steps: + - continue-on-error: ${{ matrix.experimental }} + run: echo ok + """); + } + + public void testStepTimeoutEnvReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Step Controls + on: workflow_dispatch + env: + TIMEOUT: 10 + jobs: + build: + runs-on: ubuntu-latest + steps: + - timeout-minutes: ${{ env.TIMEOUT }} + run: echo ok + """); + } + + public void testUnknownStrategyContextIsHighlighted() { + assertWorkflowHighlights(""" + name: Strategy Context + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - run: echo "${{ strategy.missing }}" + """); + } + + public void testUnknownJobContextIsHighlighted() { + assertWorkflowHighlights(""" + name: Job Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ job.missing }}" + """); + } + + public void testUnknownMatrixContextIsHighlighted() { + assertWorkflowHighlights(""" + name: Matrix Context + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - run: echo "${{ matrix.missing }}" + """); + } + + public void testBracketNotationInputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Brackets + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs['known-input'] }}" + """); + } + + public void testDoubleQuotedBracketNotationInputReferenceIsAccepted() { + assertWorkflowHighlights(""" + name: Brackets + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs[\\"known-input\\"] }}" + """); + } + + public void testBracketNotationUnknownInputIsHighlighted() { + assertWorkflowHighlights(""" + name: Brackets + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs['missing'] }}" + """); + } + + public void testBracketNotationGithubContextIsAccepted() { + assertWorkflowHighlights(""" + name: Brackets + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github['ref_name'] }}" + """); + } + + public void testBracketNotationUnknownGithubContextIsHighlighted() { + assertWorkflowHighlights(""" + name: Brackets + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ github['missing'] }}" + """); + } + + public void testBracketNotationStepOutputIsAccepted() { + assertWorkflowHighlights(""" + name: Brackets + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps['package'].outputs['artifact'] }}" + """); + } + + public void testBracketNotationUnknownStepIdIsHighlighted() { + assertWorkflowHighlights(""" + name: Brackets + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps['missing'].outputs['artifact'] }}" + """); + } + + public void testBracketNotationUnknownStepOutputIsHighlighted() { + assertWorkflowHighlights(""" + name: Brackets + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps['package'].outputs['missing'] }}" + """); + } + + public void testBracketNotationNeedsResultIsAccepted() { + assertWorkflowHighlights(""" + name: Brackets + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs['build'].result }}" + """); + } + + public void testVarsContextIsAcceptedWithoutLocalValidation() { + assertWorkflowHighlights(""" + name: Vars Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ vars.DEPLOY_TARGET }}" + """); + } + + public void testBracketVarsContextIsAcceptedWithoutLocalValidation() { + assertWorkflowHighlights(""" + name: Vars Context + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ vars['DEPLOY_TARGET'] }}" + """); + } + + public void testWorkflowCallInputDefaultExpressionIsValidated() { + assertWorkflowHighlights(""" + name: Workflow Call Defaults + on: + workflow_call: + inputs: + known-input: + type: string + target: + type: string + default: ${{ inputs.missing }} + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + } + + public void testJobContainerScalarExpressionIsValidated() { + assertWorkflowHighlights(""" + name: Container Scalar + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + container: ${{ inputs.missing }} + steps: + - run: echo ok + """); + } + + public void testJobContainerCredentialsExpressionIsValidated() { + assertWorkflowHighlights(""" + name: Container Credentials + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/owner/image + credentials: + username: ${{ inputs.missing }} + steps: + - run: echo ok + """); + } + + public void testJobEnvironmentScalarExpressionIsValidated() { + assertWorkflowHighlights(""" + name: Environment Scalar + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + environment: ${{ inputs.missing }} + steps: + - run: echo ok + """); + } + + public void testJobStrategyFailFastExpressionIsValidated() { + assertWorkflowHighlights(""" + name: Strategy Fail Fast + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: ${{ inputs.missing }} + matrix: + os: [ubuntu-latest] + steps: + - run: echo ok + """); + } + + public void testJobStrategyMaxParallelExpressionIsValidated() { + assertWorkflowHighlights(""" + name: Strategy Max Parallel + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: ${{ inputs.missing }} + matrix: + os: [ubuntu-latest] + steps: + - run: echo ok + """); + } + + public void testJobDefaultsRunShellExpressionIsValidated() { + assertWorkflowHighlights(""" + name: Defaults Run Shell + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + shell: ${{ inputs.missing }} + steps: + - run: echo ok + """); + } + + public void testJobServiceCredentialsExpressionIsValidated() { + assertWorkflowHighlights(""" + name: Service Credentials + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + services: + registry: + image: ghcr.io/owner/image + credentials: + username: ${{ inputs.missing }} + steps: + - run: echo ok + """); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowPerformanceTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowPerformanceTest.java new file mode 100644 index 0000000..d4b866a --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowPerformanceTest.java @@ -0,0 +1,49 @@ +package com.github.yunabraska.githubworkflow.services; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowPerformanceTest extends EditorFeatureTestCase { + + public void testLargeWorkflowHighlightingCompletesWithinBoundedTime() { + configureWorkflowProjectFile(largeWorkflow()); + + final long started = System.nanoTime(); + myFixture.doHighlighting(); + final long elapsedMillis = (System.nanoTime() - started) / 1_000_000L; + + assertThat(elapsedMillis).isLessThan(10_000L); + } + + private static String largeWorkflow() { + final StringBuilder workflow = new StringBuilder(""" + name: Large Workflow + on: + workflow_call: + inputs: + deploy-target: + type: string + env: + TOP_LEVEL: production + jobs: + """); + for (int job = 0; job < 40; job++) { + workflow.append(" build_").append(job).append(":\n"); + if (job > 0) { + workflow.append(" needs: build_").append(job - 1).append("\n"); + } + workflow.append(""" + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ inputs.deploy-target }} ${{ env.TOP_LEVEL }} ${{ matrix.os }} ${{ steps.package.outputs.artifact }}" + """); + } + return workflow.toString(); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java new file mode 100644 index 0000000..c15ffc1 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java @@ -0,0 +1,433 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.util.Iconable; +import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +public class WorkflowQuickFixTest extends EditorFeatureTestCase { + + public void testUnknownActionInputProvidesDeleteQuickFix() { + seedRemoteAction("owner/tool@v1", Map.of("known-input", "Known input"), Map.of()); + + assertThat(quickFixTexts(""" + name: Quick Fixes + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + wrong-input: no + """)).contains("Delete invalid input [wrong-input]"); + } + + public void testInspectionQuickFixTextsUseConfiguredPluginLanguage() { + final PluginSettings settings = PluginSettings.getInstance(); + final String previousLanguage = settings.languageTag(); + try { + settings.languageTag("de"); + seedRemoteAction("owner/tool@v1", Map.of("known-input", "Known input"), Map.of()); + + assertThat(quickFixTexts(""" + name: Quick Fixes + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + wrong-input: no + """)) + .anyMatch(text -> text.contains("Neu laden [owner/tool]")) + .anyMatch(text -> text.contains("Warnungen umschalten [aus] fรผr [owner/tool]")) + .anyMatch(text -> text.contains("Ungรผltig: Eingabe [wrong-input] lรถschen")); + } finally { + settings.languageTag(previousLanguage); + } + } + + public void testPluginQuickFixesExposeIcons() { + seedRemoteAction("owner/tool@v1", Map.of("known-input", "Known input"), Map.of()); + configureWorkflow(""" + name: Quick Fixes + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + wrong-input: no + """); + myFixture.doHighlighting(); + + assertQuickFixHasIcon("Reload [owner/tool]"); + assertQuickFixHasIcon("Toggle warnings [off] for [owner/tool]"); + assertQuickFixHasIcon("Toggle warnings [off] for [wrong-input]"); + assertQuickFixHasIcon("Delete invalid input [wrong-input]"); + } + + public void testReferenceQuickFixesExposeIcons() { + configureWorkflow(""" + name: Quick Fixes + on: + workflow_call: + inputs: + known-input: + type: string + secrets: + SECRET_TOKEN: + required: false + jobs: + build: + runs-on: ubuntu-latest + steps: + - if: secrets.SECRET_TOKEN != '' + run: echo "${{ inputs.missing }} ${{ runner.os.extra }}" + """); + myFixture.doHighlighting(); + + assertQuickFixHasIcon("Replace with [known-input]"); + assertQuickFixHasIcon("Remove [secrets.SECRET_TOKEN] - Secrets are not valid in `if` statements"); + assertQuickFixHasIcon("Remove invalid suffix [extra]"); + } + + public void testUnknownWorkflowInputProvidesReplaceQuickFix() { + assertThat(quickFixTexts(""" + name: Quick Fixes + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.missing }}" + """)).contains("Replace with [known-input]"); + } + + public void testPlainGithubUrlInsideRunBlockDoesNotOfferContextReplacement() { + assertThat(quickFixTexts(""" + name: Quick Fixes + on: workflow_dispatch + jobs: + release: + runs-on: ubuntu-latest + steps: + - run: | + notes_file="$(mktemp)" + printf -- '- https://github.com/%s/compare/%s...%s\\n' "$REPOSITORY" "$previous_tag" "$TAG_NAME" + echo "ref [${{ github.ref }}]" >> "$notes_file" + """)) + .noneMatch(text -> text.contains("Replace with [action]")) + .noneMatch(text -> text.contains("github.com")); + } + + public void testSecretInIfProvidesDeleteQuickFix() { + assertThat(quickFixTexts(""" + name: Quick Fixes + on: + workflow_call: + secrets: + SECRET_TOKEN: + required: false + jobs: + build: + runs-on: ubuntu-latest + steps: + - if: secrets.SECRET_TOKEN != '' + run: echo ok + """)).contains("Remove [secrets.SECRET_TOKEN] - Secrets are not valid in `if` statements"); + } + + public void testUnusedJobOutputProvidesDeleteQuickFix() { + assertThat(quickFixTexts(""" + name: Quick Fixes + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """)).contains("Unused [artifact]"); + } + + public void testQueuedCacheHighlightRefreshDoesNotModifyDuringHighlighting() { + configureWorkflowProjectFile(""" + name: Quick Fixes + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + """); + ((CodeInsightTestFixtureImpl) myFixture).canChangeDocumentDuringHighlighting(false); + try { + GitHubActionCache.triggerSyntaxHighlightingForActiveFiles(); + assertThatCode(() -> myFixture.doHighlighting()) + .doesNotThrowAnyException(); + } finally { + ((CodeInsightTestFixtureImpl) myFixture).canChangeDocumentDuringHighlighting(true); + } + } + + public void testReplaceQuickFixUpdatesWorkflowInputReference() { + final String fixedText = applyQuickFix(""" + name: Quick Fixes + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.missing }}" + """, "Replace with [known-input]"); + + assertThat(fixedText).contains("inputs.known-input"); + assertThat(fixedText).doesNotContain("inputs.missing"); + } + + public void testDeleteQuickFixRemovesInvalidActionInput() { + seedRemoteAction("owner/tool@v1", Map.of("known-input", "Known input"), Map.of()); + + final String fixedText = applyQuickFix(""" + name: Quick Fixes + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + wrong-input: no + """, "Delete invalid input [wrong-input]"); + + assertThat(fixedText).doesNotContain("wrong-input"); + } + + public void testDeleteQuickFixRemovesInvalidReusableWorkflowSecret() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of(), Map.of(), Map.of("access-token", "Access token")); + + final String fixedText = applyQuickFix(""" + name: Quick Fixes + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + secrets: + wrong-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + """, "Delete invalid secret [wrong-token]"); + + assertThat(fixedText).doesNotContain("wrong-token"); + } + + public void testInvalidSuffixQuickFixRemovesTrailingMember() { + final String fixedText = applyQuickFix(""" + name: QuickFix + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ runner.os.extra }}" + """, "Remove invalid suffix [extra]"); + + assertThat(fixedText).contains("${{ runner.os }}"); + assertThat(fixedText).doesNotContain("runner.os."); + } + + public void testInvalidNeedsMemberQuickFixRemovesMember() { + final String fixedText = applyQuickFix(""" + name: QuickFix + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build.bad }}" + """, "Remove invalid [bad]"); + + assertThat(fixedText).contains("${{ needs.build }}"); + assertThat(fixedText).doesNotContain("needs.build."); + } + + private void assertQuickFixHasIcon(final String text) { + final IntentionAction action = allIntentions().stream() + .filter(quickFix -> text.equals(quickFix.getText())) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing quick fix [" + text + "]")); + assertThat(action) + .as("Quick fix [%s] exposes an IDE icon", text) + .isInstanceOf(Iconable.class); + assertThat(((Iconable) action).getIcon(0)) + .as("Quick fix [%s] icon", text) + .isNotNull(); + } + + public void testReplaceQuickFixUpdatesWorkflowCallInputDefaultReference() { + final String fixedText = applyQuickFix(""" + name: QuickFix + on: + workflow_call: + inputs: + known-input: + type: string + target: + type: string + default: ${{ inputs.missing }} + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """, "Replace with [known-input]"); + + assertThat(fixedText).contains("default: ${{ inputs.known-input }}"); + assertThat(fixedText).doesNotContain("inputs.missing"); + } + + public void testReplaceQuickFixUpdatesJobContainerScalarReference() { + final String fixedText = applyQuickFix(""" + name: QuickFix + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + container: ${{ inputs.missing }} + steps: + - run: echo ok + """, "Replace with [known-input]"); + + assertThat(fixedText).contains("container: ${{ inputs.known-input }}"); + assertThat(fixedText).doesNotContain("inputs.missing"); + } + + public void testInvalidJobContainerMemberQuickFixRemovesMember() { + final String fixedText = applyQuickFix(""" + name: QuickFix + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + container: postgres:16 + steps: + - run: echo "${{ job.container.bad }}" + """, "Remove invalid [bad]"); + + assertThat(fixedText).contains("${{ job.container }}"); + assertThat(fixedText).doesNotContain("job.container."); + } + + public void testInvalidJobServiceMemberQuickFixRemovesMember() { + final String fixedText = applyQuickFix(""" + name: QuickFix + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + steps: + - run: echo "${{ job.services.postgres.bad }}" + """, "Remove invalid [bad]"); + + assertThat(fixedText).contains("${{ job.services.postgres }}"); + assertThat(fixedText).doesNotContain("job.services.postgres."); + } + + public void testReplaceQuickFixUpdatesJobServiceIdReference() { + final String fixedText = applyQuickFix(""" + name: QuickFix + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + steps: + - run: echo "${{ job.services.database.network }}" + """, "Replace with [postgres]"); + + assertThat(fixedText).contains("job.services.postgres.network"); + assertThat(fixedText).doesNotContain("job.services.database.network"); + } + + public void testReplaceQuickFixUpdatesJobServicePortReference() { + final String fixedText = applyQuickFix(""" + name: QuickFix + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + ports: + - 5432/tcp + steps: + - run: echo "${{ job.services.postgres.ports[1234] }}" + """, "Replace with [5432]"); + + assertThat(fixedText).contains("job.services.postgres.ports[5432]"); + assertThat(fixedText).doesNotContain("ports[1234]"); + } + + public void testOutdatedMajorActionProvidesUpdateQuickFixForIssue47() { + seedRemoteAction("actions/checkout@v3", Map.of(), Map.of()).remoteRefs(List.of("v4", "v3", "main")); + + assertThat(quickFixTexts(""" + name: QuickFix + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + """)).contains("Update action [actions/checkout@v3] to [actions/checkout@v4]"); + } + + public void testUpdateActionQuickFixRewritesUsesRefForIssue47() { + seedRemoteAction("actions/checkout@v3", Map.of(), Map.of()).remoteRefs(List.of("v4", "v3")); + + final String fixedText = applyQuickFix(""" + name: QuickFix + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + """, "Update action [actions/checkout@v3] to [actions/checkout@v4]"); + + assertThat(fixedText).contains("uses: actions/checkout@v4"); + assertThat(fixedText).doesNotContain("actions/checkout@v3"); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowReferenceTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowReferenceTest.java new file mode 100644 index 0000000..1b78219 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowReferenceTest.java @@ -0,0 +1,737 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.intellij.openapi.paths.WebReference; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import org.jetbrains.yaml.psi.YAMLKeyValue; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static com.github.yunabraska.githubworkflow.services.ReferenceContributor.ACTION_KEY; +import static com.github.yunabraska.githubworkflow.services.GitHubActionCache.getActionCache; + +public class WorkflowReferenceTest extends EditorFeatureTestCase { + + public void testLocalActionReferenceResolvesToActionFile() { + final PsiFile actionFile = myFixture.addFileToProject(".github/actions/local/action.yml", """ + name: Local Action + inputs: + known-input: + description: Known input + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: ./.github/actions/local + """); + seedLocalAction("./.github/actions/local", actionFile); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isNotNull(); + assertThat(resolved.getContainingFile().getName()).isEqualTo("action.yml"); + } + + public void testLocalActionYamlReferenceResolvesToActionFile() { + final PsiFile actionFile = myFixture.addFileToProject(".github/actions/local/action.yaml", """ + name: Local Action + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: ./.github/actions/local + """); + seedLocalAction("./.github/actions/local", actionFile); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isNotNull(); + assertThat(resolved.getContainingFile().getName()).isEqualTo("action.yaml"); + } + + public void testRootLocalActionReferenceResolvesToActionFile() { + final PsiFile actionFile = myFixture.addFileToProject("action.yml", """ + name: Root Action + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: ./ + """); + seedLocalAction("./", actionFile); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isNotNull(); + assertThat(resolved.getContainingFile().getName()).isEqualTo("action.yml"); + } + + public void testLocalReusableWorkflowReferenceResolvesToWorkflowFile() { + final PsiFile workflowFile = myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Reusable + on: + workflow_call: + inputs: + config-path: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + call: + uses: ./.github/workflows/reusable.yml + """); + seedLocalAction("./.github/workflows/reusable.yml", workflowFile); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isNotNull(); + assertThat(resolved.getContainingFile().getName()).isEqualTo("reusable.yml"); + } + + public void testRemoteActionReferenceKeepsGithubUrl() { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of()); + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + """); + + final PsiReference reference = referenceAtCaret(); + + assertThat(reference).isInstanceOf(WebReference.class); + assertThat(reference.getCanonicalText()).isEqualTo("owner/tool@v1"); + assertThat(reference.getElement().getUserData(ACTION_KEY)) + .extracting(GitHubAction::githubUrl) + .isEqualTo("https://github.com/owner/tool/tree/v1#readme"); + } + + public void testRemoteReusableWorkflowReferenceKeepsGithubUrl() { + seedRemoteAction("owner/repo/.github/workflows/build.yml@v1", Map.of(), Map.of()); + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + call: + uses: owner/repo/.github/workflows/build.yml@v1 + """); + + final PsiReference reference = referenceAtCaret(); + + assertThat(reference).isInstanceOf(WebReference.class); + assertThat(reference.getCanonicalText()).isEqualTo("owner/repo/.github/workflows/build.yml@v1"); + assertThat(reference.getElement().getUserData(ACTION_KEY)) + .extracting(GitHubAction::githubUrl) + .isEqualTo("https://github.com/owner/repo/blob/v1/.github/workflows/build.yml"); + } + + public void testConfiguredGithubEnterpriseRemoteActionReferenceKeepsServerUrl() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.addContent("acme", "tool", "action.yml", "main", """ + name: Enterprise Tool + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + RemoteServerSettings.getInstance().setCustomServers(List.of(new RemoteServerSettings.Server("Fake Enterprise", + server.webUrl(), + server.apiUrl("/api/v3"), + "", + true + ))); + final String usesValue = server.webUrl() + "/acme/tool@main"; + final GitHubAction action = GitHubAction.createGithubAction(false, usesValue, usesValue).resolve(); + getActionCache().getState().actions.put(usesValue, action); + + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: %s/acme/tool@main + """.formatted(server.webUrl())); + + final PsiReference reference = referenceAtCaret(); + + assertThat(reference).isInstanceOf(WebReference.class); + assertThat(reference.getElement().getUserData(ACTION_KEY)) + .extracting(GitHubAction::githubUrl) + .isEqualTo(server.webUrl() + "/acme/tool/tree/main#readme"); + } + } + + public void testNeedsScalarReferenceResolvesToPreviousJob() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("build"); + } + + public void testNeedsQuotedScalarReferenceResolvesToPreviousJob() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: "build" + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("build"); + } + + public void testNeedsArrayReferenceResolvesToPreviousJob() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: [ build ] + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("build"); + } + + public void testWorkflowInputExpressionReferenceResolvesToInputKey() { + configureWorkflowProjectFile(""" + name: References + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.known-input }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("known-input"); + } + + public void testMultipleExpressionsResolveReferenceAtCaretOnly() { + configureWorkflowProjectFile(""" + name: References + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ inputs.known-input }} ${{ steps.package.outputs.artifact }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("id"); + assertThat(((YAMLKeyValue) resolved).getValueText()).isEqualTo("package"); + } + + public void testBracketWorkflowInputExpressionReferenceResolvesToInputKey() { + configureWorkflowProjectFile(""" + name: References + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs['known-input'] }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("known-input"); + } + + public void testWorkflowCallInputDefaultExpressionReferenceResolvesToInputKey() { + configureWorkflowProjectFile(""" + name: References + on: + workflow_call: + inputs: + known-input: + type: string + target: + type: string + default: ${{ inputs.known-input }} + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("known-input"); + } + + public void testStepExpressionReferenceResolvesToStepId() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.package.outputs.artifact }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("id"); + assertThat(((YAMLKeyValue) resolved).getValueText()).isEqualTo("package"); + } + + public void testBracketStepExpressionReferenceResolvesToStepId() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps['package'].outputs.artifact }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("id"); + assertThat(((YAMLKeyValue) resolved).getValueText()).isEqualTo("package"); + } + + public void testStepRunOutputExpressionReferenceResolvesToRunKey() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.package.outputs.artifact }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("run"); + } + + public void testStepActionOutputExpressionReferenceResolvesToUsesKey() { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of("artifact", "Artifact")); + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + uses: owner/tool@v1 + - run: echo "${{ steps.package.outputs.artifact }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("uses"); + } + + public void testNeedsExpressionReferenceResolvesToNeedsScalar() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build.outputs.artifact }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isNotNull(); + assertThat(resolved.getText()).isEqualTo("build"); + } + + public void testBracketNeedsExpressionReferenceResolvesToNeedsScalar() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs['build'].outputs.artifact }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isNotNull(); + assertThat(resolved.getText()).isEqualTo("build"); + } + + public void testNeedsOutputExpressionReferenceResolvesToJobOutputKey() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "${{ needs.build.outputs.artifact }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("artifact"); + } + + public void testJobsWorkflowOutputExpressionReferenceResolvesToJob() { + configureWorkflowProjectFile(""" + name: References + on: + workflow_call: + outputs: + artifact: + value: ${{ jobs.build.outputs.artifact }} + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("build"); + } + + public void testJobsOutputExpressionReferenceResolvesToJobOutputKey() { + configureWorkflowProjectFile(""" + name: References + on: + workflow_call: + outputs: + artifact: + value: ${{ jobs.build.outputs.artifact }} + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("artifact"); + } + + public void testWorkflowEnvExpressionReferenceResolvesToEnvKey() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + env: + TOP_LEVEL: top + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ env.TOP_LEVEL }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("TOP_LEVEL"); + } + + public void testStepEnvExpressionReferencePrefersStepEnvKey() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + env: + SHARED: root + jobs: + build: + runs-on: ubuntu-latest + env: + SHARED: job + steps: + - env: + SHARED: step + run: echo "${{ env.SHARED }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("SHARED"); + assertThat(((YAMLKeyValue) resolved).getValueText()).isEqualTo("step"); + } + + public void testJobEnvMapAliasExpressionReferenceResolvesToAnchoredEnvKey() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + define: + runs-on: ubuntu-latest + env: &env_vars + NODE_ENV: production + steps: + - run: echo ok + reuse: + runs-on: ubuntu-latest + env: *env_vars + steps: + - run: echo "${{ env.NODE_ENV }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("NODE_ENV"); + } + + public void testMatrixExpressionReferenceResolvesToMatrixKey() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - run: echo "${{ matrix.os }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("os"); + } + + public void testMatrixIncludeExpressionReferenceResolvesToIncludeKey() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + node: 25 + steps: + - run: echo "${{ matrix.node }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("node"); + } + + public void testWorkflowCallSecretExpressionReferenceResolvesToSecretKey() { + configureWorkflowProjectFile(""" + name: References + on: + workflow_call: + secrets: + SECRET_TOKEN: + required: false + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ secrets.SECRET_TOKEN }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("SECRET_TOKEN"); + } + + public void testJobServiceExpressionReferenceResolvesToServiceKey() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + steps: + - run: echo "${{ job.services.postgres.network }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("postgres"); + } + + public void testJobServicePortExpressionReferenceResolvesToPortsKey() { + configureWorkflowProjectFile(""" + name: References + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + ports: + - 5432/tcp + steps: + - run: echo "${{ job.services.postgres.ports[5432] }}" + """); + + final PsiElement resolved = referenceAtCaret().resolve(); + + assertThat(resolved).isInstanceOf(YAMLKeyValue.class); + assertThat(((YAMLKeyValue) resolved).getKeyText()).isEqualTo("ports"); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolverTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolverTest.java new file mode 100644 index 0000000..5e222d3 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolverTest.java @@ -0,0 +1,58 @@ +package com.github.yunabraska.githubworkflow.services; + +import junit.framework.TestCase; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowRepositoryResolverTest extends TestCase { + + public void testGithubHttpsRemoteUsesPublicApi() { + assertThat(WorkflowRepositoryResolver.fromRemoteUrl("https://github.com/YunaBraska/github-workflow-plugin.git")) + .contains(new WorkflowRepository( + "https://github.com", + "https://api.github.com", + "YunaBraska", + "github-workflow-plugin" + )); + } + + public void testEnterpriseHttpsRemoteUsesApiV3() { + assertThat(WorkflowRepositoryResolver.fromRemoteUrl("https://github.acme.test/tools/workflows.git")) + .contains(new WorkflowRepository( + "https://github.acme.test", + "https://github.acme.test/api/v3", + "tools", + "workflows" + )); + } + + public void testSshRemoteUsesPublicApi() { + assertThat(WorkflowRepositoryResolver.fromRemoteUrl("git@github.com:YunaBraska/github-workflow-plugin.git")) + .contains(new WorkflowRepository( + "https://github.com", + "https://api.github.com", + "YunaBraska", + "github-workflow-plugin" + )); + } + + public void testResolveReadsOriginFromGitConfig() throws Exception { + final Path dir = Files.createTempDirectory("workflow-repo"); + Files.createDirectories(dir.resolve(".git")); + Files.writeString(dir.resolve(".git").resolve("config"), """ + [remote "origin"] + url = https://github.com/YunaBraska/github-workflow-plugin.git + """); + + assertThat(new WorkflowRepositoryResolver().resolve(dir)) + .contains(new WorkflowRepository( + "https://github.com", + "https://api.github.com", + "YunaBraska", + "github-workflow-plugin" + )); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java new file mode 100644 index 0000000..c5b7b62 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java @@ -0,0 +1,422 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.sun.net.httpserver.HttpServer; +import junit.framework.TestCase; + +import javax.net.ssl.SSLSession; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpClient.Version; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowRunClientTest extends TestCase { + + public void testDispatchPostsWorkflowDispatchRequest() throws Exception { + try (FakeWorkflowRunServer server = new FakeWorkflowRunServer()) { + final WorkflowRunClient client = new WorkflowRunClient(); + final WorkflowRunRequest request = new WorkflowRunRequest( + server.apiUrl(), + "acme", + "tool", + ".github/workflows/build.yml", + "feature", + Map.of("dry_run", "true"), + "" + ); + + final WorkflowRunClient.DispatchResult result = client.dispatch(request); + + assertThat(result.runId()).isEqualTo(42); + assertThat(server.requests()).contains("/repos/acme/tool/actions/workflows/build.yml/dispatches"); + assertThat(server.bodies()).contains("{\"ref\":\"feature\",\"inputs\":{\"dry_run\":\"true\"}}"); + } + } + + public void testStatusCancelJobsAndLogsUseRunEndpoints() throws Exception { + try (FakeWorkflowRunServer server = new FakeWorkflowRunServer()) { + final WorkflowRunClient client = new WorkflowRunClient(); + final WorkflowRunRequest request = new WorkflowRunRequest(server.apiUrl(), "acme", "tool", "build.yml", "main", Map.of(), ""); + + final WorkflowRunClient.RunStatus status = client.status(request, 42); + final WorkflowRunClient.CancelResult cancel = client.cancel(request, 42); + final WorkflowRunClient.RerunResult rerunAll = client.rerun(request, 42, false); + final WorkflowRunClient.RerunResult rerunFailed = client.rerun(request, 42, true); + final WorkflowRunClient.DeleteResult delete = client.delete(request, 42); + final String logs = client.logs(request, 42); + + assertThat(status.completed()).isTrue(); + assertThat(status.conclusion()).isEqualTo("success"); + assertThat(cancel.accepted()).isTrue(); + assertThat(rerunAll.accepted()).isTrue(); + assertThat(rerunFailed.accepted()).isTrue(); + assertThat(delete.accepted()).isTrue(); + assertThat(logs).contains("== build [completed/success]", "hello from job log"); + assertThat(server.requests()).contains( + "/repos/acme/tool/actions/runs/42", + "/repos/acme/tool/actions/runs/42/cancel", + "/repos/acme/tool/actions/runs/42/rerun", + "/repos/acme/tool/actions/runs/42/rerun-failed-jobs", + "/repos/acme/tool/actions/runs/42/jobs", + "/repos/acme/tool/actions/jobs/100/logs" + ); + assertThat(server.methods()).contains("DELETE"); + } + } + + public void testDispatchAcceptsLegacyNoContentResponse() throws Exception { + try (FakeWorkflowRunServer server = new FakeWorkflowRunServer(true)) { + final WorkflowRunClient client = new WorkflowRunClient(); + final WorkflowRunRequest request = new WorkflowRunRequest(server.apiUrl(), "acme", "tool", "build.yml", "main", Map.of(), ""); + + final WorkflowRunClient.DispatchResult result = client.dispatch(request); + + assertThat(result.runId()).isEqualTo(-1); + assertThat(result.htmlUrl()).isEmpty(); + } + } + + public void testLatestRunDiscoversNewestWorkflowDispatchRun() throws Exception { + try (FakeWorkflowRunServer server = new FakeWorkflowRunServer()) { + final WorkflowRunClient client = new WorkflowRunClient(); + final WorkflowRunRequest request = new WorkflowRunRequest(server.apiUrl(), "acme", "tool", "build.yml", "feature/one", Map.of(), ""); + + final var result = client.latestRun(request); + + assertThat(result).isPresent(); + assertThat(result.get().runId()).isEqualTo(77); + assertThat(result.get().status()).isEqualTo("queued"); + assertThat(result.get().htmlUrl()).isEqualTo("html-latest"); + assertThat(server.requests()).contains("/repos/acme/tool/actions/workflows/build.yml/runs?branch=feature%2Fone&event=workflow_dispatch&per_page=1"); + } + } + + public void testArtifactsAndZipUseRunArtifactEndpoints() throws Exception { + try (FakeWorkflowRunServer server = new FakeWorkflowRunServer()) { + final WorkflowRunClient client = new WorkflowRunClient(); + final WorkflowRunRequest request = new WorkflowRunRequest(server.apiUrl(), "acme", "tool", "build.yml", "main", Map.of(), ""); + + final List artifacts = client.artifacts(request, 42); + final byte[] zip = client.artifactZip(request, artifacts.get(0).id()); + + assertThat(artifacts).containsExactly(new WorkflowRunClient.ArtifactStatus(300, "reports", 9, false, "artifact-url")); + assertThat(new String(zip, StandardCharsets.UTF_8)).isEqualTo("zip-bytes"); + assertThat(server.requests()).contains( + "/repos/acme/tool/actions/runs/42/artifacts?per_page=100", + "/repos/acme/tool/actions/artifacts/300/zip" + ); + } + } + + public void testDispatchRetriesConfiguredAuthorizationsBeforeAnonymous() throws Exception { + try (FakeWorkflowRunServer server = new FakeWorkflowRunServer(false, 2)) { + final HttpClient httpClient = HttpClient.newHttpClient(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)), + request -> List.of( + new GitHubRequestAuthorizations.Authorization("github.com", "Bearer normal-token"), + new GitHubRequestAuthorizations.Authorization("enterprise", "Bearer enterprise-token"), + GitHubRequestAuthorizations.Authorization.anonymous() + ) + ); + final WorkflowRunRequest request = new WorkflowRunRequest(server.apiUrl(), "acme", "tool", "build.yml", "main", Map.of(), ""); + + final WorkflowRunClient.DispatchResult result = client.dispatch(request); + + assertThat(result.runId()).isEqualTo(42); + assertThat(server.authorizations()).containsExactly("Bearer normal-token", "Bearer enterprise-token", ""); + } + } + + public void testSuccessfulAuthorizationIsReusedWhenProviderLaterCannotLoadAccounts() throws Exception { + final AtomicInteger providerCalls = new AtomicInteger(); + final List authorizations = new ArrayList<>(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> { + authorizations.add(authorizationHeader(request)); + if (request.uri().getPath().endsWith("/dispatches")) { + return new ClientResponse(request, 200, "application/json", "{\"workflow_run_id\":42}"); + } + if (request.uri().getPath().endsWith("/jobs/100/logs") + && "Bearer account-token".equals(authorizationHeader(request))) { + return new ClientResponse(request, 200, "text/plain", "cached account log\n"); + } + return new ClientResponse(request, 403, "application/json", "{\"message\":\"API rate limit exceeded\"}"); + }, + request -> providerCalls.getAndIncrement() == 0 + ? List.of(new GitHubRequestAuthorizations.Authorization("github.com", "Bearer account-token")) + : List.of(GitHubRequestAuthorizations.Authorization.anonymous()) + ); + final WorkflowRunRequest request = new WorkflowRunRequest("https://api.github.test", "acme", "tool", "build.yml", "main", Map.of(), ""); + + client.dispatch(request); + final String logs = client.jobLogs(request, 100); + + assertThat(logs).isEqualTo("cached account log\n"); + assertThat(authorizations).containsExactly("Bearer account-token", "Bearer account-token"); + } + + public void testAuthenticatedRateLimitDoesNotFallBackToAnonymous() { + final List authorizations = new ArrayList<>(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> { + authorizations.add(authorizationHeader(request)); + if (authorizationHeader(request).isEmpty()) { + return new ClientResponse(request, 200, "text/plain", "anonymous log should not be used\n"); + } + return new ClientResponse(request, 403, "application/json", "{\"message\":\"API rate limit exceeded for token\"}"); + }, + request -> List.of( + new GitHubRequestAuthorizations.Authorization("github.com", "Bearer limited-token"), + GitHubRequestAuthorizations.Authorization.anonymous() + ) + ); + final WorkflowRunRequest request = new WorkflowRunRequest("https://api.github.test", "acme", "tool", "build.yml", "main", Map.of(), ""); + + assertThatExceptionOfType(WorkflowRunClient.WorkflowRunHttpException.class) + .isThrownBy(() -> client.jobLogs(request, 100)) + .withMessageContaining("GitHub workflow job logs failed with HTTP 403") + .withMessageContaining("rate limit"); + assertThat(authorizations).containsExactly("Bearer limited-token"); + } + + public void testJobLogsFallBackFromAccountTokenWithoutLogRightsToEnvironmentToken() throws Exception { + final List authorizations = new ArrayList<>(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> { + authorizations.add(authorizationHeader(request)); + if ("Bearer env-token".equals(authorizationHeader(request))) { + return new ClientResponse(request, 200, "text/plain", "live log from env token\n"); + } + return new ClientResponse( + request, + 403, + "application/json", + "{\"message\":\"Must have admin rights to Repository.\"}" + ); + }, + request -> List.of( + new GitHubRequestAuthorizations.Authorization("github.com", "Bearer account-token"), + new GitHubRequestAuthorizations.Authorization("GITHUB_TOKEN", "Bearer env-token"), + GitHubRequestAuthorizations.Authorization.anonymous() + ) + ); + final WorkflowRunRequest request = new WorkflowRunRequest("https://api.github.test", "acme", "tool", "build.yml", "main", Map.of(), ""); + + final String logs = client.jobLogs(request, 100); + + assertThat(logs).isEqualTo("live log from env token\n"); + assertThat(authorizations).containsExactly("Bearer account-token", "Bearer env-token"); + } + + public void testJobLogAdminFailureDoesNotSuggestRefreshingAccounts() { + final WorkflowRunClient client = new WorkflowRunClient( + request -> new ClientResponse( + request, + 403, + "application/json", + "{\"message\":\"Must have admin rights to Repository.\"}" + ), + request -> List.of(new GitHubRequestAuthorizations.Authorization("github.com", "Bearer account-token")) + ); + final WorkflowRunRequest request = new WorkflowRunRequest("https://api.github.test", "acme", "tool", "build.yml", "main", Map.of(), ""); + + assertThatExceptionOfType(WorkflowRunClient.WorkflowRunHttpException.class) + .isThrownBy(() -> client.jobLogs(request, 100)) + .withMessageContaining("GitHub workflow job logs failed with HTTP 403") + .withMessageContaining("Must have admin rights") + .withMessageNotContaining("Settings > Version Control > GitHub"); + } + + public void testDispatchAuthenticationFailureMentionsGithubSettings() throws Exception { + try (FakeWorkflowRunServer server = new FakeWorkflowRunServer(false, 1)) { + final HttpClient httpClient = HttpClient.newHttpClient(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)), + request -> List.of(GitHubRequestAuthorizations.Authorization.anonymous()) + ); + final WorkflowRunRequest request = new WorkflowRunRequest(server.apiUrl(), "acme", "tool", "build.yml", "main", Map.of(), ""); + + assertThatExceptionOfType(WorkflowRunClient.WorkflowRunHttpException.class) + .isThrownBy(() -> client.dispatch(request)) + .withMessageContaining("GitHub workflow dispatch failed with HTTP 401") + .withMessageContaining("Settings > Version Control > GitHub"); + } + } + + public void testJobLogHtmlFailureIsSummarized() { + final WorkflowRunClient client = new WorkflowRunClient( + request -> new ClientResponse( + request, + 504, + "text/html", + "

We couldn't respond in time.

" + ), + request -> List.of(GitHubRequestAuthorizations.Authorization.anonymous()) + ); + final WorkflowRunRequest request = new WorkflowRunRequest("https://api.github.test", "acme", "tool", "build.yml", "main", Map.of(), ""); + + assertThatExceptionOfType(WorkflowRunClient.WorkflowRunHttpException.class) + .isThrownBy(() -> client.jobLogs(request, 100)) + .withMessageContaining("GitHub workflow job logs failed with HTTP 504") + .withMessageContaining("GitHub returned an HTML error page") + .withMessageNotContaining(" requests = new ArrayList<>(); + private final List methods = new ArrayList<>(); + private final List bodies = new ArrayList<>(); + private final List authorizations = new ArrayList<>(); + private final boolean legacyDispatch; + private int dispatchFailures; + + FakeWorkflowRunServer() throws IOException { + this(false, 0); + } + + FakeWorkflowRunServer(final boolean legacyDispatch) throws IOException { + this(legacyDispatch, 0); + } + + FakeWorkflowRunServer(final boolean legacyDispatch, final int dispatchFailures) throws IOException { + this.legacyDispatch = legacyDispatch; + this.dispatchFailures = dispatchFailures; + server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0); + server.createContext("/", exchange -> { + final URI uri = exchange.getRequestURI(); + final String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + methods.add(exchange.getRequestMethod()); + requests.add(uri.getPath() + (uri.getRawQuery() == null ? "" : "?" + uri.getRawQuery())); + bodies.add(body); + authorizations.add(exchange.getRequestHeaders().getFirst("Authorization") == null + ? "" + : exchange.getRequestHeaders().getFirst("Authorization")); + final Response response = responseFor(uri.getPath()); + final byte[] bytes = response.body().getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().set("Content-Type", response.contentType()); + if (response.status() == 204) { + exchange.sendResponseHeaders(response.status(), -1); + exchange.close(); + return; + } + exchange.sendResponseHeaders(response.status(), bytes.length); + exchange.getResponseBody().write(bytes); + exchange.close(); + }); + server.start(); + } + + String apiUrl() { + return "http://" + server.getAddress().getHostString() + ":" + server.getAddress().getPort(); + } + + List requests() { + return List.copyOf(requests); + } + + List methods() { + return List.copyOf(methods); + } + + List bodies() { + return List.copyOf(bodies); + } + + List authorizations() { + return List.copyOf(authorizations); + } + + @Override + public void close() { + server.stop(0); + } + + private Response responseFor(final String path) { + if (path.endsWith("/dispatches")) { + if (dispatchFailures > 0) { + dispatchFailures--; + return new Response(401, "application/json", "{\"message\":\"Requires authentication\",\"status\":\"401\"}"); + } + if (legacyDispatch) { + return new Response(204, "application/json", ""); + } + return new Response(200, "application/json", "{\"workflow_run_id\":42,\"run_url\":\"api-run\",\"html_url\":\"html-run\"}"); + } + if (path.endsWith("/cancel")) { + return new Response(202, "application/json", "{}"); + } + if (path.endsWith("/rerun") || path.endsWith("/rerun-failed-jobs")) { + return new Response(201, "application/json", "{}"); + } + if (path.endsWith("/workflows/build.yml/runs")) { + return new Response(200, "application/json", "{\"workflow_runs\":[{\"id\":77,\"status\":\"queued\",\"conclusion\":null,\"html_url\":\"html-latest\"}]}"); + } + if (path.endsWith("/runs/42/jobs")) { + return new Response(200, "application/json", "{\"jobs\":[{\"id\":100,\"name\":\"build\",\"status\":\"completed\",\"conclusion\":\"success\"}]}"); + } + if (path.endsWith("/runs/42/artifacts")) { + return new Response(200, "application/json", "{\"artifacts\":[{\"id\":300,\"name\":\"reports\",\"size_in_bytes\":9,\"expired\":false,\"archive_download_url\":\"artifact-url\"}]}"); + } + if (path.endsWith("/artifacts/300/zip")) { + return new Response(200, "application/zip", "zip-bytes"); + } + if (path.endsWith("/jobs/100/logs")) { + return new Response(200, "text/plain", "hello from job log\n"); + } + if (path.endsWith("/runs/42")) { + return new Response(200, "application/json", "{\"id\":42,\"status\":\"completed\",\"conclusion\":\"success\",\"html_url\":\"html-run\"}"); + } + return new Response(404, "application/json", "{}"); + } + + private record Response(int status, String contentType, String body) { + } + } + + private record ClientResponse(HttpRequest request, int statusCode, String contentType, String body) implements HttpResponse { + @Override + public HttpHeaders headers() { + return HttpHeaders.of(Map.of("Content-Type", List.of(contentType)), (left, right) -> true); + } + + @Override + public Optional> previousResponse() { + return Optional.empty(); + } + + @Override + public URI uri() { + return request.uri(); + } + + @Override + public Version version() { + return Version.HTTP_1_1; + } + + @Override + public Optional sslSession() { + return Optional.empty(); + } + } + + private static String authorizationHeader(final HttpRequest request) { + return request.headers().firstValue("Authorization").orElse(""); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabsTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabsTest.java new file mode 100644 index 0000000..4263a42 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabsTest.java @@ -0,0 +1,22 @@ +package com.github.yunabraska.githubworkflow.services; + +import junit.framework.TestCase; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowRunConsoleTabsTest extends TestCase { + + public void testMatrixStyleJobNameIsGroupedLikeJUnitClassAndMethod() { + final WorkflowRunConsoleTabs.JobDisplayName name = WorkflowRunConsoleTabs.splitJobName("Node Test / test (ubuntu-latest)"); + + assertThat(name.group()).isEqualTo("Node Test"); + assertThat(name.name()).isEqualTo("test (ubuntu-latest)"); + } + + public void testPlainJobNameStaysUnderWorkflowRoot() { + final WorkflowRunConsoleTabs.JobDisplayName name = WorkflowRunConsoleTabs.splitJobName("build"); + + assertThat(name.group()).isEmpty(); + assertThat(name.name()).isEqualTo("build"); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjectionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjectionTest.java new file mode 100644 index 0000000..2f7415b --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjectionTest.java @@ -0,0 +1,53 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.lang.Language; +import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.yaml.psi.YAMLScalar; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowRunLanguageInjectionTest extends EditorFeatureTestCase { + + public void testRunBlockInjectsShellScriptLanguageWhenAvailable() { + assertThat(Language.findLanguageByID("Shell Script")).isNotNull(); + + configureWorkflowProjectFile(""" + name: Injection + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - shell: bash + run: | + echo "hello" + if [ -f pom.xml ]; then + echo ok + fi + """); + + final YAMLScalar scalar = scalarAtCaret(); + final List> injected = InjectedLanguageManager.getInstance(getProject()).getInjectedPsiFiles(scalar); + + assertThat(injected).isNotEmpty(); + assertThat(injected.get(0).first.getLanguage().getID()).isEqualTo("Shell Script"); + } + + private YAMLScalar scalarAtCaret() { + final int offset = myFixture.getCaretOffset(); + final YAMLScalar scalar = PsiTreeUtil.findChildrenOfType(myFixture.getFile(), YAMLScalar.class) + .stream() + .filter(candidate -> candidate.getTextRange().getStartOffset() <= offset) + .filter(candidate -> offset <= candidate.getTextRange().getEndOffset()) + .findFirst() + .orElse(null); + assertThat(scalar).isNotNull(); + return scalar; + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRendererTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRendererTest.java new file mode 100644 index 0000000..83f3791 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRendererTest.java @@ -0,0 +1,136 @@ +package com.github.yunabraska.githubworkflow.services; + +import junit.framework.TestCase; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowRunLogRendererTest extends TestCase { + + public void testRenderStripsGithubTimestampAndFormatsGroupsAndCommands() { + final List segments = WorkflowRunLogRenderer.renderOnce(""" + 2026-05-22T13:38:12.0538840Z ##[group]Run actions/checkout@main + 2026-05-22T13:38:12.0539220Z ##[command]/usr/bin/git version + 2026-05-22T13:38:12.0539420Z ##[/group] + """); + + assertThat(segments) + .extracting(WorkflowRunLogRenderer.Segment::text) + .containsExactly( + "== Run actions/checkout@main ==\n", + "0001 | run: /usr/bin/git version\n" + ); + assertThat(segments) + .extracting(WorkflowRunLogRenderer.Segment::kind) + .containsExactly( + WorkflowRunLogRenderer.Kind.SYSTEM, + WorkflowRunLogRenderer.Kind.SYSTEM + ); + } + + public void testRenderClassifiesGithubWarningsAndErrors() { + final List segments = WorkflowRunLogRenderer.renderOnce(""" + ##[warning]old input + ##[error file=build.gradle,line=7]broken build + ::warning::soft problem + ::error::hard problem + """); + + assertThat(segments) + .extracting(WorkflowRunLogRenderer.Segment::text) + .containsExactly( + "0001 | warning: old input\n", + "0002 | error: broken build\n", + "0003 | warning: soft problem\n", + "0004 | error: hard problem\n" + ); + assertThat(segments) + .extracting(WorkflowRunLogRenderer.Segment::kind) + .containsExactly( + WorkflowRunLogRenderer.Kind.WARNING, + WorkflowRunLogRenderer.Kind.ERROR, + WorkflowRunLogRenderer.Kind.WARNING, + WorkflowRunLogRenderer.Kind.ERROR + ); + } + + public void testRenderInfersCommonWarningAndErrorPrefixes() { + final List segments = WorkflowRunLogRenderer.renderOnce(""" + npm warn deprecated old-package + warning: check this + fatal: repository not found + normal output + """); + + assertThat(segments) + .extracting(WorkflowRunLogRenderer.Segment::kind) + .containsExactly( + WorkflowRunLogRenderer.Kind.WARNING, + WorkflowRunLogRenderer.Kind.WARNING, + WorkflowRunLogRenderer.Kind.ERROR, + WorkflowRunLogRenderer.Kind.NORMAL + ); + } + + public void testRenderPlainKeepsReadableTextOnly() { + assertThat(WorkflowRunLogRenderer.renderPlainOnce(""" + 2026-05-22T13:38:12.0538840Z ##[group]Install + ##[command]npm ci + ##[warning]deprecated + """)) + .isEqualTo(""" + == Install == + 0001 | run: npm ci + 0002 | warning: deprecated + """); + } + + public void testRenderKeepsGroupLineNumbersAcrossChunksAndResetsPerGroup() { + final WorkflowRunLogRenderer renderer = new WorkflowRunLogRenderer(); + + assertThat(renderer.renderPlain(""" + ##[group]Install + first + """)) + .isEqualTo(""" + == Install == + 0001 | first + """); + assertThat(renderer.renderPlain(""" + second + ##[endgroup] + ##[group]Test + again + """)) + .isEqualTo(""" + 0002 | second + + == Test == + 0001 | again + """); + } + + public void testRenderStripsAnsiAndMapsCommonColors() { + final List segments = WorkflowRunLogRenderer.renderOnce(""" + \u001B[36;1mnpm ci && npm run test\u001B[0m + \u001B[33mcareful\u001B[0m + \u001B[31mboom\u001B[0m + """); + + assertThat(segments) + .extracting(WorkflowRunLogRenderer.Segment::text) + .containsExactly( + "0001 | npm ci && npm run test\n", + "0002 | careful\n", + "0003 | boom\n" + ); + assertThat(segments) + .extracting(WorkflowRunLogRenderer.Segment::kind) + .containsExactly( + WorkflowRunLogRenderer.Kind.SYSTEM, + WorkflowRunLogRenderer.Kind.WARNING, + WorkflowRunLogRenderer.Kind.ERROR + ); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java new file mode 100644 index 0000000..123c14d --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java @@ -0,0 +1,819 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessListener; +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import org.jetbrains.annotations.NotNull; + +import javax.net.ssl.SSLSession; +import java.net.URI; +import java.net.http.HttpClient.Version; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowRunProcessHandlerTest extends BasePlatformTestCase { + + public void testProcessStreamsJobLogDeltasWithoutAuthStrategyNoise() throws Exception { + final AtomicInteger statusCalls = new AtomicInteger(0); + final AtomicInteger jobCalls = new AtomicInteger(0); + final AtomicInteger logCalls = new AtomicInteger(0); + final WorkflowRunClient client = new WorkflowRunClient( + request -> responseFor(request, statusCalls, jobCalls, logCalls), + request -> List.of(GitHubRequestAuthorizations.Authorization.anonymous()) + ); + final WorkflowRunRequest request = new WorkflowRunRequest( + "https://api.github.test", + "acme", + "tool", + ".github/workflows/test.yml", + "main", + Map.of(), + "" + ); + final WorkflowRunProcessHandler handler = new WorkflowRunProcessHandler( + getProject(), + request, + client, + new WorkflowRunProcessHandler.PollSettings(10, 10, 10) + ); + final CountDownLatch terminated = new CountDownLatch(1); + final StringBuilder output = new StringBuilder(); + handler.addProcessListener(new ProcessListener() { + @Override + public void onTextAvailable(@NotNull final ProcessEvent event, @NotNull final com.intellij.openapi.util.Key outputType) { + if (ProcessOutputTypes.STDOUT.equals(outputType)) { + output.append(event.getText()); + } + } + + @Override + public void processTerminated(@NotNull final ProcessEvent event) { + terminated.countDown(); + } + }); + + handler.startNotify(); + + assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(output.toString()) + .contains(".github/workflows/test.yml") + .contains("acme/tool") + .contains("main") + .contains("Status: in_progress") + .contains("Status: completed/success") + .contains("URL: job-url") + .contains("Job: [RUN] build [in_progress") + .contains("Job: [OK] build [completed/success") + .contains("Workflow run [----------] 0/1 done, 1 running") + .contains("Workflow run [##########] 1/1 done, 0 running") + .contains("first line") + .contains("second line") + .doesNotContain("Trying IDE GitHub accounts"); + assertThat(logCalls.get()).isLessThanOrEqualTo(2); + } + + public void testDestroyCancelsRemoteRunAndTerminates() throws Exception { + final CountDownLatch statusSeen = new CountDownLatch(1); + final CountDownLatch cancelSeen = new CountDownLatch(1); + final CapturingJobConsole jobConsole = new CapturingJobConsole(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> cancellationResponseFor(request, statusSeen, cancelSeen), + request -> List.of(GitHubRequestAuthorizations.Authorization.anonymous()) + ); + final WorkflowRunRequest request = new WorkflowRunRequest( + "https://api.github.test", + "acme", + "tool", + ".github/workflows/test.yml", + "main", + Map.of(), + "" + ); + final WorkflowRunProcessHandler handler = new WorkflowRunProcessHandler( + getProject(), + request, + client, + new WorkflowRunProcessHandler.PollSettings(1_000, 1_000, 10), + jobConsole + ); + final CountDownLatch terminated = new CountDownLatch(1); + final StringBuilder output = new StringBuilder(); + handler.addProcessListener(new ProcessListener() { + @Override + public void onTextAvailable(@NotNull final ProcessEvent event, @NotNull final com.intellij.openapi.util.Key outputType) { + output.append(event.getText()); + } + + @Override + public void processTerminated(@NotNull final ProcessEvent event) { + terminated.countDown(); + } + }); + + handler.startNotify(); + assertThat(statusSeen.await(5, TimeUnit.SECONDS)).isTrue(); + handler.destroyProcess(); + + assertThat(cancelSeen.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(output.toString()).contains("Cancel requested: 42."); + assertThat(jobConsole.finished()).containsExactly("42:cancelled"); + } + + public void testDeleteRemoteRunUsesCompletedRunIdAndReportsToWorkflowConsole() throws Exception { + final CountDownLatch deleteSeen = new CountDownLatch(1); + final CapturingJobConsole jobConsole = new CapturingJobConsole(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> completedRunWithDeleteResponseFor(request, deleteSeen), + request -> List.of(GitHubRequestAuthorizations.Authorization.anonymous()) + ); + final WorkflowRunRequest request = new WorkflowRunRequest( + "https://api.github.test", + "acme", + "tool", + ".github/workflows/test.yml", + "main", + Map.of(), + "" + ); + final WorkflowRunProcessHandler handler = new WorkflowRunProcessHandler( + getProject(), + request, + client, + new WorkflowRunProcessHandler.PollSettings(10, 10, 10), + jobConsole + ); + final CountDownLatch terminated = new CountDownLatch(1); + handler.addProcessListener(new ProcessListener() { + @Override + public void processTerminated(@NotNull final ProcessEvent event) { + terminated.countDown(); + } + }); + + handler.startNotify(); + + assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); + handler.deleteRemoteRun(); + + assertThat(deleteSeen.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(waitForWorkflowOutput(jobConsole, "Run 42 deleted.")).isTrue(); + assertThat(waitForDeletedRun(jobConsole, 42L)).isTrue(); + assertThat(jobConsole.workflowOutput()).contains("Deleting run 42.", "Run 42 deleted."); + assertThat(jobConsole.deleted()).containsExactly(42L); + } + + public void testRerunRemoteRunUsesCompletedRunIdAndReportsToWorkflowConsole() throws Exception { + final CountDownLatch rerunAllSeen = new CountDownLatch(1); + final CountDownLatch rerunFailedSeen = new CountDownLatch(1); + final CapturingJobConsole jobConsole = new CapturingJobConsole(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> completedRunWithRerunResponseFor(request, rerunAllSeen, rerunFailedSeen), + request -> List.of(GitHubRequestAuthorizations.Authorization.anonymous()) + ); + final WorkflowRunRequest request = new WorkflowRunRequest( + "https://api.github.test", + "acme", + "tool", + ".github/workflows/test.yml", + "main", + Map.of(), + "" + ); + final WorkflowRunProcessHandler handler = new WorkflowRunProcessHandler( + getProject(), + request, + client, + new WorkflowRunProcessHandler.PollSettings(10, 10, 10), + jobConsole + ); + final CountDownLatch terminated = new CountDownLatch(1); + handler.addProcessListener(new ProcessListener() { + @Override + public void processTerminated(@NotNull final ProcessEvent event) { + terminated.countDown(); + } + }); + + handler.startNotify(); + + assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); + handler.rerunRemoteRun(false); + handler.rerunRemoteRun(true); + + assertThat(rerunAllSeen.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(rerunFailedSeen.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(waitForWorkflowOutput(jobConsole, "Rerun queued: 42.")).isTrue(); + assertThat(waitForWorkflowOutput(jobConsole, "Failed jobs queued: 42.")).isTrue(); + } + + public void testProcessRoutesEachJobLogToSeparateJobConsole() throws Exception { + final AtomicInteger statusCalls = new AtomicInteger(0); + final CapturingJobConsole jobConsole = new CapturingJobConsole(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> multiJobResponseFor(request, statusCalls), + request -> List.of(GitHubRequestAuthorizations.Authorization.anonymous()) + ); + final WorkflowRunRequest request = new WorkflowRunRequest( + "https://api.github.test", + "acme", + "tool", + ".github/workflows/test.yml", + "main", + Map.of(), + "" + ); + final WorkflowRunProcessHandler handler = new WorkflowRunProcessHandler( + getProject(), + request, + client, + new WorkflowRunProcessHandler.PollSettings(10, 10, 10), + jobConsole + ); + final CountDownLatch terminated = new CountDownLatch(1); + final StringBuilder mainOutput = new StringBuilder(); + handler.addProcessListener(new ProcessListener() { + @Override + public void onTextAvailable(@NotNull final ProcessEvent event, @NotNull final com.intellij.openapi.util.Key outputType) { + if (ProcessOutputTypes.STDOUT.equals(outputType)) { + mainOutput.append(event.getText()); + } + } + + @Override + public void processTerminated(@NotNull final ProcessEvent event) { + terminated.countDown(); + } + }); + + handler.startNotify(); + + assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(mainOutput.toString()) + .contains("Job: [OK] Node Test / test (ubuntu-latest) [completed/success") + .contains("Job: [OK] Node Test / test (windows-latest) [completed/success") + .doesNotContain("ubuntu log") + .doesNotContain("windows log"); + assertThat(jobConsole.output(100)).contains("URL: ubuntu-url", "Status: [OK] completed/success", "0001 | ubuntu log"); + assertThat(jobConsole.output(200)).contains("URL: windows-url", "Status: [OK] completed/success", "0001 | windows log"); + assertThat(jobConsole.output(100)).doesNotContain("windows log"); + assertThat(jobConsole.output(200)).doesNotContain("ubuntu log"); + } + + public void testProcessDefersLiveLogPermissionFailuresUntilFinalLogIsAvailable() throws Exception { + final AtomicInteger statusCalls = new AtomicInteger(0); + final AtomicInteger jobCalls = new AtomicInteger(0); + final CapturingJobConsole jobConsole = new CapturingJobConsole(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> adminLiveLogResponseFor(request, statusCalls, jobCalls), + request -> List.of(new GitHubRequestAuthorizations.Authorization("github.com", "Bearer account-token")) + ); + final WorkflowRunRequest request = new WorkflowRunRequest( + "https://api.github.test", + "acme", + "tool", + ".github/workflows/test.yml", + "main", + Map.of(), + "" + ); + final WorkflowRunProcessHandler handler = new WorkflowRunProcessHandler( + getProject(), + request, + client, + new WorkflowRunProcessHandler.PollSettings(10, 10, 10), + jobConsole + ); + final CountDownLatch terminated = new CountDownLatch(1); + final StringBuilder output = new StringBuilder(); + handler.addProcessListener(new ProcessListener() { + @Override + public void onTextAvailable(@NotNull final ProcessEvent event, @NotNull final com.intellij.openapi.util.Key outputType) { + output.append(event.getText()); + } + + @Override + public void processTerminated(@NotNull final ProcessEvent event) { + terminated.countDown(); + } + }); + + handler.startNotify(); + + assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(output.toString()).doesNotContain("Log download failed"); + assertThat(jobConsole.output(100)) + .contains("Logs will appear when GitHub publishes them.", "Status: [RUN] in_progress", "Status: [OK] completed/success", "final log after completion") + .doesNotContain("Log download failed"); + } + + public void testProcessFetchesCompletedJobLogAfterLiveFailureBeforeRunCompletes() throws Exception { + final AtomicInteger statusCalls = new AtomicInteger(0); + final AtomicInteger jobCalls = new AtomicInteger(0); + final AtomicInteger completedLogFetchedAtStatusCall = new AtomicInteger(-1); + final CapturingJobConsole jobConsole = new CapturingJobConsole(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> completedJobLogAfterLiveFailureResponseFor(request, statusCalls, jobCalls, completedLogFetchedAtStatusCall), + request -> List.of(new GitHubRequestAuthorizations.Authorization("github.com", "Bearer account-token")) + ); + final WorkflowRunRequest request = new WorkflowRunRequest( + "https://api.github.test", + "acme", + "tool", + ".github/workflows/test.yml", + "main", + Map.of(), + "" + ); + final WorkflowRunProcessHandler handler = new WorkflowRunProcessHandler( + getProject(), + request, + client, + new WorkflowRunProcessHandler.PollSettings(10, 10, 10, 60_000), + jobConsole + ); + final CountDownLatch terminated = new CountDownLatch(1); + final StringBuilder output = new StringBuilder(); + handler.addProcessListener(new ProcessListener() { + @Override + public void onTextAvailable(@NotNull final ProcessEvent event, @NotNull final com.intellij.openapi.util.Key outputType) { + output.append(event.getText()); + } + + @Override + public void processTerminated(@NotNull final ProcessEvent event) { + terminated.countDown(); + } + }); + + handler.startNotify(); + + assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(completedLogFetchedAtStatusCall.get()).isEqualTo(2); + assertThat(output.toString()).doesNotContain("Log download failed"); + assertThat(jobConsole.output(100)) + .contains("Status: [RUN] in_progress", "Status: [OK] completed/success", "job log before run completed") + .doesNotContain("Log download failed"); + } + + public void testProcessUsesEnterpriseWorkflowUrlInDispatchMessage() throws Exception { + final AtomicInteger statusCalls = new AtomicInteger(0); + final AtomicInteger jobCalls = new AtomicInteger(0); + final AtomicInteger logCalls = new AtomicInteger(0); + final WorkflowRunClient client = new WorkflowRunClient( + request -> responseFor(request, statusCalls, jobCalls, logCalls), + request -> List.of(GitHubRequestAuthorizations.Authorization.anonymous()) + ); + final WorkflowRunRequest request = new WorkflowRunRequest( + "https://github.acme.test/api/v3", + "tools", + "workflow-box", + ".github/workflows/test.yml", + "feature/live-logs", + Map.of(), + "" + ); + final WorkflowRunProcessHandler handler = new WorkflowRunProcessHandler( + getProject(), + request, + client, + new WorkflowRunProcessHandler.PollSettings(10, 10, 10) + ); + final CountDownLatch terminated = new CountDownLatch(1); + final StringBuilder output = new StringBuilder(); + handler.addProcessListener(new ProcessListener() { + @Override + public void onTextAvailable(@NotNull final ProcessEvent event, @NotNull final com.intellij.openapi.util.Key outputType) { + output.append(event.getText()); + } + + @Override + public void processTerminated(@NotNull final ProcessEvent event) { + terminated.countDown(); + } + }); + + handler.startNotify(); + + assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(output.toString()) + .contains("https://github.acme.test/tools/workflow-box/blob/feature/live-logs/.github/workflows/test.yml") + .contains("tools/workflow-box") + .contains("feature/live-logs"); + } + + public void testProcessRetriesLiveLogAfterDeferredHttpFailure() throws Exception { + final AtomicInteger statusCalls = new AtomicInteger(0); + final AtomicInteger logCalls = new AtomicInteger(0); + final CapturingJobConsole jobConsole = new CapturingJobConsole(); + final WorkflowRunClient client = new WorkflowRunClient( + request -> retryableLiveLogResponseFor(request, statusCalls, logCalls), + request -> List.of(new GitHubRequestAuthorizations.Authorization("github.com", "Bearer account-token")) + ); + final WorkflowRunRequest request = new WorkflowRunRequest( + "https://api.github.test", + "acme", + "tool", + ".github/workflows/test.yml", + "main", + Map.of(), + "" + ); + final WorkflowRunProcessHandler handler = new WorkflowRunProcessHandler( + getProject(), + request, + client, + new WorkflowRunProcessHandler.PollSettings(20, 1, 10, 1), + jobConsole + ); + final CountDownLatch terminated = new CountDownLatch(1); + final StringBuilder output = new StringBuilder(); + handler.addProcessListener(new ProcessListener() { + @Override + public void onTextAvailable(@NotNull final ProcessEvent event, @NotNull final com.intellij.openapi.util.Key outputType) { + output.append(event.getText()); + } + + @Override + public void processTerminated(@NotNull final ProcessEvent event) { + terminated.countDown(); + } + }); + + handler.startNotify(); + + assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(logCalls.get()).isGreaterThanOrEqualTo(2); + assertThat(output.toString()).doesNotContain("Log download failed"); + assertThat(jobConsole.output(100)) + .contains("live log after retry") + .doesNotContain("Log download failed"); + } + + private static HttpResponse responseFor( + final HttpRequest request, + final AtomicInteger statusCalls, + final AtomicInteger jobCalls, + final AtomicInteger logCalls + ) { + final String path = request.uri().getPath(); + if (path.endsWith("/dispatches")) { + return response(request, 200, "{\"workflow_run_id\":42,\"html_url\":\"html-run\"}"); + } + if (path.endsWith("/runs/42/jobs")) { + final int call = jobCalls.incrementAndGet(); + final String status = call == 1 ? "in_progress" : "completed"; + final String conclusion = call == 1 ? "" : ",\"conclusion\":\"success\""; + return response(request, 200, "{\"jobs\":[{\"id\":100,\"name\":\"build\",\"status\":\"" + status + "\"" + conclusion + ",\"html_url\":\"job-url\"}]}"); + } + if (path.endsWith("/jobs/100/logs")) { + final int call = logCalls.incrementAndGet(); + return response(request, 200, call == 1 ? "first line\n" : "first line\nsecond line\n"); + } + if (path.endsWith("/runs/42")) { + final int call = statusCalls.incrementAndGet(); + return response(request, 200, call == 1 + ? "{\"id\":42,\"status\":\"in_progress\",\"html_url\":\"html-run\"}" + : "{\"id\":42,\"status\":\"completed\",\"conclusion\":\"success\",\"html_url\":\"html-run\"}"); + } + return response(request, 404, "{}"); + } + + private static HttpResponse multiJobResponseFor( + final HttpRequest request, + final AtomicInteger statusCalls + ) { + final String path = request.uri().getPath(); + if (path.endsWith("/dispatches")) { + return response(request, 200, "{\"workflow_run_id\":42,\"html_url\":\"html-run\"}"); + } + if (path.endsWith("/runs/42/jobs")) { + return response(request, 200, """ + {"jobs":[ + {"id":100,"name":"Node Test / test (ubuntu-latest)","status":"completed","conclusion":"success","html_url":"ubuntu-url"}, + {"id":200,"name":"Node Test / test (windows-latest)","status":"completed","conclusion":"success","html_url":"windows-url"} + ]} + """); + } + if (path.endsWith("/jobs/100/logs")) { + return response(request, 200, "ubuntu log\n"); + } + if (path.endsWith("/jobs/200/logs")) { + return response(request, 200, "windows log\n"); + } + if (path.endsWith("/runs/42")) { + statusCalls.incrementAndGet(); + return response(request, 200, "{\"id\":42,\"status\":\"completed\",\"conclusion\":\"success\",\"html_url\":\"html-run\"}"); + } + return response(request, 404, "{}"); + } + + private static HttpResponse cancellationResponseFor( + final HttpRequest request, + final CountDownLatch statusSeen, + final CountDownLatch cancelSeen + ) { + final String path = request.uri().getPath(); + if (path.endsWith("/dispatches")) { + return response(request, 200, "{\"workflow_run_id\":42,\"html_url\":\"html-run\"}"); + } + if (path.endsWith("/runs/42/cancel")) { + cancelSeen.countDown(); + return response(request, 202, "{}"); + } + if (path.endsWith("/runs/42/jobs")) { + return response(request, 200, "{\"jobs\":[]}"); + } + if (path.endsWith("/runs/42")) { + statusSeen.countDown(); + return response(request, 200, "{\"id\":42,\"status\":\"in_progress\",\"html_url\":\"html-run\"}"); + } + return response(request, 404, "{}"); + } + + private static HttpResponse completedRunWithDeleteResponseFor( + final HttpRequest request, + final CountDownLatch deleteSeen + ) { + final String path = request.uri().getPath(); + if (path.endsWith("/dispatches")) { + return response(request, 200, "{\"workflow_run_id\":42,\"html_url\":\"html-run\"}"); + } + if ("DELETE".equals(request.method()) && path.endsWith("/runs/42")) { + deleteSeen.countDown(); + return response(request, 204, ""); + } + if (path.endsWith("/runs/42/jobs")) { + return response(request, 200, "{\"jobs\":[]}"); + } + if (path.endsWith("/runs/42")) { + return response(request, 200, "{\"id\":42,\"status\":\"completed\",\"conclusion\":\"success\",\"html_url\":\"html-run\"}"); + } + return response(request, 404, "{}"); + } + + private static HttpResponse completedRunWithRerunResponseFor( + final HttpRequest request, + final CountDownLatch rerunAllSeen, + final CountDownLatch rerunFailedSeen + ) { + final String path = request.uri().getPath(); + if (path.endsWith("/dispatches")) { + return response(request, 200, "{\"workflow_run_id\":42,\"html_url\":\"html-run\"}"); + } + if (path.endsWith("/runs/42/rerun")) { + rerunAllSeen.countDown(); + return response(request, 201, "{}"); + } + if (path.endsWith("/runs/42/rerun-failed-jobs")) { + rerunFailedSeen.countDown(); + return response(request, 201, "{}"); + } + if (path.endsWith("/runs/42/jobs")) { + return response(request, 200, "{\"jobs\":[]}"); + } + if (path.endsWith("/runs/42")) { + return response(request, 200, "{\"id\":42,\"status\":\"completed\",\"conclusion\":\"success\",\"html_url\":\"html-run\"}"); + } + return response(request, 404, "{}"); + } + + private static HttpResponse adminLiveLogResponseFor( + final HttpRequest request, + final AtomicInteger statusCalls, + final AtomicInteger jobCalls + ) { + final String path = request.uri().getPath(); + if (path.endsWith("/dispatches")) { + return response(request, 200, "{\"workflow_run_id\":42,\"html_url\":\"html-run\"}"); + } + if (path.endsWith("/runs/42/jobs")) { + final int call = jobCalls.incrementAndGet(); + final String status = call == 1 ? "in_progress" : "completed"; + final String conclusion = call == 1 ? "" : ",\"conclusion\":\"success\""; + return response(request, 200, "{\"jobs\":[{\"id\":100,\"name\":\"build\",\"status\":\"" + status + "\"" + conclusion + ",\"html_url\":\"job-url\"}]}"); + } + if (path.endsWith("/jobs/100/logs")) { + return jobCalls.get() == 1 + ? response(request, 403, "{\"message\":\"Must have admin rights to Repository.\"}") + : response(request, 200, "final log after completion\n"); + } + if (path.endsWith("/runs/42")) { + final int call = statusCalls.incrementAndGet(); + return response(request, 200, call == 1 + ? "{\"id\":42,\"status\":\"in_progress\",\"html_url\":\"html-run\"}" + : "{\"id\":42,\"status\":\"completed\",\"conclusion\":\"success\",\"html_url\":\"html-run\"}"); + } + return response(request, 404, "{}"); + } + + private static HttpResponse completedJobLogAfterLiveFailureResponseFor( + final HttpRequest request, + final AtomicInteger statusCalls, + final AtomicInteger jobCalls, + final AtomicInteger completedLogFetchedAtStatusCall + ) { + final String path = request.uri().getPath(); + if (path.endsWith("/dispatches")) { + return response(request, 200, "{\"workflow_run_id\":42,\"html_url\":\"html-run\"}"); + } + if (path.endsWith("/runs/42/jobs")) { + final int call = jobCalls.incrementAndGet(); + final String status = call == 1 ? "in_progress" : "completed"; + final String conclusion = call == 1 ? "" : ",\"conclusion\":\"success\""; + return response(request, 200, "{\"jobs\":[{\"id\":100,\"name\":\"build\",\"status\":\"" + status + "\"" + conclusion + "}]}"); + } + if (path.endsWith("/jobs/100/logs")) { + if (jobCalls.get() == 1) { + return response(request, 403, "{\"message\":\"Must have admin rights to Repository.\"}"); + } + completedLogFetchedAtStatusCall.compareAndSet(-1, statusCalls.get()); + return response(request, 200, "job log before run completed\n"); + } + if (path.endsWith("/runs/42")) { + final int call = statusCalls.incrementAndGet(); + return response(request, 200, call < 3 + ? "{\"id\":42,\"status\":\"in_progress\",\"html_url\":\"html-run\"}" + : "{\"id\":42,\"status\":\"completed\",\"conclusion\":\"success\",\"html_url\":\"html-run\"}"); + } + return response(request, 404, "{}"); + } + + private static HttpResponse retryableLiveLogResponseFor( + final HttpRequest request, + final AtomicInteger statusCalls, + final AtomicInteger logCalls + ) { + final String path = request.uri().getPath(); + if (path.endsWith("/dispatches")) { + return response(request, 200, "{\"workflow_run_id\":42,\"html_url\":\"html-run\"}"); + } + if (path.endsWith("/runs/42/jobs")) { + final boolean completed = statusCalls.get() >= 4; + final String status = completed ? "completed" : "in_progress"; + final String conclusion = completed ? ",\"conclusion\":\"success\"" : ""; + return response(request, 200, "{\"jobs\":[{\"id\":100,\"name\":\"build\",\"status\":\"" + status + "\"" + conclusion + "}]}"); + } + if (path.endsWith("/jobs/100/logs")) { + final int call = logCalls.incrementAndGet(); + if (call == 1) { + return response(request, 504, "GitHub timeout"); + } + return response(request, 200, call == 2 + ? "live log after retry\n" + : "live log after retry\nfinal log\n"); + } + if (path.endsWith("/runs/42")) { + final int call = statusCalls.incrementAndGet(); + return response(request, 200, call < 4 + ? "{\"id\":42,\"status\":\"in_progress\",\"html_url\":\"html-run\"}" + : "{\"id\":42,\"status\":\"completed\",\"conclusion\":\"success\",\"html_url\":\"html-run\"}"); + } + return response(request, 404, "{}"); + } + + private static Response response(final HttpRequest request, final int status, final String body) { + return new Response(request, status, body); + } + + private static boolean waitForWorkflowOutput(final CapturingJobConsole console, final String text) throws InterruptedException { + for (int attempt = 0; attempt < 50; attempt++) { + if (console.workflowOutput().contains(text)) { + return true; + } + TimeUnit.MILLISECONDS.sleep(100); + } + return false; + } + + private static boolean waitForDeletedRun(final CapturingJobConsole console, final long runId) throws InterruptedException { + for (int attempt = 0; attempt < 50; attempt++) { + if (console.deleted().contains(runId)) { + return true; + } + TimeUnit.MILLISECONDS.sleep(100); + } + return false; + } + + private record Response(HttpRequest request, int statusCode, String body) implements HttpResponse { + @Override + public HttpHeaders headers() { + return HttpHeaders.of(Map.of(), (left, right) -> true); + } + + @Override + public Optional> previousResponse() { + return Optional.empty(); + } + + @Override + public URI uri() { + return request.uri(); + } + + @Override + public Version version() { + return Version.HTTP_1_1; + } + + @Override + public Optional sslSession() { + return Optional.empty(); + } + } + + private static final class CapturingJobConsole implements WorkflowRunJobConsole { + private final Object lock = new Object(); + private final Map output = new HashMap<>(); + private final StringBuilder workflowOutput = new StringBuilder(); + private final java.util.ArrayList finished = new java.util.ArrayList<>(); + private final java.util.ArrayList deleted = new java.util.ArrayList<>(); + + @Override + public boolean jobStatus(final WorkflowRunClient.JobStatus job, final String text) { + append(job, text); + return true; + } + + @Override + public boolean jobStdout(final WorkflowRunClient.JobStatus job, final String text) { + append(job, text); + return true; + } + + @Override + public boolean jobStderr(final WorkflowRunClient.JobStatus job, final String text) { + append(job, text); + return true; + } + + @Override + public boolean jobLog(final WorkflowRunClient.JobStatus job, final String text) { + WorkflowRunLogRenderer.renderOnce(text).forEach(segment -> append(job, segment.text())); + return true; + } + + @Override + public void workflowStatus(final String text, final boolean error) { + synchronized (lock) { + workflowOutput.append(text); + } + } + + @Override + public void runFinished(final long runId, final String conclusion) { + synchronized (lock) { + finished.add(runId + ":" + conclusion); + } + } + + @Override + public void runDeleted(final long runId) { + synchronized (lock) { + deleted.add(runId); + } + } + + private void append(final WorkflowRunClient.JobStatus job, final String text) { + synchronized (lock) { + output.computeIfAbsent(job.id(), ignored -> new StringBuilder()).append(text); + } + } + + private String output(final long jobId) { + synchronized (lock) { + return output.getOrDefault(jobId, new StringBuilder()).toString(); + } + } + + private String workflowOutput() { + synchronized (lock) { + return workflowOutput.toString(); + } + } + + private List finished() { + synchronized (lock) { + return List.copyOf(finished); + } + } + + private List deleted() { + synchronized (lock) { + return List.copyOf(deleted); + } + } + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditorTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditorTest.java new file mode 100644 index 0000000..63ada58 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditorTest.java @@ -0,0 +1,25 @@ +package com.github.yunabraska.githubworkflow.services; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowRunSettingsEditorTest extends EditorFeatureTestCase { + + public void testResetUsesOnlyTheSelectedConfigurationInputs() throws Exception { + final WorkflowRunSettingsEditor editor = new WorkflowRunSettingsEditor(); + final WorkflowRunConfiguration first = configuration("first").inputsText("old_key=old-value\n"); + final WorkflowRunConfiguration second = configuration("second").inputsText("new_key=new-value\n"); + + editor.resetFrom(first); + editor.applyTo(first); + editor.resetFrom(second); + editor.applyTo(second); + + assertThat(second.toRequest().inputs()) + .containsEntry("new_key", "new-value") + .doesNotContainKey("old_key"); + } + + private WorkflowRunConfiguration configuration(final String name) { + return new WorkflowRunConfiguration(getProject(), WorkflowRunConfigurationType.getInstance().factory(), name); + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowShowcaseTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowShowcaseTest.java new file mode 100644 index 0000000..608782d --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowShowcaseTest.java @@ -0,0 +1,172 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.psi.PsiFile; + +import java.util.Map; + +public class WorkflowShowcaseTest extends EditorFeatureTestCase { + + public void testLargeShowcaseWorkflowHighlightsWithoutErrors() { + final PsiFile localAction = myFixture.addFileToProject(".github/actions/local/action.yml", """ + name: Local Action + inputs: + target: + description: Target + outputs: + local-artifact: + description: Local artifact + runs: + using: composite + steps: + - id: package + run: echo "local-artifact=dist/local" >> "$GITHUB_OUTPUT" + shell: sh + """); + final PsiFile reusableWorkflow = myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Local Reusable + on: + workflow_call: + inputs: + config: + type: string + secrets: + access-token: + required: true + outputs: + artifact: + value: ${{ jobs.build.outputs.artifact }} + jobs: + build: + runs-on: ubuntu-latest + outputs: + artifact: ${{ steps.package.outputs.artifact }} + steps: + - id: package + run: echo "artifact=dist/reusable" >> "$GITHUB_OUTPUT" + """); + seedLocalAction("./.github/actions/local", localAction); + seedLocalAction("./.github/workflows/reusable.yml", reusableWorkflow); + seedRemoteAction( + "actions/checkout@v4", + Map.of("fetch-depth", "Number of commits to fetch"), + Map.of() + ); + seedRemoteAction( + "owner/tool@v1", + Map.of("target", "Target", "path", "Path"), + Map.of("artifact", "Artifact", "status", "Status") + ); + seedRemoteAction( + "owner/repo/.github/workflows/deploy.yml@v1", + Map.of("config", "Config"), + Map.of("remote-artifact", "Remote artifact"), + Map.of("access-token", "Access token") + ); + + configureWorkflowProjectFile(showcaseWorkflow()); + myFixture.checkHighlighting(true, false, true); + } + + private static String showcaseWorkflow() { + return """ + name: Showcase + on: + push: + branches: [main] + tags: ["v*"] + workflow_dispatch: + inputs: + deploy-target: + type: choice + options: [staging, production] + default: staging + workflow_call: + inputs: + deploy-target: + type: string + default: staging + secrets: + access-token: + required: true + permissions: + contents: read + actions: read + concurrency: + group: showcase-${{ github.ref_name }} + cancel-in-progress: true + env: &workflow_env + TOP_LEVEL: production + CACHE_SCOPE: ${{ github.ref_name }} + jobs: + build: + name: Build ${{ matrix.os }} / ${{ matrix.node }} + runs-on: ${{ matrix.os }} + env: + <<: *workflow_env + JOB_LEVEL: build + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + node: [20, 22] + include: + - os: ubuntu-latest + node: 24 + experimental: true + services: + postgres: + image: postgres:16 + ports: + - 5432:5432 + outputs: + artifact: ${{ steps.package.outputs.artifact }} + tee-version: ${{ steps.version.outputs.dev_version }} + remote-artifact: ${{ steps.remote.outputs.artifact }} + steps: + - uses: actions/checkout@v4 + - id: package + run: | + { + echo "artifact<> "$GITHUB_OUTPUT" + - id: version + run: echo "dev_version=$GITHUB_REF_NAME" | tee -a $GITHUB_OUTPUT $GITHUB_STEP_SUMMARY + - id: remote + uses: owner/tool@v1 + with: + target: ${{ inputs.deploy-target }} + path: ${{ steps.package.outputs.artifact }} + - uses: ./.github/actions/local + with: + target: ${{ env.TOP_LEVEL }} + - run: echo "${{ job.services.postgres.ports[5432] }} ${{ runner.environment }} ${{ strategy.fail-fast }}" + local-call: + needs: build + uses: ./.github/workflows/reusable.yml + with: + config: ${{ needs.build.outputs.artifact }} + secrets: + access-token: ${{ secrets.access-token }} + remote-call: + needs: build + uses: owner/repo/.github/workflows/deploy.yml@v1 + with: + config: ${{ needs.build.outputs.tee-version }} + secrets: + access-token: ${{ secrets.access-token }} + publish: + needs: [build, local-call, remote-call] + if: always() && startsWith(github.ref, 'refs/tags/') && needs.build.result == 'success' + runs-on: ubuntu-latest + steps: + - run: | + echo "${{ needs.build.outputs.artifact }}" + echo "${{ needs.build.outputs.remote-artifact }}" + echo "${{ needs.local-call.outputs.artifact }}" + echo "${{ needs.remote-call.outputs.remote-artifact }}" + echo "${{ vars.DEPLOY_TARGET }} ${{ gitea.ref_name }}" + """; + } +} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowStylingTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowStylingTest.java new file mode 100644 index 0000000..2999875 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowStylingTest.java @@ -0,0 +1,404 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.model.GitHubAction; + +import java.util.List; +import java.util.Map; + +import static com.github.yunabraska.githubworkflow.services.GitHubActionCache.getActionCache; + +public class WorkflowStylingTest extends EditorFeatureTestCase { + + public void testResolvedRemoteActionUseIsStyledAsReference() { + seedRemoteAction("owner/tool@v1", Map.of(), Map.of()); + + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + """); + + assertHighlightedReferenceAtCurrentCaret(); + } + + public void testConfiguredGithubEnterpriseRemoteActionUseIsStyledAsReference() throws Exception { + try (FakeRemoteServer server = new FakeRemoteServer()) { + server.addContent("acme", "tool", "action.yml", "main", """ + name: Enterprise Tool + runs: + using: composite + steps: + - run: echo ok + shell: sh + """); + RemoteServerSettings.getInstance().setCustomServers(List.of(new RemoteServerSettings.Server("Fake Enterprise", + server.webUrl(), + server.apiUrl("/api/v3"), + "", + true + ))); + final String usesValue = server.webUrl() + "/acme/tool@main"; + final GitHubAction action = GitHubAction.createGithubAction(false, usesValue, usesValue).resolve(); + getActionCache().getState().actions.put(usesValue, action); + + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: %s/acme/tool@main + """.formatted(server.webUrl())); + + assertHighlightedReferenceAtCurrentCaret(); + } + } + + public void testResolvedLocalWorkflowUseIsStyledAsReference() { + seedLocalAction("./.github/workflows/reusable.yml", myFixture.addFileToProject(".github/workflows/reusable.yml", """ + name: Reusable + on: workflow_call + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """)); + + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + call: + uses: ./.github/workflows/reusable.yml + """); + + assertHighlightedReferenceAtCurrentCaret(); + } + + public void testWorkflowInputExpressionIsStyledAsReference() { + configureWorkflowProjectFile(""" + name: Styling + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.known-input }}" + """); + + assertHighlightedReferenceAtCurrentCaret(); + } + + public void testWorkflowInputExpressionUsesWorkflowVariableColor() { + configureWorkflowProjectFile(""" + name: Styling + on: + workflow_call: + inputs: + known-input: + type: string + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.known-input }}" + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.VARIABLE_REFERENCE); + } + + public void testUnresolvedExpressionContextSegmentUsesWorkflowVariableColor() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ steps.pom.outputs.has_pom }}" + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.VARIABLE_REFERENCE); + } + + public void testAutomaticGithubTokenSecretUsesWorkflowVariableColorWithoutReferenceRequirement() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ secrets.GITHUB_TOKEN }}" + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.VARIABLE_REFERENCE); + } + + public void testIfExpressionWithoutBracesUsesWorkflowVariableColor() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.VARIABLE_REFERENCE); + } + + public void testRunCommandGithubOutputUsesRunnerVariableColor() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "java_version=21" >> "$GITHUB_OUTPUT" + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.RUNNER_VARIABLE); + } + + public void testBooleanAndNumberScalarsUseLiteralColor() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + generateReleaseNotes: true + fetch-depth: 500 + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.SCALAR_LITERAL); + } + + public void testNumberScalarsUseLiteralColor() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: owner/tool@v1 + with: + fetch-depth: 500 + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.SCALAR_LITERAL); + } + + public void testJobIdUsesWorkflowDeclarationColor() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.DECLARATION); + } + + public void testStepIdUsesWorkflowDeclarationColor() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo ok + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.DECLARATION); + } + + public void testMixedStepNameTextDoesNotUseWorkflowVariableOrDeclarationColor() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: java_info + run: echo "project_version=1.0.0" >> "$GITHUB_OUTPUT" + - id: semver_info + run: echo "clean_semver=1.0.1" >> "$GITHUB_OUTPUT" + - name: "Update Project Version (Maven Only) [${{ steps.java_info.outputs.project_version }} > ${{ steps.semver_info.outputs.clean_semver }}]" + run: echo ok + """); + + assertNoTextAttributeAtCurrentCaret(WorkflowTextAttributes.VARIABLE_REFERENCE); + assertNoTextAttributeAtCurrentCaret(WorkflowTextAttributes.DECLARATION); + } + + public void testMixedStepNameExpressionUsesWorkflowVariableColorOnlyInsideExpression() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: java_info + run: echo "project_version=1.0.0" >> "$GITHUB_OUTPUT" + - id: semver_info + run: echo "clean_semver=1.0.1" >> "$GITHUB_OUTPUT" + - name: "Update Project Version (Maven Only) [${{ steps.java_info.outputs.project_version }} > ${{ steps.semver_info.outputs.clean_semver }}]" + run: echo ok + """); + + assertTextAttributeAtCurrentCaret(WorkflowTextAttributes.VARIABLE_REFERENCE); + } + + public void testWorkflowCallInputDefaultExpressionIsStyledAsReference() { + configureWorkflowProjectFile(""" + name: Styling + on: + workflow_call: + inputs: + known-input: + type: string + target: + type: string + default: ${{ inputs.known-input }} + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertHighlightedReferenceAtCurrentCaret(); + } + + public void testEnvExpressionIsStyledAsReference() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + env: + TOP_LEVEL: top + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "${{ env.TOP_LEVEL }}" + """); + + assertHighlightedReferenceAtCurrentCaret(); + } + + public void testJobEnvMapAliasExpressionIsStyledAsReference() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + define: + runs-on: ubuntu-latest + env: &env_vars + NODE_ENV: production + steps: + - run: echo ok + reuse: + runs-on: ubuntu-latest + env: *env_vars + steps: + - run: echo "${{ env.NODE_ENV }}" + """); + + assertHighlightedReferenceAtCurrentCaret(); + } + + public void testMatrixExpressionIsStyledAsReference() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - run: echo "${{ matrix.os }}" + """); + + assertHighlightedReferenceAtCurrentCaret(); + } + + public void testStepOutputExpressionIsStyledAsReference() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - id: package + run: echo "artifact=dist" >> "$GITHUB_OUTPUT" + - run: echo "${{ steps.package.outputs.artifact }}" + """); + + assertHighlightedReferenceAtCurrentCaret(); + } + + public void testNeedsScalarIsStyledAsReference() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo ok + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo ok + """); + + assertHighlightedReferenceAtCurrentCaret(); + } + + public void testJobServiceExpressionIsStyledAsReference() { + configureWorkflowProjectFile(""" + name: Styling + on: workflow_dispatch + jobs: + build: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16 + steps: + - run: echo "${{ job.services.postgres.network }}" + """); + + assertHighlightedReferenceAtCurrentCaret(); + } +} diff --git a/src/test/resources/testdata/.github/action.yml b/src/test/resources/testdata/.github/action.yml deleted file mode 100644 index 8612a25..0000000 --- a/src/test/resources/testdata/.github/action.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: 'Java Info Action' -description: 'Fast Maven/Gradle parser detects and extracts info such as Java version, project version, encoding, build cmd and props,...' -inputs: - deep: - description: 'folder scan deep (-1 == endless)' - required: false - default: '-1' - work-dir: - description: 'folder scan ("." == current)' - required: false - default: '.' - jv-fallback: - description: 'java version fallback if no java version was found' - required: false - default: '17' - pv-fallback: - description: 'project version fallback if no project version was found' - required: false - default: 'null' - pe-fallback: - description: 'project encoding fallback if no project encoding was found' - required: false - default: 'null' - custom-gradle-cmd: - required: false - description: 'custom command for output "cmd_custom" which adds automatically gradle/maven prefix' - custom-maven-cmd: - required: false - description: 'custom command for output "cmd_custom" which adds automatically gradle/maven prefix' - null-to-empty: - description: 'converts null to empty string' - required: false - default: 'true' -outputs: - java_version: - description: 'java version - parsed from build files e.g. 6,7,8,9,10,11' - java_version_legacy: - description: 'java version - parsed from build files e.g. 1.6,1.7,1.8,1.9,10,11' - project_version: - description: 'project version - parsed from build files e.g. 1.2.3' - project_encoding: - description: 'project encoding - parsed from build files e.g. utf-8' - has_wrapper: - description: 'if a wrapper exists - e.g. gradlew, mvnw,...' - builder_name: - description: 'Name of the builder [Gradle, Maven, null]' - builder_version: - description: 'version of the wrapper' - is_gradle: - description: 'true if a gradle build was found' - is_maven: - description: 'true if a maven build was found' - artifact_name: - description: 'artifact name if defined e.g. "archiveFileName, baseName, finalName"' - artifact_name_jar: - description: 'artifact name ending with ".jar" if defined e.g. "archiveFileName, baseName, finalName"' - artifact_names: - description: 'artifact names if defined e.g. "archiveFileName, baseName, finalName"' - artifact_names_jar: - description: 'artifact names ending with ".jar" if defined e.g. "archiveFileName, baseName, finalName"' - cmd: - description: 'build command e.g. gradle, gradlew, gradle.bat, mvn, mvnw, mvn.bat' - cmd_custom: - description: "Concatenation of 'cmd' + 'custom-gradle-cmd' or 'custom-maven-cmd'" - cmd_test: - description: 'build command e.g. gradle, gradlew, gradle.bat, mvn, mvnw, mvn.bat' - cmd_build: - description: 'test command e.g. gradle clean build -x test / maven clean package -DskipTests' - cmd_test_build: - description: 'test command e.g. gradle clean build / maven clean package' - cmd_update_deps: - description: 'test command e.g. gradle check / maven versions:use-latest-versions -B -q -DgenerateBackupPoms=false' - cmd_update_plugs: - description: 'test command e.g. gradle check / maven versions:use-latest-versions -B -q -DgenerateBackupPoms=false' - cmd_update_props: - description: 'test command e.g. gradle check / maven versions:update-properties -B -q -DgenerateBackupPoms=false' - cmd_update_parent: - description: 'test command e.g. gradle check / maven versions:update-parent -B -q -DgenerateBackupPoms=false' - cmd_update_wrapper: - description: 'test command e.g. gradle gradle wrapper --gradle-version latest / maven -B -q -N io.takari:maven:wrapper' -runs: - using: 'node16' - main: 'dist/index.js' -branding: - icon: file-text - color: purple diff --git a/src/test/resources/testdata/.github/issue_06.yml b/src/test/resources/testdata/.github/issue_06.yml deleted file mode 100644 index 6458d2d..0000000 --- a/src/test/resources/testdata/.github/issue_06.yml +++ /dev/null @@ -1,105 +0,0 @@ -name: Code Samples -on: - push: - branches: [ main ] - paths: [ 'code_samples/**','.github/workflows/issue_06.yml' ] - pull_request: - paths: [ 'code_samples/**','.github/workflows/issue_06.yml' ] - -env: - PLUGIN_VERIFIER_IDE_VERSIONS: '2022.3.3 2023.1.5 2023.2' - -jobs: - - gradleValidation: - name: Gradle Wrappers - runs-on: ubuntu-latest - steps: - - name: Fetch Sources - uses: actions/checkout@v3 - - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v1.1.0 - - samples: - name: Code Samples / ${{ matrix.plugin }} - needs: gradleValidation - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - plugin: - - action_basics - - comparing_string_references_inspection - - conditional_operator_intention - - editor_basics - - facet_basics - - framework_basics - - kotlin_demo - - live_templates - - max_opened_projects - - module - - project_model - - project_view_pane - - project_wizard - - psi_demo - - run_configuration - - simple_language_plugin - - tool_window - - tree_structure_provider - steps: - - name: Fetch Sources - uses: actions/checkout@v3 - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: zulu - java-version: 17 - - - name: Verify Plugin - run: (cd code_samples/${{ matrix.plugin }}; ./gradlew verifyPlugin) - - name: Test Plugin - run: (cd code_samples/${{ matrix.plugin }}; ./gradlew test) - - - name: Export Properties - id: properties - shell: bash - run: | - echo "ideVersions=${PLUGIN_VERIFIER_IDE_VERSIONS// /-}" >> $GITHUB_OUTPUT - echo "pluginVerifierHomeDir=~/.pluginVerifier" >> $GITHUB_OUTPUT - - name: Run Plugin Verifier - run: | - echo " - tasks { - runPluginVerifier { - ideVersions.set("\"$PLUGIN_VERIFIER_IDE_VERSIONS\"".split(' ').toList()) - } - } - " >> code_samples/${{ matrix.plugin }}/build.gradle.kts - cd code_samples/${{ matrix.plugin }} - ./gradlew runPluginVerifier -Pplugin.verifier.home.dir=${{ steps.properties.outputs.pluginVerifierHomeDir }} - - - name: Collect Plugin Verifier Result - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.plugin }}-pluginVerifier-result - path: ${{ github.workspace }}/code_samples/${{ matrix.plugin }}/build/reports/pluginVerifier - - mirror: - name: Code Samples Mirror - if: github.ref == 'refs/heads/main' && github.repository == 'JetBrains/intellij-sdk-docs' - needs: samples - runs-on: ubuntu-latest - steps: - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.SSH_KEY }} - known_hosts: ${{ secrets.KNOWN_HOSTS }} - - name: Prepare Mirror - run: | - 2022.3.3 2023.1.5 2023.2 - git clone --bare git@github.com:JetBrains/intellij-sdk-docs.git . - git branch | grep -v "main" | xargs -r git branch -D - git filter-branch --prune-empty --subdirectory-filter code_samples main - git push --mirror git@github.com:JetBrains/intellij-sdk-code-samples.git \ No newline at end of file diff --git a/src/test/resources/testdata/.github/issue_10.yml b/src/test/resources/testdata/.github/issue_10.yml deleted file mode 100644 index ad26aff..0000000 --- a/src/test/resources/testdata/.github/issue_10.yml +++ /dev/null @@ -1,131 +0,0 @@ -# URL: https://github.com/YunaBraska/github-workflow-plugin/issues/10 -# Issue 1: NullPointerException: Cannot invoke "Object.hashCode()" because "key" is null #at java.base/java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936) #at com.github.yunabraska.githubworkflow.highlights.HighlightAnnotator.lambda$annotate$23(HighlightAnnotator.java:90) - -# GitHub Actions Workflow for launching UI tests on Linux, Windows, and Mac in the following steps: -# - prepare and launch IDE with your plugin and robot-server plugin, which is needed to interact with UI -# - wait for IDE to start -# - run UI tests with separate Gradle task -# -# Please check https://github.com/JetBrains/intellij-ui-test-robot for information about UI tests with IntelliJ Platform -# -# Workflow is triggered manually. - -name: Run UI Tests -on: - workflow_dispatch: - push: - branches: - - main - -jobs: - - testUI: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - runIde: | - export DISPLAY=:99.0 - Xvfb -ac :99 -screen 0 1920x1080x16 & - gradle runIdeForUiTests & - # - os: windows-latest - # runIde: start gradlew.bat runIdeForUiTests - - os: macos-latest - runIde: ./gradlew runIdeForUiTests & - - steps: - - # Check out current repository - - name: Fetch Sources - uses: actions/checkout@v3 - - - # Setup Java environment for the next steps - - name: Setup Java - uses: actions/setup-java@v3.10.0 - with: - distribution: zulu - java-version: 17 - - # Setup Gradle - - name: Setup Gradle - uses: gradle/gradle-build-action@v2.7.0 - - - - name: Set SSH - uses: webfactory/ssh-agent@v0.8.0 - with: - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - - - name: Set Git user - run: | - git config --global user.name "Jonathan Gafner" - git config --global user.email "jgafner@dorkag.com" - - # Run IDEA prepared for UI testing - - name: Run IDE - run: ${{ matrix.runIde }} - - # Wait for IDEA to be started - - name: Health Check - uses: jtalk/url-health-check-action@v3 - with: - url: http://127.0.0.1:8082 - max-attempts: 15 - retry-delay: 30s - - # Run tests - - name: Tests - env: - AZD_TOKEN: ${{ secrets.AZD_TOKEN }} - run: ./gradlew check -PrunUiTests=true - - # Collect Tests Result of failed tests - - name: Collect Tests Result - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: tests-result-${{ matrix.os }} - path: ${{ github.workspace }}/build/reports - - - name: Save Coverage Report - uses: actions/upload-artifact@v3 - with: - name: coverage-${{ matrix.os }} - path: ${{ github.workspace }}/build/reports/kover/report.xml - - aggregateCoverage: - needs: testUI - runs-on: ubuntu-latest - steps: - # Download coverage reports from each OS run - - name: Download Coverage from Ubuntu - uses: actions/download-artifact@v3 - with: - name: coverage-ubuntu-latest - path: coverage-reports/ubuntu-latest/ - - # Uncomment if you add windows to the matrix in the future - # - name: Download Coverage from Windows - # uses: actions/download-artifact@v3 - # with: - # name: coverage-windows-latest - # path: coverage-reports/windows-latest/ - - - name: Download Coverage from MacOS - uses: actions/download-artifact@v3 - with: - name: coverage-macos-latest - path: coverage-reports/macos-latest/ - - # Upload all coverage reports to codecov - - name: Upload Aggregate Code Coverage Report - uses: codecov/codecov-action@v3 - with: - directory: coverage-reports/ - flags: integration - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - diff --git a/src/test/resources/testdata/.github/issue_24.yml b/src/test/resources/testdata/.github/issue_24.yml deleted file mode 100644 index a2eb75e..0000000 --- a/src/test/resources/testdata/.github/issue_24.yml +++ /dev/null @@ -1,17 +0,0 @@ -# URL: https://github.com/YunaBraska/github-workflow-plugin/issues/24 -jobs: - build_number: - name: Generate next Build Number - runs-on: Ubuntu-latest - concurrency: generate_build_number-${{ inputs.concurrency-suffix }} - outputs: - # Issue 1: `steps.buildnumber` was not recognized - build_number: ${{ steps.buildnumber.outputs.build_number }} - permissions: - contents: write - steps: - - id: buildnumber - name: Generate Build Number - uses: onyxmueller/build-tag-number@v1 - with: - token: ${{ secrets.github_token }} diff --git a/src/test/resources/testdata/.github/issue_25.yml b/src/test/resources/testdata/.github/issue_25.yml deleted file mode 100644 index 3122f3a..0000000 --- a/src/test/resources/testdata/.github/issue_25.yml +++ /dev/null @@ -1,16 +0,0 @@ -# URL: https://github.com/YunaBraska/github-workflow-plugin/issues/25 -jobs: - docker-push: - runs-on: 'ubuntu-20.04' - steps: - - name: Read version from ref - id: version - shell: pwsh - # Issue 1: GitHub output `$env:GITHUB_OUTPUT` was not recognized - run: echo "version=$(./Scripts/Get-Version.ps1 -RefName $env:GITHUB_REF)" >> $env:GITHUB_OUTPUT - - name: Build and push Docker image - uses: docker/build-push-action@v4 - with: - push: true - # Issue 2: `steps.version` was not recognized - tags: test:v${{ steps.version.outputs.version }} diff --git a/src/test/resources/testdata/.github/issue_29.yml b/src/test/resources/testdata/.github/issue_29.yml deleted file mode 100644 index f3b8bc8..0000000 --- a/src/test/resources/testdata/.github/issue_29.yml +++ /dev/null @@ -1,34 +0,0 @@ -# URL: https://github.com/YunaBraska/github-workflow-plugin/issues/25 -name: "Trigger: On Push" - -on: - workflow_dispatch: -# push: - -jobs: - maven_update: - name: "maven" - uses: YunaBraska/YunaBraska/.github/workflows/wc_maven_update.yml@main - # has_pom ${{needs.maven_update.outputs.has_pom}} - # has_changes ${{needs.maven_update.outputs.has_changes}} - # java_version ${{needs.maven_update.outputs.java_version}} - # project_version ${{needs.maven_update.outputs.project_version}} - maven_test: - needs: maven_update - name: "maven" - uses: YunaBraska/YunaBraska/.github/workflows/wc_maven_test.yml@main - # has_pom ${{needs.maven_test.outputs.has_pom}} - # has_changes ${{needs.maven_test.outputs.has_changes}} - # java_version ${{needs.maven_test.outputs.java_version}} - # project_version ${{needs.maven_test.outputs.project_version}} - # has_publish_to_code_climate ${{needs.maven_test.outputs.has_publish_to_code_climate}} - do_something: - needs: maven_test - runs-on: ubuntu-latest - steps: - - name: "Set up JDK" - uses: actions/setup-java@main - with: - # Issue 1: GitHub Workflow output `java_version` was not recognized - java-version: ${{needs.maven_test.outputs.java_version}} - distribution: 'adopt' diff --git a/src/test/resources/testdata/.github/local_references.yml b/src/test/resources/testdata/.github/local_references.yml deleted file mode 100644 index b7f4cd6..0000000 --- a/src/test/resources/testdata/.github/local_references.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: "DEPLOY [JAVA]" -#type: Workflow Trigger - -on: - workflow_dispatch: - -jobs: - local_0: - name: "Tests Java" - uses: ./ - with: - deep: ${{ inputs.ref }} - jv-fallback: ${{ inputs.skip_test }} - INVALID: ${{ inputs.skip_test }} - secrets: inherit - local_1: - name: "Tests Java" - uses: ./action.yml - with: - deep: ${{ inputs.ref }} - jv-fallback: ${{ inputs.skip_test }} - INVALID: ${{ inputs.skip_test }} - secrets: inherit - local_2: - name: "Tests Java" - uses: ./.github/my_action - with: - deep: ${{ inputs.ref }} - jv-fallback: ${{ inputs.skip_test }} - secrets: inherit - local_3: - name: "Tests Java" - uses: ./.github/my_action/action.yml - with: - deep: ${{ inputs.ref }} - jv-fallback: ${{ inputs.skip_test }} - secrets: inherit - INVALID: ${{ inputs.skip_test }} - diff --git a/src/test/resources/testdata/.github/my_action/action.yml b/src/test/resources/testdata/.github/my_action/action.yml deleted file mode 100644 index 8612a25..0000000 --- a/src/test/resources/testdata/.github/my_action/action.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: 'Java Info Action' -description: 'Fast Maven/Gradle parser detects and extracts info such as Java version, project version, encoding, build cmd and props,...' -inputs: - deep: - description: 'folder scan deep (-1 == endless)' - required: false - default: '-1' - work-dir: - description: 'folder scan ("." == current)' - required: false - default: '.' - jv-fallback: - description: 'java version fallback if no java version was found' - required: false - default: '17' - pv-fallback: - description: 'project version fallback if no project version was found' - required: false - default: 'null' - pe-fallback: - description: 'project encoding fallback if no project encoding was found' - required: false - default: 'null' - custom-gradle-cmd: - required: false - description: 'custom command for output "cmd_custom" which adds automatically gradle/maven prefix' - custom-maven-cmd: - required: false - description: 'custom command for output "cmd_custom" which adds automatically gradle/maven prefix' - null-to-empty: - description: 'converts null to empty string' - required: false - default: 'true' -outputs: - java_version: - description: 'java version - parsed from build files e.g. 6,7,8,9,10,11' - java_version_legacy: - description: 'java version - parsed from build files e.g. 1.6,1.7,1.8,1.9,10,11' - project_version: - description: 'project version - parsed from build files e.g. 1.2.3' - project_encoding: - description: 'project encoding - parsed from build files e.g. utf-8' - has_wrapper: - description: 'if a wrapper exists - e.g. gradlew, mvnw,...' - builder_name: - description: 'Name of the builder [Gradle, Maven, null]' - builder_version: - description: 'version of the wrapper' - is_gradle: - description: 'true if a gradle build was found' - is_maven: - description: 'true if a maven build was found' - artifact_name: - description: 'artifact name if defined e.g. "archiveFileName, baseName, finalName"' - artifact_name_jar: - description: 'artifact name ending with ".jar" if defined e.g. "archiveFileName, baseName, finalName"' - artifact_names: - description: 'artifact names if defined e.g. "archiveFileName, baseName, finalName"' - artifact_names_jar: - description: 'artifact names ending with ".jar" if defined e.g. "archiveFileName, baseName, finalName"' - cmd: - description: 'build command e.g. gradle, gradlew, gradle.bat, mvn, mvnw, mvn.bat' - cmd_custom: - description: "Concatenation of 'cmd' + 'custom-gradle-cmd' or 'custom-maven-cmd'" - cmd_test: - description: 'build command e.g. gradle, gradlew, gradle.bat, mvn, mvnw, mvn.bat' - cmd_build: - description: 'test command e.g. gradle clean build -x test / maven clean package -DskipTests' - cmd_test_build: - description: 'test command e.g. gradle clean build / maven clean package' - cmd_update_deps: - description: 'test command e.g. gradle check / maven versions:use-latest-versions -B -q -DgenerateBackupPoms=false' - cmd_update_plugs: - description: 'test command e.g. gradle check / maven versions:use-latest-versions -B -q -DgenerateBackupPoms=false' - cmd_update_props: - description: 'test command e.g. gradle check / maven versions:update-properties -B -q -DgenerateBackupPoms=false' - cmd_update_parent: - description: 'test command e.g. gradle check / maven versions:update-parent -B -q -DgenerateBackupPoms=false' - cmd_update_wrapper: - description: 'test command e.g. gradle gradle wrapper --gradle-version latest / maven -B -q -N io.takari:maven:wrapper' -runs: - using: 'node16' - main: 'dist/index.js' -branding: - icon: file-text - color: purple diff --git a/src/test/resources/testdata/.github/show_case.yml b/src/test/resources/testdata/.github/show_case.yml deleted file mode 100644 index b44968e..0000000 --- a/src/test/resources/testdata/.github/show_case.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: "PUBLISH" -# Screenshots made with 960 x 575 - but should be at least 1280px - -on: - workflow_call: - inputs: - input_1: - type: string - description: "Input Description 1" - required: false - input_2: - type: string - description: " " - required: false - secrets: - SECRET_1: - required: false - SECRET_2: - required: false - workflow_dispatch: - inputs: - input_1: - type: string - description: "Alternative" - input_2: - type: string - description: "Input Description 2" - -env: - day: "monday" - -jobs: - my_job_1: - runs-on: ubuntu-latest - outputs: - JAVA_VERSION: "${{steps.java_info.outputs.java_version}}" - IS_MAVEN: "${{steps.java_info.outputs.is_maven}}" - IS_GRADLE: "${{steps.java_info.outputs.is_gradle}}" - steps: - - name: "Checkout" - uses: actions/checkout@main - with: - ref: ${{ github.ref_name || github.head_ref }} - - name: "READ JAVA" - id: "java_info" - uses: YunaBraska/java-info-action@main - with: - INVALID: ${{ secrets.SECRET_1 }} - - name: "Invalid step 1" - uses: invalid\action1 - with: - java-version: ${{ steps.java_info.outputs.java_version }} - distribution: 'adopt' - - name: "Invalid step 2" - uses: invalid\action2 - with: - java-version: ${{ steps.java_info.outputs.java_version }} - distribution: 'adopt' - - name: "BUILD & TEST ${{steps.java_info.outputs.builder_name}}" - run: ${{ steps.java_info.outputs.cmd_test_build }} - my_job_2: - needs: my_job_1 - runs-on: ubuntu-latest - env: - job_env_1: "My Job Env 1" - job_env_2: "My Job Env 2" - steps: - - name: "Set Variables" - id: "my_variables" - run: | - echo "${{ needs.my_job_1.outputs.IS_GRADLE }}" - echo "custom_output_key=custom_output_value" >> $GITHUB_OUTPUT - echo "custom_env_key=custom_env_value" >> $GITHUB_ENV - - name: "Print Variables" - run: |- - echo "java version [${{ needs.my_job_1.outputs.JAVA_VERSION }}]" - echo "custom_output [${{ steps.my_variables.outputs.custom_output_key }}]" - echo "custom_env [${{ env.custom_env_key }}]" - echo "input_1 [${{ inputs.input_1 }}]" - echo "SECRET_1 [${{ secrets.SECRET_1 }}]" - env: - step_env_1: "My Step Env 1" - step_env_2: "My Step Env 2" - my_job_3: - needs: [ my_job_1, my_job_2, my_job_3 ] - runs-on: ubuntu-latest - env: - job_env_1: "My Job Env 1" - job_env_2: "My Job Env 2" - steps: - - name: "SETUP JAVA" - id: "setup_java" - uses: actions/setup-java@main - with: - INVALID_KEY: some_value - INVALID: ${{ secrets.SECRET_1 }} - java-version: ${{ steps.INVALID.outputs.java_version }} - distribution: 'adopt' - - name: "Set Variables" - id: "my_variables" - run: | - echo "${{ needs.my_job_1.outputs.IS_GRADLE }}" - echo "custom_output_key=custom_output_value" >> $GITHUB_OUTPUT - echo "custom_env_key=custom_env_value" >> $GITHUB_ENV - echo "outcome [${{ steps.my_variables.outcome }}]" - echo "outcome [${{ steps.my_variables.conclusion }}]" - echo "conclusion [${{ steps.setup_java.outcome }}]" - echo "conclusion [${{ steps.setup_java.conclusion }}]" - echo "invalid_key [${{ steps.setup_java.outputs.invalid_key }}]" - - name: - if: secrets.SECRET_1 != '' - env: - step_env_1: "My Step Env 1" - step_env_2: "My Step Env 2" - run: |- - echo "java version [${{ needs.INVALID_JOB.outputs.JAVA_VERSION }} ${{ needs.my_job_1.outputs.INVALID_VAR }} ${{ needs.INCOMPLETE }} ${{ needs.my_job_1.outputs.JAVA_VERSION.TOO_LONG }} ${{ needs.my_job_1.outputs.JAVA_VERSION }}]" - echo "input_1 [${{ inputs.INVALID_INPUT }} ${{ inputs. }} ${{ inputs.input_1TOO_LONG }} ${{ inputs.input_1 }}]" - echo "custom_output [${{ steps.INVALID_STEP.outputs.custom_output_key }}, ${{ steps.my_variables.outputs.INVALID_VAR }} ${{ steps.my_variables.INCOMPLETE }} ${{ steps.my_variables.outputs.custom_output_keyTOO_LONG }}] ${{ steps.setup_java.outputs.cache-hit }} ${{ steps.my_variables.outputs.custom_output_key }}" - echo "custom_env [${{ env.INVALID }} ${{ env.custom_env_key.payload }} ${{ env.custom_env_key }}]" - echo "custom_env [${{ github.INVALID }} ${{ github.repository_owner.payload }} ${{ github.repository_owner }}]" - echo "SECRET_1 [${{ secrets.SECRET_3 }} ${{ secrets.SECRET_1 }}]" - echo "step_env_1 [${{ env.step_env_3 }} ${{ env.step_env_1 }}]" -