From 00b78e6798d9f7db82c8810eab5011be98c3874d Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Wed, 20 May 2026 00:10:04 +0200 Subject: [PATCH 01/17] Harden GitHub workflow editor support --- .github/dependabot.yml | 8 +- .github/workflows/build.yml | 192 +- .github/workflows/release.yml | 113 +- .github/workflows/run-ui-tests.yml | 59 - .github/workflows/show_case.yml | 116 -- .github/workflows/tag.yml | 62 + CHANGELOG.md | 12 + README.md | 45 +- build.gradle | 221 ++ build.gradle.kts | 120 -- ...001-use-gradle-wrapper-for-plugin-build.md | 33 + ...02-configurable-remote-action-providers.md | 35 + ...e-verifier-clean-documentation-provider.md | 36 + ...for-cache-controls-and-resource-bundles.md | 35 + ...nd-remote-discovery-and-testable-reload.md | 34 + ...se-generated-github-docs-data-snapshots.md | 21 + doc/spec/editor-test-matrix.md | 91 + doc/spec/ux-dx-gaps.md | 39 + gradle.properties | 5 +- gradle/wrapper/gradle-wrapper.properties | 3 +- qodana.yml | 2 +- settings.gradle | 1 + settings.gradle.kts | 1 - .../helper/AutoPopupInsertHandler.java | 6 +- .../githubworkflow/helper/FileDownloader.java | 88 +- .../helper/GitHubWorkflowConfig.java | 189 +- .../helper/GitHubWorkflowHelper.java | 122 +- .../helper/HighlightAnnotatorHelper.java | 31 +- .../helper/PsiElementHelper.java | 115 +- .../githubworkflow/logic/Action.java | 53 +- .../yunabraska/githubworkflow/logic/Envs.java | 2 - .../githubworkflow/logic/GitHub.java | 9 + .../githubworkflow/logic/JobContext.java | 162 ++ .../yunabraska/githubworkflow/logic/Jobs.java | 12 +- .../githubworkflow/logic/Matrix.java | 61 + .../githubworkflow/logic/Needs.java | 27 +- .../githubworkflow/logic/Secrets.java | 22 +- .../githubworkflow/logic/Steps.java | 12 +- .../githubworkflow/logic/Strategy.java | 30 + .../githubworkflow/model/GitHubAction.java | 195 +- .../model/LocalActionReferenceResolver.java | 9 +- .../model/VariableReferenceResolver.java | 55 +- .../services/ClearActionCacheAction.java | 30 + .../services/CodeCompletion.java | 346 +++- .../services/ExpressionReferenceTarget.java | 7 + .../services/ExpressionReferenceTargets.java | 314 +++ .../services/GitHubActionCache.java | 177 +- .../services/GitHubWorkflowBundle.java | 20 + .../services/HighlightAnnotator.java | 292 ++- .../services/PluginErrorReportSubmitter.java | 13 +- .../services/ProjectStartup.java | 58 +- .../services/ReferenceContributor.java | 35 +- .../services/RefreshActionCacheAction.java | 36 + .../services/RemoteActionProviders.java | 328 +++ .../services/RemoteActionResolution.java | 14 + .../services/RemoteServerSettings.java | 130 ++ .../services/RestoreActionWarningsAction.java | 35 + .../WorkflowDocumentationProvider.java | 538 +++++ .../services/WorkflowRunLanguageInjector.java | 221 ++ .../services/WorkflowTextAttributes.java | 31 + src/main/resources/META-INF/plugin.xml | 25 + .../resources/github-docs/default-env.tsv | 45 + .../resources/github-docs/github-context.tsv | 40 + .../messages/GitHubWorkflowBundle.properties | 13 + .../GitHubWorkflowBundle_ar.properties | 13 + .../GitHubWorkflowBundle_cs.properties | 13 + .../GitHubWorkflowBundle_de.properties | 13 + .../GitHubWorkflowBundle_es.properties | 13 + .../GitHubWorkflowBundle_fr.properties | 13 + .../GitHubWorkflowBundle_hi.properties | 13 + .../GitHubWorkflowBundle_id.properties | 13 + .../GitHubWorkflowBundle_it.properties | 13 + .../GitHubWorkflowBundle_ja.properties | 13 + .../GitHubWorkflowBundle_ko.properties | 13 + .../GitHubWorkflowBundle_nl.properties | 13 + .../GitHubWorkflowBundle_pl.properties | 13 + .../GitHubWorkflowBundle_pt_BR.properties | 13 + .../GitHubWorkflowBundle_ru.properties | 13 + .../GitHubWorkflowBundle_sv.properties | 13 + .../GitHubWorkflowBundle_th.properties | 13 + .../GitHubWorkflowBundle_tr.properties | 13 + .../GitHubWorkflowBundle_uk.properties | 13 + .../GitHubWorkflowBundle_vi.properties | 13 + .../GitHubWorkflowBundle_zh_CN.properties | 13 + .../resources/messages/MyBundle.properties | 4 - .../helper/FileDownloaderTest.java | 69 + .../helper/GitHubWorkflowConfigTest.java | 158 ++ .../helper/GitHubWorkflowHelperTest.java | 50 + .../model/GitHubActionTest.java | 94 + .../services/DownloadSchemasTest.java | 41 - .../services/EditorFeatureTestCase.java | 218 ++ .../services/FakeRemoteServer.java | 162 ++ .../services/GitHubActionCacheTest.java | 146 +- .../services/HighlightAnnotatorTest.java | 30 - .../services/LocalizationResourcesTest.java | 83 + .../PluginErrorReportSubmitterTest.java | 25 + .../services/RemoteActionProvidersTest.java | 224 +++ .../services/SchemaResourcesTest.java | 38 + .../WorkflowActionRegistrationTest.java | 46 + .../services/WorkflowCompletionTest.java | 1241 ++++++++++++ .../services/WorkflowDocumentationTest.java | 288 +++ .../services/WorkflowGutterActionTest.java | 105 + .../services/WorkflowHighlightingTest.java | 1782 +++++++++++++++++ .../services/WorkflowPerformanceTest.java | 49 + .../services/WorkflowQuickFixTest.java | 274 +++ .../services/WorkflowReferenceTest.java | 737 +++++++ .../WorkflowRunLanguageInjectionTest.java | 53 + .../services/WorkflowShowcaseTest.java | 172 ++ .../services/WorkflowStylingTest.java | 404 ++++ .../resources/testdata/.github/action.yml | 86 - .../resources/testdata/.github/issue_06.yml | 105 - .../resources/testdata/.github/issue_10.yml | 131 -- .../resources/testdata/.github/issue_24.yml | 17 - .../resources/testdata/.github/issue_25.yml | 16 - .../resources/testdata/.github/issue_29.yml | 34 - .../testdata/.github/local_references.yml | 39 - .../testdata/.github/my_action/action.yml | 86 - .../resources/testdata/.github/show_case.yml | 123 -- 118 files changed, 10908 insertions(+), 1714 deletions(-) delete mode 100644 .github/workflows/run-ui-tests.yml delete mode 100644 .github/workflows/show_case.yml create mode 100644 .github/workflows/tag.yml create mode 100644 build.gradle delete mode 100644 build.gradle.kts create mode 100644 doc/adr/0001-use-gradle-wrapper-for-plugin-build.md create mode 100644 doc/adr/0002-configurable-remote-action-providers.md create mode 100644 doc/adr/0003-use-verifier-clean-documentation-provider.md create mode 100644 doc/adr/0004-use-actions-for-cache-controls-and-resource-bundles.md create mode 100644 doc/adr/0005-bound-remote-discovery-and-testable-reload.md create mode 100644 doc/adr/0006-use-generated-github-docs-data-snapshots.md create mode 100644 doc/spec/editor-test-matrix.md create mode 100644 doc/spec/ux-dx-gaps.md create mode 100644 settings.gradle delete mode 100644 settings.gradle.kts create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/logic/Matrix.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/logic/Strategy.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTarget.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTargets.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowBundle.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionProviders.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionResolution.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationProvider.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjector.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowTextAttributes.java create mode 100644 src/main/resources/github-docs/default-env.tsv create mode 100644 src/main/resources/github-docs/github-context.tsv create mode 100644 src/main/resources/messages/GitHubWorkflowBundle.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_ar.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_cs.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_de.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_es.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_fr.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_hi.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_id.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_it.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_ja.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_ko.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_nl.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_pl.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_ru.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_sv.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_th.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_tr.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_uk.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_vi.properties create mode 100644 src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties delete mode 100644 src/main/resources/messages/MyBundle.properties create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfigTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelperTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/DownloadSchemasTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/FakeRemoteServer.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotatorTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitterTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/RemoteActionProvidersTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/SchemaResourcesTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowPerformanceTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowReferenceTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjectionTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowShowcaseTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowStylingTest.java delete mode 100644 src/test/resources/testdata/.github/action.yml delete mode 100644 src/test/resources/testdata/.github/issue_06.yml delete mode 100644 src/test/resources/testdata/.github/issue_10.yml delete mode 100644 src/test/resources/testdata/.github/issue_24.yml delete mode 100644 src/test/resources/testdata/.github/issue_25.yml delete mode 100644 src/test/resources/testdata/.github/issue_29.yml delete mode 100644 src/test/resources/testdata/.github/local_references.yml delete mode 100644 src/test/resources/testdata/.github/my_action/action.yml delete mode 100644 src/test/resources/testdata/.github/show_case.yml 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..783374a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,173 +1,67 @@ -# 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 + 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: + push: + branches: + - main + pull_request: -jobs: +permissions: + contents: read - # 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 +jobs: build: - name: Build + name: Test, Verify, Package runs-on: ubuntu-latest - outputs: - version: ${{ steps.properties.outputs.version }} - changelog: ${{ steps.properties.outputs.changelog }} - steps: + timeout-minutes: 60 - # Free GitHub Actions Environment Disk Space - - name: Maximize Build Space + steps: + - name: Free disk space + shell: sh run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /usr/local/lib/android - sudo rm -rf /opt/ghc + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc - # Check out current repository - - name: Fetch Sources + - name: Fetch sources uses: actions/checkout@v4 - # Validate wrapper - - name: Gradle Wrapper Validation - uses: gradle/wrapper-validation-action@v3.5.0 - - # Setup Java 17 environment for the next steps - - name: Setup Java + - name: Set up Java uses: actions/setup-java@v4 with: - distribution: zulu - java-version: 17 + distribution: temurin + java-version: 25 - # Set environment variables - - name: Export Properties - id: properties - shell: bash - 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)" + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 - 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 + - name: Test and verify plugin + shell: sh + run: ./gradlew --no-daemon check verifyPlugin --warning-mode all - ./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier + - name: Build plugin archive + shell: sh + run: ./gradlew --no-daemon buildPlugin --warning-mode all - # 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 - - # Upload Kover report to CodeCov - - name: Upload Code Coverage Report - uses: codecov/codecov-action@v4 - with: - files: ${{ github.workspace }}/build/reports/kover/xml/report.xml - - # Cache Plugin Verifier IDEs - - name: Setup Plugin Verifier IDEs Cache - uses: actions/cache@v4 + - name: Upload test reports + if: failure() + uses: actions/upload-artifact@v4 with: - path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides - key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} - - # 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 }} - - # Collect Plugin Verifier Result - - name: Collect Plugin Verifier Result - if: ${{ always() }} + name: test-reports + path: | + build/reports/tests + build/reports/jacoco + + - name: Upload verifier reports + id: upload + if: always() uses: actions/upload-artifact@v4 with: - name: pluginVerifier-result - path: ${{ github.workspace }}/build/reports/pluginVerifier - -# # Run Qodana inspections -# - name: Qodana - Code Inspection -# uses: JetBrains/qodana-action@v2022.3.4 - - # Prepare plugin archive content for creating artifact - - name: Prepare Plugin Artifact - id: artifact - shell: bash - run: | - cd ${{ github.workspace }}/build/distributions - FILENAME=`ls *.zip` - unzip "$FILENAME" -d content + name: plugin-verifier-reports + path: build/reports/pluginVerifier - echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT - - # Store already-built plugin as an artifact for downloading - - name: Upload artifact + - name: Upload plugin archive + if: success() 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: - - # Check out current repository - - name: Fetch Sources - uses: actions/checkout@v4 - - # Remove old release drafts by using the curl request for the available releases with a draft flag - - name: Remove Old Release Drafts - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh api repos/{owner}/{repo}/releases \ - --jq '.[] | select(.draft == true) | .id' \ - | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} - - # Create a new release draft which is not publicly visible and requires manual acceptance - - name: Create Release Draft - env: - GITHUB_TOKEN: ${{ secrets.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 - )" + name: plugin-archive + path: build/distributions/*.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e1d109..3f094b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,97 +1,50 @@ -# 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] + inputs: + tag: + description: Existing release tag to publish, for example v2024.3.0 + required: true + type: string -jobs: +permissions: + contents: write - # Prepare and publish the plugin to the Marketplace repository +jobs: release: - name: Publish Plugin + name: Build Release Asset + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: + timeout-minutes: 60 - # Check out current repository - - name: Fetch Sources - uses: actions/checkout@v3 + steps: + - name: Fetch sources + uses: actions/checkout@v4 with: - ref: ${{ github.event.release.tag_name }} + ref: ${{ inputs.tag }} - # Setup Java 11 environment for the next steps - - name: Setup Java - uses: actions/setup-java@v3 + - name: Set up Java + uses: actions/setup-java@v4 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 - )" + distribution: temurin + java-version: 25 - echo "changelog<> $GITHUB_OUTPUT - echo "$CHANGELOG" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 - # 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" + - name: Test and verify plugin + shell: sh + run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all - # 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 != '' }} + - name: Create or update GitHub release asset + shell: sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ inputs.tag }} 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 + if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then + gh release upload "$RELEASE_TAG" ./build/distributions/*.zip --clobber + else + gh release create "$RELEASE_TAG" ./build/distributions/*.zip --title "$RELEASE_TAG" --notes "Plugin release $RELEASE_TAG" + fi 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/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..08ffdab --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,62 @@ +name: Tag + +on: + workflow_dispatch: + inputs: + tag: + description: Release tag to create, for example v2024.3.0 + required: true + type: string + +permissions: + contents: write + +jobs: + tag: + name: Test, Verify, Build, Tag + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Fetch sources + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 25 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Test, verify, and build plugin + shell: sh + run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all + + - name: Create tag + shell: sh + env: + RELEASE_TAG: ${{ inputs.tag }} + run: | + case "$RELEASE_TAG" in + v[0-9]*.[0-9]*.[0-9]*) ;; + *) + echo "Tag must look like v2024.3.0" >&2 + exit 1 + ;; + esac + + 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 + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "$RELEASE_TAG" -m "$RELEASE_TAG" + git push origin "$RELEASE_TAG" diff --git a/CHANGELOG.md b/CHANGELOG.md index ae3dc19..326ca32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ ## [Unreleased] +## [2024.3.0] - 2026-05-19 + +### 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. +- 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 from `Tools > GitHub Workflow` to refresh metadata, clear cached entries, or restore hidden warnings. +- The plugin build, tests, verifier checks, release packaging, and local development setup are ready for the next version. + ## [3.2.1] - 2023-11-04 ### Investigating UI Freeze diff --git a/README.md b/README.md index 606a842..eb51dd1 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ _[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 or clear resolved action/workflow metadata from `Tools > GitHub Workflow`. * 🗺️ 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 +47,26 @@ _[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 `Tools > GitHub Workflow` to refresh stale remote metadata or clear cached action/workflow metadata. * **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. + +For manual IDE testing, run `./gradlew runIde`. The first run downloads IDE artifacts and can take a while. This is +annoying, but at least it is predictable. Progress. + +Current UX/DX gaps are tracked in [UX/DX Gaps](doc/spec/ux-dx-gaps.md); editor behavior coverage is tracked in +[Editor Test Matrix](doc/spec/editor-test-matrix.md). + ## Dependencies This plugin depends on: @@ -73,23 +93,22 @@ Yuna Morgenstern, your GitHub Jedi. #### TODO -- [ ] 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 +- [x] 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] 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). diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..947b678 --- /dev/null +++ b/build.gradle @@ -0,0 +1,221 @@ +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 '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() + } +} + +dependencies { + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.assertj:assertj-core:3.26.3' + + intellijPlatform { + intellijIdeaCommunity(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('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 { + 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') + } + + signing { + certificateChain = providers.environmentVariable('CERTIFICATE_CHAIN') + privateKey = providers.environmentVariable('PRIVATE_KEY') + password = providers.environmentVariable('PRIVATE_KEY_PASSWORD') + } +} + +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/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..3236b7c --- /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.3 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/spec/editor-test-matrix.md b/doc/spec/editor-test-matrix.md new file mode 100644 index 0000000..f1f7d50 --- /dev/null +++ b/doc/spec/editor-test-matrix.md @@ -0,0 +1,91 @@ +# 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@`. +- 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 are registered through `plugin.xml`, localized through resource bundles, and covered by cache summary + and clear behavior tests. +- The default resource bundle and 20 locale resource bundles keep matching key sets with nonblank values. +- 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. +- `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. +- 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. +- 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. +- 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. +- Cache tools can refresh metadata, clear metadata, and restore suppressed action/input/output warnings. +- 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. +- 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. +- GitHub context/default-env data is generated into checked-in resource snapshots by `./gradlew generateGitHubDocsData`. + Tests ensure completion/highlighting metadata uses exactly those snapshots. diff --git a/doc/spec/ux-dx-gaps.md b/doc/spec/ux-dx-gaps.md new file mode 100644 index 0000000..50b83ba --- /dev/null +++ b/doc/spec/ux-dx-gaps.md @@ -0,0 +1,39 @@ +# UX/DX Gaps + +## UX + +- Remote action resolution uses public GitHub plus GitHub Enterprise accounts from JetBrains GitHub settings. There is + no plugin-owned server settings UI. +- Unresolved remote actions now mention common failure modes directly in the editor, including account access, private + repository permissions, rate limits, missing refs, and missing metadata. Per-status diagnostics are still missing. +- Cache controls are exposed under `Tools > GitHub Workflow`, with clear and refresh actions for resolved metadata. A + richer cache inspector is still missing. +- Suppressed action/input/output warnings can be restored from `Tools > GitHub Workflow`. A detailed suppression review + panel is still missing. +- Link navigation now covers resolved local/remote `uses` from github.com and GitHub Enterprise, job IDs, `needs`, + inputs, secrets, envs, matrix keys, + job service IDs/ports, step output references, and reusable workflow job outputs where a local target exists. Remote + metadata internals still need explicit data sources. +- Quick documentation now covers resolved `uses`, workflow/action parameters, expression variables, and common context + segments. The remaining gap is richer rendered Markdown from remote action README files. +- Plugin/action/cache-control labels and notifications use resource bundles with top-20 locale files. Most editor + inspection, quick-fix, and documentation strings still need extraction before localization can be called complete. +- Highlighting, references, styling, documentation, and completion now have focused fixture tests through YAML editor + entrypoints. GitHub context/default-env metadata is generated from checked-in documentation snapshots. + +## DX + +- Schema updates should be a separate manual task, not a test side effect. +- Verifier reports are now generated, but release decisions still need a documented review checklist. +- JaCoCo reporting is present but currently not reliable with the IntelliJ fixture/runtime class loading; coverage gates + stay off until that is made truthful. +- Editor fixture coverage has been split into isolated highlighting, reference, and completion tests. The remaining + matrix is tracked in [Editor Test Matrix](editor-test-matrix.md). +- Bounded large-workflow highlighting performance coverage now runs through `./gradlew performanceTest`. +- A fake HTTP server covers remote metadata behavior deterministically. Remote reload gutter execution still needs a + resolver boundary before it can be exercised without sleeps. +- The release workflow builds, verifies, packages, and uploads the plugin zip to a GitHub release. It still needs one + real dry run before it can be considered boring. +- Tag and GitHub release workflows are main-branch gated. Marketplace publishing/signing is intentionally not wired into + release yet. +- UI testing is not configured. The stale UI workflow was removed instead of pretending it worked. Harsh, but fair. diff --git a/gradle.properties b/gradle.properties index 150abd2..55d66c8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ 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 @@ -13,7 +13,6 @@ 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 # 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 @@ -23,7 +22,7 @@ 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 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..ea6be32 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,8 +18,6 @@ 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.concurrent.Future; @@ -38,9 +34,9 @@ private FileDownloader() { public static String downloadFileFromGitHub(final String downloadUrl) { return GHAccountsUtil.getAccounts().stream() .map(account -> downloadFromGitHub(downloadUrl, account)) - .filter(Objects::nonNull) + .filter(PsiElementHelper::hasText) .findFirst() - .orElse(null); + .orElse(""); } @@ -49,7 +45,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 +54,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 +110,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..322d895 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,13 @@ package com.github.yunabraska.githubworkflow.helper; -import java.util.HashMap; +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 +15,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,147 +27,127 @@ 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_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(); 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<>(); + private static Map getRunnerItems() { + final Map result = new LinkedHashMap<>(); 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\"."); + result.put("tool_cache", "The path to the directory containing preinstalled tools for GitHub-hosted runners."); + result.put("debug", "This is set only if debug logging is enabled, and always has the value of 1."); + result.put("environment", "The environment of the runner executing the job. Possible values are github-hosted or self-hosted."); return result; } - private static HashMap getCaretBracketItems() { - final HashMap result = new HashMap<>(); + private static Map getJobItems() { + final Map result = new LinkedHashMap<>(); + result.put("status", "The current status of the job."); + result.put("check_run_id", "The check run ID of the current job."); + result.put("container", "Information about the job's container."); + result.put("services", "The service containers created for a job."); + result.put("workflow_ref", "The full ref of the workflow file that defines the current job."); + result.put("workflow_sha", "The commit SHA of the workflow file that defines the current job."); + result.put("workflow_repository", "The owner/repo of the repository containing the workflow file that defines the current job."); + result.put("workflow_file_path", "The workflow file path, relative to the repository root."); + return result; + } + + private static Map getStrategyItems() { + final Map result = new LinkedHashMap<>(); + result.put("fail-fast", "Whether all in-progress jobs are canceled if any matrix job fails."); + result.put("job-index", "The zero-based index of the current job in the matrix."); + result.put("job-total", "The total number of jobs in the matrix."); + result.put("max-parallel", "The maximum number of matrix jobs that can run simultaneously."); + return result; + } + + private static Map getCaretBracketItems() { + final Map result = new LinkedHashMap<>(); result.put(FIELD_INPUTS, "Workflow inputs e.g. from workflow_dispatch, workflow_call"); result.put(FIELD_SECRETS, "Workflow secrets"); + result.put(FIELD_JOB, "Information about the currently running job"); result.put(FIELD_JOBS, "Workflow jobs"); + result.put(FIELD_MATRIX, "Matrix properties defined for the current matrix job"); + result.put(FIELD_STRATEGY, "Matrix execution strategy information for the current job"); 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"); + result.put(FIELD_GITEA, "Information about the Gitea Actions workflow run. Gitea keeps many GitHub-compatible context names and also exposes gitea.* in Gitea workflows."); + result.put(FIELD_RUNNER, "Information about the runner that is executing the current job"); 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."); - return result; + private static Map getGitHubContextEnvs() { + return loadGeneratedItems("/github-docs/github-context.tsv"); } - //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"); - return result; + 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..ed6c439 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java @@ -7,6 +7,7 @@ import com.github.yunabraska.githubworkflow.services.GitHubActionCache; 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 +20,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; @@ -75,9 +77,8 @@ public static void ifEnoughItems( 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, @@ -109,9 +110,9 @@ 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 + "]", + "Remove invalid [" + itemId.text() + "]", null, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); @@ -145,7 +146,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", + "Unresolved [" + removeQuotes(element.getValueText()) + "] - check GitHub account access, private repository permissions, rate limits, missing refs, or missing action/workflow metadata", SETTINGS, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, @@ -200,7 +201,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,6 +223,23 @@ 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); 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..4e9b2eb 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/PsiElementHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/PsiElementHelper.java @@ -9,8 +9,11 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.impl.source.tree.LeafPsiElement; 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 +26,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 +39,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 +134,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 +173,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 +181,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 +269,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("Description: " + description)); + getText(psiElement, "type") + .ifPresent(type -> details.add("Type: " + type)); + if (requiredField) { + details.add("Required: " + getText(psiElement, "required").map(Boolean::parseBoolean).orElse(false)); + } + getText(psiElement, "default") + .ifPresent(defaultValue -> details.add("Default: " + defaultValue)); + getText(psiElement, "deprecationMessage") + .ifPresent(message -> details.add("Deprecated: " + message)); + return String.join("\n", details); } public static Optional toPath(final VirtualFile virtualFile) { @@ -227,16 +293,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) { @@ -262,12 +331,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 +341,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 +401,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..2ee372e 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java @@ -24,6 +24,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; @@ -58,7 +60,7 @@ public static void highLightAction(final AnnotationHolder holder, final YAMLKeyV result.add(newReloadAction(action)); } 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)); } @@ -69,21 +71,30 @@ public static void highLightAction(final AnnotationHolder holder, final YAMLKeyV 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) ? "secret" : "input"; + addAnnotation(holder, item, new SyntaxAnnotation( + "Delete invalid " + label + " [" + id + "]", + null, + deleteElementAction(item.getTextRange()) + )); + } + }); } public static List highlightActionOutputs(final YAMLSequenceItem stepItem, final SimpleElement part) { @@ -113,8 +124,8 @@ 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) @@ -122,7 +133,9 @@ private static void highlightLocalActions(final AnnotationHolder holder, final Y .textAttributes(DefaultLanguageHighlighterColors.HIGHLIGHTED_REFERENCE) .tooltip(tooltip) .create(); - result.add(newJumpToFile(action)); + if (action.isLocal()) { + result.add(newJumpToFile(action)); + } }); } } @@ -147,7 +160,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 : EMPTY, HighlightSeverity.INFORMATION, suppressed ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.INFORMATION, f -> { 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..4896742 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java @@ -0,0 +1,162 @@ +package com.github.yunabraska.githubworkflow.logic; + +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 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, "Job container field"), ICON_NODE); + case FIELD_SERVICES -> completionItemsOf(toMap(listServiceIds(position), "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, "Job service field"), 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), "Mapped service port"), 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..d73fbcc 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java @@ -23,6 +23,7 @@ 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; @@ -49,13 +50,18 @@ 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])) { + if (isDefinedItem0(element, holder, jobId, jobIds) && isField2Valid(element, holder, parts[2], List.of("outputs", FIELD_RESULT))) { + if (FIELD_RESULT.equals(parts[2].text())) { + return; + } // TODO: find target for highlighting reference // TODO: implement reference ... does highlighting comes after reference which could add an reference indicator? 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); + } } }); } @@ -86,6 +92,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() @@ -128,7 +138,16 @@ private static void highlightLocalActions(final AnnotationHolder holder, final Y // ########## 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..4b33f34 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java @@ -10,9 +10,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; @@ -33,6 +34,8 @@ public class Secrets { + private static final String GITHUB_TOKEN = "GITHUB_TOKEN"; + public static void highLightSecrets( final AnnotationHolder holder, final PsiElement psiElement, @@ -68,11 +71,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, "Automatically created token for each workflow run."); + 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..39ce6b3 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java @@ -91,13 +91,21 @@ 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)) ); } 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/GitHubAction.java b/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java index 54a5feb..095dd38 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,26 +21,25 @@ 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; @@ -60,6 +60,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 +91,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) { @@ -120,16 +121,22 @@ 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 +160,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 +176,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", ""); } @@ -262,13 +294,35 @@ public Set ignoredOutputs() { return unmodifiableSet(ignoredOutputs); } + /** + * 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.isEmpty() || !ignoredOutputs.isEmpty(); + } + + /** + * 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 +331,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 +349,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(";")).toList()); + this.ignoredOutputs.addAll(Arrays.stream(values.getOrDefault("ignoredOutputs", "").split(";")).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 +394,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 +428,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 +490,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 +524,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 +565,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/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/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..ad2ce8f --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java @@ -0,0 +1,30 @@ +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.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 @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()); + } +} 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..d46084a 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,61 @@ 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.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,6 +77,9 @@ 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()); } @@ -66,9 +93,10 @@ 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); caretBracketItem.ifPresent(cbi -> addCodeCompletionItems(resultSet, cbi, position, prefix)); @@ -76,19 +104,39 @@ public void addCompletions( //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()) { + // 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 (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 (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 = getParentStepOrJob(position) - .flatMap(step -> PsiElementHelper.getChild(step, FIELD_USES)) - .map(GitHubActionCache::getAction) + 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 +147,205 @@ 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 int offset = Math.min(parameters.getOffset(), wholeText.length()); + final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; + final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + return beforeCaret.matches("\\s*-?\\s*" + FIELD_USES + "\\s*:\\s*.*"); + } + + private static Optional remoteUsesRef(final CompletionParameters parameters) { + final String wholeText = parameters.getOriginalFile().getText(); + final int offset = Math.min(parameters.getOffset(), wholeText.length()); + final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; + final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + 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 int offset = Math.min(parameters.getOffset(), wholeText.length()); + final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; + final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + 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, reusableWorkflowUse ? "Local reusable workflow" : "Local action")); + return true; + }); + return result; + } + + 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, "Known workflow reference")); + knownRemoteUsesValues(position).stream() + .map(CodeCompletion::splitRemoteUses) + .flatMap(Optional::stream) + .filter(uses -> usesBase.equals(uses.base())) + .forEach(uses -> result.putIfAbsent(uses.ref(), "Known workflow reference")); + GitHubActionCache.getActionCache().remoteRefsFor(usesBase, 10) + .forEach(ref -> result.putIfAbsent(ref, "Remote workflow reference")); + 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(), "Known remote action or reusable workflow")); + 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 = Math.min(parameters.getOffset(), wholeText.length()); + final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; + final String beforeCaret = wholeText.substring(0, lineStart); + 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,24 +365,84 @@ 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 int offset = Math.min(parameters.getOffset(), wholeText.length()); + final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; + final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + 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), @@ -150,8 +450,10 @@ private static void handleSecondItem(final String[] cbi, final int i, final Map< completionItemOf(FIELD_OUTCOME, "The result of a completed step before continue-on-error is applied.", 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, "The set of outputs defined for the job.", ICON_OUTPUT), + completionItemOf(FIELD_RESULT, "The result of the job.", ICON_OUTPUT) )); + case FIELD_JOB -> completionItemMap.put(i, codeCompletionJob(cbi[1], position)); default -> { // ignored } @@ -164,7 +466,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 +496,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; @@ -202,7 +509,7 @@ 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 wholeText.substring(indexStart, caretOffset).replace("IntellijIdeaRulezzz", "").trim(); } private static void addLookupElements(final CompletionResultSet resultSet, final Map map, final NodeIcon icon, final char suffix) { @@ -219,4 +526,13 @@ 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 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..2d8956f 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java @@ -26,12 +26,17 @@ import java.nio.file.Path; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; 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; @@ -49,6 +54,22 @@ public static class State { } 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); @@ -83,23 +104,114 @@ protected GitHubAction get(final Project project, final String usesValue) { final boolean isLocal = !usesCleaned.contains("@"); final String path = getAbsolutePath(isLocal, usesCleaned, project); return ofNullable(path) - .map(state.actions::get) - .map(action -> System.currentTimeMillis() < action.expiryTime() ? action : saveNewAction(usesCleaned, path, isLocal, action)) + .map(absolutePath -> ofNullable(state.actions.get(absolutePath)).or(() -> ofNullable(state.actions.get(usesCleaned))).orElse(null)) + .map(action -> cachedOrRefresh(usesCleaned, path, isLocal, action)) .orElseGet(() -> saveNewAction(usesCleaned, path, isLocal, null)); } + private GitHubAction cachedOrRefresh(final String usesValue, final String path, final boolean isLocal, final GitHubAction action) { + if (System.currentTimeMillis() < action.expiryTime()) { + return action; + } + if (action.isResolved() && !isLocal) { + queueRefresh(action); + return action; + } + return saveNewAction(usesValue, path, isLocal, action); + } + + private GitHubAction queueRefresh(final GitHubAction action) { + if (action != null && inFlightResolutions.add(action.usesValue())) { + resolveAsync(List.of(action)); + } + return action; + } + public String remove(final String usesValue) { ofNullable(usesValue).ifPresent(state.actions::remove); 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 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; @@ -121,9 +233,17 @@ public void run(@NotNull final ProgressIndicator indicator) { indicator.setIndeterminate(false); actions.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("Resolving " + (action.isAction() ? "action" : "workflow") + " " + action.name()); + jitterBeforeRemoteRequest(action); + actionResolver.get().resolve(action); + if (action.isResolved()) { + action.expiryTime(System.currentTimeMillis() + (CACHE_ONE_DAY * 14)); + } + } finally { + inFlightResolutions.remove(action.usesValue()); + indicator.setFraction(i / totalActions); + } }); triggerSyntaxHighlightingForActiveFiles(); } catch (final Exception e) { @@ -136,24 +256,43 @@ public void run(@NotNull final ProgressIndicator indicator) { }.queue(); } + 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); - } - }) - ) - ) + 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().resolveAsync(actions); } public static GitHubAction reloadActionAsync(final Project project, final String usesValue) { @@ -231,4 +370,6 @@ private static Optional getChildWithUsesValue(final PsiElement psiElemen return ofNullable(psiElement).filter(PsiElement::isValid).flatMap(element -> PsiElementHelper.getChild(element, FIELD_USES)).flatMap(PsiElementHelper::getText); } + public record CacheSummary(long total, long resolved, long remote, long expired, long suppressed) { + } } 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..55dc3e4 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowBundle.java @@ -0,0 +1,20 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.DynamicBundle; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.PropertyKey; + +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) { + return INSTANCE.getMessage(key, params); + } + + private GitHubWorkflowBundle() { + // static bundle + } +} 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..21343d2 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java @@ -8,7 +8,7 @@ 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.editor.DefaultLanguageHighlighterColors; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.impl.source.tree.LeafPsiElement; @@ -16,7 +16,6 @@ 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.List; @@ -29,23 +28,31 @@ 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.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_TEXT_VARIABLE; import static com.intellij.lang.annotation.HighlightSeverity.INFORMATION; import static java.util.Optional.ofNullable; @@ -58,7 +65,11 @@ 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); // HIGHLIGHT ACTION INPUTS highlightActionInput(holder, psiElement); highlightNeeds(holder, psiElement); @@ -90,11 +101,52 @@ private static void highlightRunOutputs(final AnnotationHolder holder, final Psi ).flatMap(Collection::stream).collect(Collectors.groupingBy(SimpleElement::startIndexOffset)).forEach((integer, elements) -> ofNullable(getFirstChild(elements)).ifPresent(lineElement -> holder .newSilentAnnotation(INFORMATION) .range(lineElement.range()) + .textAttributes(WorkflowTextAttributes.DECLARATION) .gutterIconRenderer(new IconRenderer(null, element, ICON_TEXT_VARIABLE)) .create() ))); } + 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 outputsHandler(final AnnotationHolder holder, final PsiElement psiElement) { getParentJob(psiElement).ifPresent(job -> { final List outputs = PsiElementHelper.getChildren(psiElement).stream().toList(); @@ -126,6 +178,22 @@ public static Predicate isElementWithVariables(final YAMLKeyValue pa .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 +201,82 @@ 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(); + 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 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 +295,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 +311,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()); + } + } + + 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++; } - elementStart = -1; - currentElement.setLength(0); - return elementStart; + 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..9a6b7d2 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; @@ -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(); @@ -60,12 +64,11 @@ public boolean submit(final IdeaLoggingEvent @NotNull [] events, 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; + final PluginDescriptor descriptor = getPluginDescriptor(); 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("OS: " + SystemInfo.OS_NAME + " " + SystemInfo.OS_VERSION, UTF_8)); sb.append(URLEncoder.encode("\n\n### Stacktrace\n", UTF_8)); sb.append(URLEncoder.encode("```\n", UTF_8)); 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..474b8d4 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java @@ -0,0 +1,36 @@ +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.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) { + 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()); + } +} 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..d32e217 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionProviders.java @@ -0,0 +1,328 @@ +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) { + 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 (!server.authorizationHeader().isBlank()) { + builder.header("Authorization", server.authorizationHeader()); + } + final HttpResponse response = CLIENT.send(builder.GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + if (response.statusCode() / 100 != 2) { + return Optional.empty(); + } + return Optional.of(JsonParser.parseString(response.body())); + } 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(); + } + } + + 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("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..9647333 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java @@ -0,0 +1,130 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.application.ApplicationManager; +import org.jetbrains.plugins.github.authentication.accounts.GHPersistentAccounts; + +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 { + final GHPersistentAccounts accounts = ApplicationManager.getApplication().getService(GHPersistentAccounts.class); + return Optional.ofNullable(accounts).stream() + .flatMap(service -> service.getAccounts().stream()) + .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(); + } + } + + 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..7fcd5a2 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java @@ -0,0 +1,35 @@ +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.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) { + 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()); + } +} 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..4984110 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationProvider.java @@ -0,0 +1,538 @@ +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); + return ofNullable(details) + .map(text -> new DocPayload( + (secret ? "Secret " : "Input ") + name, + renderParameter(secret ? "Secret" : "Input", name, text, action.githubUrl()), + plainParameter(secret ? "Secret" : "Input", 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("Input", target.segment().text(), target.target()); + case "secret" -> yamlParameterDoc("Secret", target.segment().text(), target.target()); + case "env" -> yamlValueDoc("Environment variable", target.segment().text(), target.target()); + case "matrix" -> yamlValueDoc("Matrix property", target.segment().text(), target.target()); + case "step" -> stepDoc(target.target()); + case "step-output" -> stepOutputDoc(target.segment().text(), target.target()); + case "need" -> simpleDoc("Needed job", target.segment().text(), "Direct job dependency."); + case "need-output" -> outputDoc("Needed job output", target.segment().text(), target.target()); + case "job" -> simpleDoc("Reusable workflow job", target.segment().text(), "Job declared in this reusable workflow."); + case "job-output" -> outputDoc("Reusable workflow job output", target.segment().text(), target.target()); + case "service" -> yamlValueDoc("Service container", target.segment().text(), target.target()); + case "service-port" -> yamlValueDoc("Service port", target.segment().text(), target.target()); + case "container" -> yamlValueDoc("Job container", target.segment().text(), target.target()); + default -> simpleDoc("Workflow symbol", target.segment().text(), "Resolved workflow expression."); + }; + } + + 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() ? "Workflow output" : "Job output"; + } + + 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() ? "Action" : "Reusable workflow").append(""); + if (action.isResolved()) { + html.append(" resolved from ").append(escape(action.usesValue())).append(""); + } else { + html.append(" not resolved yet"); + } + html.append("

"); + appendParagraph(html, action.description()); + appendLink(html, action.githubUrl()); + appendMap(html, "Inputs", action.freshInputs()); + appendMap(html, "Outputs", action.freshOutputs()); + appendMap(html, "Secrets", action.freshSecrets()); + return new DocPayload(title, html.toString(), title + "\n" + action.usesValue()); + } + + private static DocPayload yamlParameterDoc(final String label, final String name, final PsiElement target) { + final String details = target instanceof YAMLKeyValue keyValue + ? PsiElementHelper.getDescription(keyValue, "Input".equals(label)) + : ""; + 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() ? "" : "

Value: " + 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 = "Step " + name; + final StringBuilder html = new StringBuilder("

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

"); + stepItem.flatMap(step -> getChild(step, "name")).flatMap(PsiElementHelper::getText).ifPresent(value -> appendDetail(html, "Name", value)); + stepItem.flatMap(step -> getChild(step, FIELD_USES)).flatMap(PsiElementHelper::getText).ifPresent(value -> appendDetail(html, "Uses", value)); + stepItem.flatMap(step -> getChild(step, FIELD_USES)) + .map(GitHubActionCache::getAction) + .filter(action -> action != null && action.isResolved()) + .ifPresent(action -> { + appendDetail(html, action.isAction() ? "Action" : "Reusable workflow", action.displayName()); + appendParagraph(html, action.description()); + }); + stepItem.flatMap(step -> getChild(step, "run")).flatMap(PsiElementHelper::getText).ifPresent(value -> appendDetail(html, "Run", value)); + stepItem.ifPresent(step -> appendList(html, "Outputs", 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, "Description", value)); + source.ifPresent(value -> { + appendLine(details, "Step", value.stepLabel()); + appendLine(details, "Uses", value.usesValue()); + appendLine(details, "Source", value.sourceLabel()); + }); + final String plainDetails = details.toString(); + final StringBuilder html = new StringBuilder(renderParameter("Step output", outputName, plainDetails, "")); + source.flatMap(StepOutputSource::url).ifPresent(url -> appendLink(html, url, source.map(StepOutputSource::usesValue).orElse(url))); + return new DocPayload( + "Step output " + outputName, + html.toString(), + plainParameter("Step output", 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, "Description", getText(output, "description").orElse("")); + appendLine(details, "Value", getText(output, "value").or(() -> getText(output)).orElse("")); + outputSourceDetails(output).ifPresent(source -> appendLine(details, "Source", 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("github context", "github", "Information about the current workflow run and event.")); + case "gitea" -> Optional.of(simpleDoc("gitea context", "gitea", "Gitea-compatible alias for the GitHub Actions context.")); + case "inputs" -> Optional.of(simpleDoc("inputs context", "inputs", "Workflow, dispatch, or action inputs available here.")); + case "secrets" -> Optional.of(simpleDoc("secrets context", "secrets", "Secret values available to this workflow or reusable workflow call.")); + case "env" -> Optional.of(simpleDoc("env context", "env", "Environment variables visible at this location.")); + case "matrix" -> Optional.of(simpleDoc("matrix context", "matrix", "Matrix values for the current job.")); + case "steps" -> Optional.of(simpleDoc("steps context", "steps", "Previous steps in the current job, including outputs and status.")); + case "needs" -> Optional.of(simpleDoc("needs context", "needs", "Direct job dependencies and their outputs/results.")); + case "jobs" -> Optional.of(simpleDoc("jobs context", "jobs", "Reusable workflow jobs and outputs.")); + case "outputs" -> Optional.of(simpleDoc("outputs", "outputs", "Output values exposed by this step or job.")); + case "result" -> Optional.of(simpleDoc("result", "result", "Job result: success, failure, cancelled, or skipped.")); + case "outcome" -> Optional.of(simpleDoc("outcome", "outcome", "Step result before continue-on-error is applied.")); + case "conclusion" -> Optional.of(simpleDoc("conclusion", "conclusion", "Step result after continue-on-error is applied.")); + default -> Optional.empty(); + }; + } + + 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() ? "External action" : "Reusable workflow"; + 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/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/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..d2d8962 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,19 @@ + + + @@ -30,16 +38,33 @@ + + + + + + + + + + + + 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..eb3599b --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle.properties @@ -0,0 +1,13 @@ +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. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ar.properties b/src/main/resources/messages/GitHubWorkflowBundle_ar.properties new file mode 100644 index 0000000..90f89af --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_ar.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=دعم ملفات سير عمل GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=أدوات إضافة GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=تحديث ذاكرة الإجراءات +action.GitHubWorkflow.RefreshActionCache.description=تحديث بيانات GitHub Actions البعيدة وسير العمل القابلة لإعادة الاستخدام التي تم حلها +action.GitHubWorkflow.RestoreActionWarnings.text=استعادة تحذيرات الإجراءات +action.GitHubWorkflow.RestoreActionWarnings.description=استعادة تحذيرات التحقق المخفية للإجراءات والمدخلات والمخرجات +action.GitHubWorkflow.ClearActionCache.text=مسح ذاكرة الإجراءات +action.GitHubWorkflow.ClearActionCache.description=مسح بيانات GitHub Actions وسير العمل القابلة لإعادة الاستخدام من الذاكرة المؤقتة +notification.cache.cleared=تم مسح {0} إدخالات من ذاكرة GitHub Workflow. +notification.cache.refresh.started=يتم تحديث {0} إدخالات بعيدة من ذاكرة GitHub Workflow. +notification.warnings.restored=تمت استعادة التحذيرات لـ {0} إدخالات GitHub Workflow. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_cs.properties b/src/main/resources/messages/GitHubWorkflowBundle_cs.properties new file mode 100644 index 0000000..9f8835a --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_cs.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Podpora souborů workflow GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Nástroje pluginu GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Obnovit cache akcí +action.GitHubWorkflow.RefreshActionCache.description=Obnoví metadata vzdálených GitHub Actions a znovupoužitelných workflow +action.GitHubWorkflow.RestoreActionWarnings.text=Obnovit varování akcí +action.GitHubWorkflow.RestoreActionWarnings.description=Obnoví potlačená validační varování akcí, vstupů a výstupů +action.GitHubWorkflow.ClearActionCache.text=Vymazat cache akcí +action.GitHubWorkflow.ClearActionCache.description=Vymaže uložená metadata GitHub Actions a znovupoužitelných workflow +notification.cache.cleared=Vymazáno {0} položek cache GitHub Workflow. +notification.cache.refresh.started=Obnovuje se {0} vzdálených položek cache GitHub Workflow. +notification.warnings.restored=Varování obnovena pro {0} položek GitHub Workflow. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_de.properties b/src/main/resources/messages/GitHubWorkflowBundle_de.properties new file mode 100644 index 0000000..95dfa9f --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_de.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Unterstützung für GitHub-Actions-Workflow-Dateien +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Werkzeuge des GitHub-Workflow-Plugins +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=Action-Warnungen wiederherstellen +action.GitHubWorkflow.RestoreActionWarnings.description=Unterdrückte Validierungswarnungen für Actions, Eingaben und Ausgaben wiederherstellen +action.GitHubWorkflow.ClearActionCache.text=Action-Cache leeren +action.GitHubWorkflow.ClearActionCache.description=Zwischengespeicherte Metadaten von GitHub Actions und wiederverwendbaren Workflows löschen +notification.cache.cleared={0} zwischengespeicherte GitHub-Workflow-Einträge gelöscht. +notification.cache.refresh.started={0} zwischengespeicherte entfernte GitHub-Workflow-Einträge werden aktualisiert. +notification.warnings.restored=Warnungen für {0} GitHub-Workflow-Einträge wiederhergestellt. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_es.properties b/src/main/resources/messages/GitHubWorkflowBundle_es.properties new file mode 100644 index 0000000..90dd9a2 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_es.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Compatibilidad con archivos de flujos de trabajo de GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Herramientas del complemento GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Actualizar caché de acciones +action.GitHubWorkflow.RefreshActionCache.description=Actualiza los metadatos resueltos de acciones remotas y flujos reutilizables de GitHub +action.GitHubWorkflow.RestoreActionWarnings.text=Restaurar advertencias de acciones +action.GitHubWorkflow.RestoreActionWarnings.description=Restaura advertencias de validación ocultas de acciones, entradas y salidas +action.GitHubWorkflow.ClearActionCache.text=Vaciar caché de acciones +action.GitHubWorkflow.ClearActionCache.description=Borra los metadatos almacenados de acciones y flujos reutilizables de GitHub +notification.cache.cleared=Se borraron {0} entradas en caché de GitHub Workflow. +notification.cache.refresh.started=Actualizando {0} entradas remotas en caché de GitHub Workflow. +notification.warnings.restored=Advertencias restauradas para {0} entradas de GitHub Workflow. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_fr.properties b/src/main/resources/messages/GitHubWorkflowBundle_fr.properties new file mode 100644 index 0000000..f5bdf45 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_fr.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Prise en charge des fichiers de workflow GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Outils du plugin GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Actualiser le cache des actions +action.GitHubWorkflow.RefreshActionCache.description=Actualise les métadonnées résolues des actions distantes et workflows réutilisables GitHub +action.GitHubWorkflow.RestoreActionWarnings.text=Restaurer les avertissements d'actions +action.GitHubWorkflow.RestoreActionWarnings.description=Restaure les avertissements de validation masqués des actions, entrées et sorties +action.GitHubWorkflow.ClearActionCache.text=Vider le cache des actions +action.GitHubWorkflow.ClearActionCache.description=Supprime les métadonnées mises en cache des actions et workflows réutilisables GitHub +notification.cache.cleared={0} entrées GitHub Workflow en cache supprimées. +notification.cache.refresh.started=Actualisation de {0} entrées distantes GitHub Workflow en cache. +notification.warnings.restored=Avertissements restaurés pour {0} entrées GitHub Workflow. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_hi.properties b/src/main/resources/messages/GitHubWorkflowBundle_hi.properties new file mode 100644 index 0000000..31e72d6 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_hi.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=GitHub Actions workflow फ़ाइलों के लिए समर्थन +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=GitHub Workflow प्लगइन टूल +action.GitHubWorkflow.RefreshActionCache.text=Action cache रीफ़्रेश करें +action.GitHubWorkflow.RefreshActionCache.description=हल किए गए remote GitHub Actions और reusable workflow metadata को रीफ़्रेश करता है +action.GitHubWorkflow.RestoreActionWarnings.text=Action warnings वापस लाएँ +action.GitHubWorkflow.RestoreActionWarnings.description=छिपाए गए action, input और output validation warnings वापस लाता है +action.GitHubWorkflow.ClearActionCache.text=Action cache साफ़ करें +action.GitHubWorkflow.ClearActionCache.description=कैश किए गए GitHub Actions और reusable workflow metadata को साफ़ करता है +notification.cache.cleared={0} GitHub Workflow cache entries साफ़ किए गए। +notification.cache.refresh.started={0} remote GitHub Workflow cache entries रीफ़्रेश हो रहे हैं। +notification.warnings.restored={0} GitHub Workflow entries के warnings वापस लाए गए। diff --git a/src/main/resources/messages/GitHubWorkflowBundle_id.properties b/src/main/resources/messages/GitHubWorkflowBundle_id.properties new file mode 100644 index 0000000..ca4285f --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_id.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Dukungan untuk file workflow GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Alat plugin GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Segarkan cache action +action.GitHubWorkflow.RefreshActionCache.description=Menyegarkan metadata GitHub Actions jarak jauh dan workflow pakai ulang yang telah diselesaikan +action.GitHubWorkflow.RestoreActionWarnings.text=Pulihkan peringatan action +action.GitHubWorkflow.RestoreActionWarnings.description=Memulihkan peringatan validasi action, input, dan output yang disembunyikan +action.GitHubWorkflow.ClearActionCache.text=Hapus cache action +action.GitHubWorkflow.ClearActionCache.description=Menghapus metadata GitHub Actions dan workflow pakai ulang dari cache +notification.cache.cleared={0} entri cache GitHub Workflow dihapus. +notification.cache.refresh.started=Menyegarkan {0} entri cache GitHub Workflow jarak jauh. +notification.warnings.restored=Peringatan dipulihkan untuk {0} entri GitHub Workflow. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_it.properties b/src/main/resources/messages/GitHubWorkflowBundle_it.properties new file mode 100644 index 0000000..e7daa1a --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_it.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Supporto per i file workflow di GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Strumenti del plugin GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Aggiorna cache azioni +action.GitHubWorkflow.RefreshActionCache.description=Aggiorna i metadati risolti di GitHub Actions remote e workflow riutilizzabili +action.GitHubWorkflow.RestoreActionWarnings.text=Ripristina avvisi azioni +action.GitHubWorkflow.RestoreActionWarnings.description=Ripristina gli avvisi di validazione nascosti per azioni, input e output +action.GitHubWorkflow.ClearActionCache.text=Svuota cache azioni +action.GitHubWorkflow.ClearActionCache.description=Svuota i metadati in cache di GitHub Actions e workflow riutilizzabili +notification.cache.cleared=Rimosse {0} voci dalla cache di GitHub Workflow. +notification.cache.refresh.started=Aggiornamento di {0} voci remote nella cache di GitHub Workflow. +notification.warnings.restored=Avvisi ripristinati per {0} voci GitHub Workflow. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ja.properties b/src/main/resources/messages/GitHubWorkflowBundle_ja.properties new file mode 100644 index 0000000..4c5ee22 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_ja.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=GitHub Actions ワークフローファイルをサポートします +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=GitHub Workflow プラグインツール +action.GitHubWorkflow.RefreshActionCache.text=アクションキャッシュを更新 +action.GitHubWorkflow.RefreshActionCache.description=解決済みのリモート GitHub Actions と再利用可能ワークフローのメタデータを更新します +action.GitHubWorkflow.RestoreActionWarnings.text=アクション警告を復元 +action.GitHubWorkflow.RestoreActionWarnings.description=非表示にしたアクション、入力、出力の検証警告を復元します +action.GitHubWorkflow.ClearActionCache.text=アクションキャッシュを消去 +action.GitHubWorkflow.ClearActionCache.description=キャッシュされた GitHub Actions と再利用可能ワークフローのメタデータを消去します +notification.cache.cleared={0} 件の GitHub Workflow キャッシュエントリを消去しました。 +notification.cache.refresh.started={0} 件の GitHub Workflow リモートキャッシュエントリを更新しています。 +notification.warnings.restored={0} 件の GitHub Workflow エントリの警告を復元しました。 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ko.properties b/src/main/resources/messages/GitHubWorkflowBundle_ko.properties new file mode 100644 index 0000000..2b720ac --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_ko.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=GitHub Actions 워크플로 파일 지원 +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=GitHub Workflow 플러그인 도구 +action.GitHubWorkflow.RefreshActionCache.text=액션 캐시 새로 고침 +action.GitHubWorkflow.RefreshActionCache.description=해결된 원격 GitHub Actions 및 재사용 가능한 워크플로 메타데이터를 새로 고칩니다 +action.GitHubWorkflow.RestoreActionWarnings.text=액션 경고 복원 +action.GitHubWorkflow.RestoreActionWarnings.description=숨긴 액션, 입력, 출력 검증 경고를 복원합니다 +action.GitHubWorkflow.ClearActionCache.text=액션 캐시 지우기 +action.GitHubWorkflow.ClearActionCache.description=캐시된 GitHub Actions 및 재사용 가능한 워크플로 메타데이터를 지웁니다 +notification.cache.cleared=GitHub Workflow 캐시 항목 {0}개를 지웠습니다. +notification.cache.refresh.started=GitHub Workflow 원격 캐시 항목 {0}개를 새로 고치는 중입니다. +notification.warnings.restored=GitHub Workflow 항목 {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..5928dcd --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_nl.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Ondersteuning voor GitHub Actions-workflowbestanden +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Hulpmiddelen van de GitHub Workflow-plugin +action.GitHubWorkflow.RefreshActionCache.text=Actiecache vernieuwen +action.GitHubWorkflow.RefreshActionCache.description=Vernieuwt opgeloste metadata van externe GitHub Actions en herbruikbare workflows +action.GitHubWorkflow.RestoreActionWarnings.text=Actiewaarschuwingen herstellen +action.GitHubWorkflow.RestoreActionWarnings.description=Herstelt onderdrukte validatiewaarschuwingen voor acties, invoer en uitvoer +action.GitHubWorkflow.ClearActionCache.text=Actiecache wissen +action.GitHubWorkflow.ClearActionCache.description=Wist gecachte metadata van GitHub Actions en herbruikbare workflows +notification.cache.cleared={0} gecachte GitHub Workflow-items gewist. +notification.cache.refresh.started={0} externe gecachte GitHub Workflow-items worden vernieuwd. +notification.warnings.restored=Waarschuwingen hersteld voor {0} GitHub Workflow-items. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_pl.properties b/src/main/resources/messages/GitHubWorkflowBundle_pl.properties new file mode 100644 index 0000000..3a2cf81 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_pl.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Obsługa plików workflow GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Narzędzia wtyczki GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Odśwież pamięć podręczną akcji +action.GitHubWorkflow.RefreshActionCache.description=Odświeża metadane zdalnych GitHub Actions i workflow wielokrotnego użytku +action.GitHubWorkflow.RestoreActionWarnings.text=Przywróć ostrzeżenia akcji +action.GitHubWorkflow.RestoreActionWarnings.description=Przywraca ukryte ostrzeżenia walidacji akcji, wejść i wyjść +action.GitHubWorkflow.ClearActionCache.text=Wyczyść pamięć podręczną akcji +action.GitHubWorkflow.ClearActionCache.description=Czyści zapisane metadane GitHub Actions i workflow wielokrotnego użytku +notification.cache.cleared=Wyczyszczono {0} wpisów pamięci podręcznej GitHub Workflow. +notification.cache.refresh.started=Odświeżanie {0} zdalnych wpisów pamięci podręcznej GitHub Workflow. +notification.warnings.restored=Przywrócono ostrzeżenia dla {0} wpisów GitHub Workflow. 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..d023a43 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Suporte a arquivos de workflow do GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Ferramentas do plugin GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Atualizar cache de ações +action.GitHubWorkflow.RefreshActionCache.description=Atualiza metadados resolvidos de ações remotas e workflows reutilizáveis do GitHub +action.GitHubWorkflow.RestoreActionWarnings.text=Restaurar avisos de ações +action.GitHubWorkflow.RestoreActionWarnings.description=Restaura avisos de validação ocultos de ações, entradas e saídas +action.GitHubWorkflow.ClearActionCache.text=Limpar cache de ações +action.GitHubWorkflow.ClearActionCache.description=Limpa metadados em cache de ações e workflows reutilizáveis do GitHub +notification.cache.cleared={0} entradas em cache do GitHub Workflow foram limpas. +notification.cache.refresh.started=Atualizando {0} entradas remotas em cache do GitHub Workflow. +notification.warnings.restored=Avisos restaurados para {0} entradas do GitHub Workflow. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ru.properties b/src/main/resources/messages/GitHubWorkflowBundle_ru.properties new file mode 100644 index 0000000..342c501 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_ru.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Поддержка файлов рабочих процессов GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Инструменты плагина GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Обновить кэш действий +action.GitHubWorkflow.RefreshActionCache.description=Обновляет метаданные найденных удалённых GitHub Actions и переиспользуемых workflows +action.GitHubWorkflow.RestoreActionWarnings.text=Восстановить предупреждения действий +action.GitHubWorkflow.RestoreActionWarnings.description=Восстанавливает скрытые предупреждения проверки действий, входов и выходов +action.GitHubWorkflow.ClearActionCache.text=Очистить кэш действий +action.GitHubWorkflow.ClearActionCache.description=Очищает кэшированные метаданные GitHub Actions и переиспользуемых workflows +notification.cache.cleared=Очищено записей кэша GitHub Workflow: {0}. +notification.cache.refresh.started=Обновляются удалённые записи кэша GitHub Workflow: {0}. +notification.warnings.restored=Предупреждения восстановлены для записей GitHub Workflow: {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..72a4fc6 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_sv.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Stöd för GitHub Actions-workflowfiler +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Verktyg för GitHub Workflow-pluginen +action.GitHubWorkflow.RefreshActionCache.text=Uppdatera åtgärdscache +action.GitHubWorkflow.RefreshActionCache.description=Uppdaterar lösta metadata för fjärrbaserade GitHub Actions och återanvändbara workflows +action.GitHubWorkflow.RestoreActionWarnings.text=Återställ åtgärdsvarningar +action.GitHubWorkflow.RestoreActionWarnings.description=Återställer dolda valideringsvarningar för åtgärder, indata och utdata +action.GitHubWorkflow.ClearActionCache.text=Rensa åtgärdscache +action.GitHubWorkflow.ClearActionCache.description=Rensar cachade metadata för GitHub Actions och återanvändbara workflows +notification.cache.cleared=Rensade {0} cachade GitHub Workflow-poster. +notification.cache.refresh.started=Uppdaterar {0} cachade fjärrposter för GitHub Workflow. +notification.warnings.restored=Varningar återställda för {0} GitHub Workflow-poster. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_th.properties b/src/main/resources/messages/GitHubWorkflowBundle_th.properties new file mode 100644 index 0000000..468e1e9 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_th.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=รองรับไฟล์ workflow ของ GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=เครื่องมือปลั๊กอิน GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=รีเฟรชแคช action +action.GitHubWorkflow.RefreshActionCache.description=รีเฟรชเมตาดาตาของ GitHub Actions ระยะไกลและ workflow ที่ใช้ซ้ำได้ซึ่งแก้ไขแล้ว +action.GitHubWorkflow.RestoreActionWarnings.text=คืนค่าคำเตือน action +action.GitHubWorkflow.RestoreActionWarnings.description=คืนค่าคำเตือนการตรวจสอบ action, input และ output ที่ถูกซ่อนไว้ +action.GitHubWorkflow.ClearActionCache.text=ล้างแคช action +action.GitHubWorkflow.ClearActionCache.description=ล้างเมตาดาตาที่แคชไว้ของ GitHub Actions และ workflow ที่ใช้ซ้ำได้ +notification.cache.cleared=ล้างรายการแคช GitHub Workflow แล้ว {0} รายการ +notification.cache.refresh.started=กำลังรีเฟรชรายการแคช GitHub Workflow ระยะไกล {0} รายการ +notification.warnings.restored=คืนค่าคำเตือนสำหรับรายการ GitHub Workflow แล้ว {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..50d7025 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_tr.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=GitHub Actions iş akışı dosyaları için destek +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=GitHub Workflow eklenti araçları +action.GitHubWorkflow.RefreshActionCache.text=Action önbelleğini yenile +action.GitHubWorkflow.RefreshActionCache.description=Çözümlenmiş uzak GitHub Actions ve yeniden kullanılabilir workflow meta verilerini yeniler +action.GitHubWorkflow.RestoreActionWarnings.text=Action uyarılarını geri yükle +action.GitHubWorkflow.RestoreActionWarnings.description=Gizlenen action, input ve output doğrulama uyarılarını geri yükler +action.GitHubWorkflow.ClearActionCache.text=Action önbelleğini temizle +action.GitHubWorkflow.ClearActionCache.description=Önbelleğe alınmış GitHub Actions ve yeniden kullanılabilir workflow meta verilerini temizler +notification.cache.cleared={0} GitHub Workflow önbellek kaydı temizlendi. +notification.cache.refresh.started={0} uzak GitHub Workflow önbellek kaydı yenileniyor. +notification.warnings.restored={0} GitHub Workflow kaydı için uyarılar geri yüklendi. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_uk.properties b/src/main/resources/messages/GitHubWorkflowBundle_uk.properties new file mode 100644 index 0000000..8bb8f39 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_uk.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Підтримка файлів робочих процесів GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Інструменти плагіна GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Оновити кеш дій +action.GitHubWorkflow.RefreshActionCache.description=Оновлює метадані знайдених віддалених GitHub Actions і повторно використовуваних workflows +action.GitHubWorkflow.RestoreActionWarnings.text=Відновити попередження дій +action.GitHubWorkflow.RestoreActionWarnings.description=Відновлює приховані попередження перевірки дій, входів і виходів +action.GitHubWorkflow.ClearActionCache.text=Очистити кеш дій +action.GitHubWorkflow.ClearActionCache.description=Очищає кешовані метадані GitHub Actions і повторно використовуваних workflows +notification.cache.cleared=Очищено {0} кешованих записів GitHub Workflow. +notification.cache.refresh.started=Оновлюється {0} віддалених кешованих записів GitHub Workflow. +notification.warnings.restored=Попередження відновлено для {0} записів GitHub Workflow. diff --git a/src/main/resources/messages/GitHubWorkflowBundle_vi.properties b/src/main/resources/messages/GitHubWorkflowBundle_vi.properties new file mode 100644 index 0000000..2377b05 --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_vi.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=Hỗ trợ tệp workflow của GitHub Actions +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=Công cụ của plugin GitHub Workflow +action.GitHubWorkflow.RefreshActionCache.text=Làm mới cache action +action.GitHubWorkflow.RefreshActionCache.description=Làm mới metadata đã phân giải của GitHub Actions từ xa và workflow tái sử dụng +action.GitHubWorkflow.RestoreActionWarnings.text=Khôi phục cảnh báo action +action.GitHubWorkflow.RestoreActionWarnings.description=Khôi phục cảnh báo xác thực action, input và output đã bị ẩn +action.GitHubWorkflow.ClearActionCache.text=Xóa cache action +action.GitHubWorkflow.ClearActionCache.description=Xóa metadata đã cache của GitHub Actions và workflow tái sử dụng +notification.cache.cleared=Đã xóa {0} mục cache GitHub Workflow. +notification.cache.refresh.started=Đang làm mới {0} mục cache GitHub Workflow từ xa. +notification.warnings.restored=Đã khôi phục cảnh báo cho {0} mục GitHub Workflow. 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..ab5fa6c --- /dev/null +++ b/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties @@ -0,0 +1,13 @@ +plugin.name=GitHub Workflow +plugin.description=支持 GitHub Actions 工作流文件 +group.GitHubWorkflow.Tools.text=GitHub Workflow +group.GitHubWorkflow.Tools.description=GitHub Workflow 插件工具 +action.GitHubWorkflow.RefreshActionCache.text=刷新 Action 缓存 +action.GitHubWorkflow.RefreshActionCache.description=刷新已解析的远程 GitHub Actions 和可复用工作流元数据 +action.GitHubWorkflow.RestoreActionWarnings.text=恢复 Action 警告 +action.GitHubWorkflow.RestoreActionWarnings.description=恢复已隐藏的 Action、输入和输出校验警告 +action.GitHubWorkflow.ClearActionCache.text=清除 Action 缓存 +action.GitHubWorkflow.ClearActionCache.description=清除已缓存的 GitHub Actions 和可复用工作流元数据 +notification.cache.cleared=已清除 {0} 个 GitHub Workflow 缓存条目。 +notification.cache.refresh.started=正在刷新 {0} 个 GitHub Workflow 远程缓存条目。 +notification.warnings.restored=已恢复 {0} 个 GitHub Workflow 条目的警告。 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..40e0d31 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java @@ -0,0 +1,69 @@ +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 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..5b1df72 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java @@ -0,0 +1,94 @@ +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"); + 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 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..a76d2e9 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java @@ -0,0 +1,218 @@ +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.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(); + if (action == null) { + throw new AssertionError("Gutter action has no click action [" + text + "]"); + } + action.actionPerformed(AnActionEvent.createEvent( + action, + DataContext.EMPTY_CONTEXT, + new Presentation(), + "GithubWorkflowPluginTest", + ActionUiKind.NONE, + 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(); + } + + private 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..51a3058 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,148 @@ 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 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 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(); + } + + 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/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..504a2c1 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java @@ -0,0 +1,83 @@ +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.Properties; + +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()); + } + } + + 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..2729f76 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java @@ -0,0 +1,46 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +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"); + } +} 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..d153d22 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java @@ -0,0 +1,1241 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.model.GitHubAction; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import static com.github.yunabraska.githubworkflow.services.GitHubActionCache.getActionCache; +import static org.assertj.core.api.Assertions.assertThat; + +public class WorkflowCompletionTest extends EditorFeatureTestCase { + + 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"); + } + + 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 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 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"); + } +} 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..f440f76 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java @@ -0,0 +1,105 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.github.yunabraska.githubworkflow.model.GitHubAction; +import com.intellij.psi.PsiFile; + +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.services.GitHubActionCache.getActionCache; + +public class WorkflowGutterActionTest extends EditorFeatureTestCase { + + public void testSuppressActionGutterActionTogglesResolvedAction() { + 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 + """); + + clickGutterActionContaining("Toggle warnings [off]"); + + assertThat(action.isSuppressed()).isTrue(); + } + + public void testSuppressInputGutterActionTogglesResolvedInput() { + 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 + """); + + clickGutterActionContaining("known-input"); + + assertThat(action.ignoredInputs()).contains("known-input"); + } + + public void testJumpToFileGutterActionOpensLocalActionFile() { + 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 + """); + + clickGutterActionContaining("Jump to file"); + } + + public void testReloadRemoteActionGutterActionUsesResolverBoundaryWithoutSleeping() 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 + """); + + clickGutterActionContaining("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); + } + } +} 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..7074074 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java @@ -0,0 +1,1782 @@ +package com.github.yunabraska.githubworkflow.services; + +import java.util.Map; + +public class WorkflowHighlightingTest extends EditorFeatureTestCase { + + 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 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 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..978abbb --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java @@ -0,0 +1,274 @@ +package com.github.yunabraska.githubworkflow.services; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +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 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 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 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."); + } + + 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]"); + } +} 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/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/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 }}]" - From 686ea608dd0a4f2757fc1f86e08cf81f19b8ed1c Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Wed, 20 May 2026 08:07:12 +0200 Subject: [PATCH 02/17] Automate tag and GitHub release flow --- .github/workflows/release.yml | 61 ++++++++++++++++++++++++----- .github/workflows/tag.yml | 73 ++++++++++++++++++++++++++++------- README.md | 13 +++---- 3 files changed, 115 insertions(+), 32 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3f094b4..8ec345e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,50 +1,91 @@ name: Release on: + push: + tags: + - v* workflow_dispatch: inputs: tag: - description: Existing release tag to publish, for example v2024.3.0 + description: Existing tag to publish as a GitHub release. required: true type: string + dry_run: + description: Build and validate without creating or updating a GitHub release. + required: true + default: false + type: boolean permissions: contents: write +concurrency: + group: release-${{ github.ref_name || inputs.tag }} + cancel-in-progress: false + jobs: release: - name: Build Release Asset - if: github.ref == 'refs/heads/main' + name: Build GitHub Release runs-on: ubuntu-latest timeout-minutes: 60 + env: + RELEASE_TAG: ${{ inputs.tag || github.ref_name }} + DRY_RUN: ${{ inputs.dry_run || 'false' }} steps: + - name: Validate tag + shell: sh + run: | + if ! printf '%s\n' "$RELEASE_TAG" | grep -Eq '^v[0-9]+[.][0-9]+[.][0-9]+([-+][0-9A-Za-z._-]+)?$'; then + echo "Tag must look like v2024.3.0 or v2024.3.0-rc.1: $RELEASE_TAG" >&2 + exit 1 + fi + - name: Fetch sources uses: actions/checkout@v4 with: - ref: ${{ inputs.tag }} + ref: ${{ env.RELEASE_TAG }} - name: Set up Java uses: actions/setup-java@v4 with: distribution: temurin java-version: 25 - + - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 - - name: Test and verify plugin + - name: Test, verify, and build plugin shell: sh run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all - - name: Create or update GitHub release asset + - name: Extract release notes + shell: sh + 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 + printf 'Plugin release %s\n' "$RELEASE_TAG" > release-notes.md + fi + + - name: Create or update GitHub release shell: sh env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_TAG: ${{ inputs.tag }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} run: | + if [ "$DRY_RUN" = "true" ]; then + echo "Dry run: would create or update GitHub release $RELEASE_TAG" + exit 0 + fi + if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then gh release upload "$RELEASE_TAG" ./build/distributions/*.zip --clobber + gh release edit "$RELEASE_TAG" --title "$RELEASE_TAG" --notes-file release-notes.md else - gh release create "$RELEASE_TAG" ./build/distributions/*.zip --title "$RELEASE_TAG" --notes "Plugin release $RELEASE_TAG" + gh release create "$RELEASE_TAG" ./build/distributions/*.zip --title "$RELEASE_TAG" --notes-file release-notes.md fi diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 08ffdab..e572d92 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -1,20 +1,35 @@ name: Tag on: + pull_request: + types: + - closed + branches: + - main workflow_dispatch: inputs: tag: - description: Release tag to create, for example v2024.3.0 - required: true + description: Release tag to create. Defaults to v. + required: false type: string + dry_run: + description: Build and validate without pushing a tag. + required: true + default: true + type: boolean permissions: + actions: write contents: write +concurrency: + group: tag-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: false + jobs: tag: name: Test, Verify, Build, Tag - if: github.ref == 'refs/heads/main' + if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true runs-on: ubuntu-latest timeout-minutes: 60 @@ -23,6 +38,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + token: ${{ secrets.RELEASE_TOKEN || github.token }} - name: Set up Java uses: actions/setup-java@v4 @@ -33,30 +49,59 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 + - name: Resolve release tag + id: tag + shell: sh + env: + INPUT_TAG: ${{ inputs.tag }} + run: | + if [ -n "$INPUT_TAG" ]; then + release_tag="$INPUT_TAG" + else + version="$(awk -F= '/^[[:space:]]*pluginVersion[[:space:]]*=/{ gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2 }' gradle.properties)" + release_tag="v$version" + fi + + if ! printf '%s\n' "$release_tag" | grep -Eq '^v[0-9]+[.][0-9]+[.][0-9]+([-+][0-9A-Za-z._-]+)?$'; then + echo "Tag must look like v2024.3.0 or v2024.3.0-rc.1: $release_tag" >&2 + exit 1 + fi + + echo "tag=$release_tag" >> "$GITHUB_OUTPUT" + - name: Test, verify, and build plugin shell: sh run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all - - name: Create tag + - name: Create release tag shell: sh env: - RELEASE_TAG: ${{ inputs.tag }} + DRY_RUN: ${{ inputs.dry_run || 'false' }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + RELEASE_TAG: ${{ steps.tag.outputs.tag }} run: | - case "$RELEASE_TAG" in - v[0-9]*.[0-9]*.[0-9]*) ;; - *) - echo "Tag must look like v2024.3.0" >&2 - exit 1 - ;; - esac - 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 + if [ "$DRY_RUN" = "true" ]; then + echo "Dry run: would create $RELEASE_TAG" + exit 0 + fi + git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + if [ -n "$RELEASE_TOKEN" ]; then + git remote set-url origin "https://x-access-token:${RELEASE_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + fi + git tag -a "$RELEASE_TAG" -m "$RELEASE_TAG" - git push origin "$RELEASE_TAG" + git push origin "refs/tags/$RELEASE_TAG" + + if [ -z "$RELEASE_TOKEN" ]; then + gh workflow run release.yml --ref "$RELEASE_TAG" -f tag="$RELEASE_TAG" + fi diff --git a/README.md b/README.md index eb51dd1..4a9cc22 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,13 @@ [![](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) -## ⚠️ 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. --- From 240d9a5ea7dcdf08f45cfb05a5cc0d1cd84c8e54 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Wed, 20 May 2026 08:21:38 +0200 Subject: [PATCH 03/17] Add Marketplace publish workflow --- .github/workflows/build.yml | 6 +- .github/workflows/publish-marketplace.yml | 104 ++++++++++++++++++++++ .github/workflows/release.yml | 16 +++- .github/workflows/tag.yml | 6 +- 4 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/publish-marketplace.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 783374a..2116561 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,16 +23,16 @@ jobs: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 25 - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v6 - name: Test and verify plugin shell: sh diff --git a/.github/workflows/publish-marketplace.yml b/.github/workflows/publish-marketplace.yml new file mode 100644 index 0000000..e8b408a --- /dev/null +++ b/.github/workflows/publish-marketplace.yml @@ -0,0 +1,104 @@ +name: Publish Marketplace + +on: + release: + types: + - published + workflow_dispatch: + inputs: + tag: + description: Release tag to publish or dry-run. + required: true + type: string + dry_run: + description: Build and validate without publishing to JetBrains Marketplace. + required: true + default: true + type: boolean + +permissions: + contents: read + +concurrency: + group: publish-marketplace-${{ github.event.release.tag_name || inputs.tag }} + cancel-in-progress: false + +jobs: + publish: + name: Publish JetBrains Marketplace + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }} + DRY_RUN: ${{ inputs.dry_run || 'false' }} + + steps: + - name: Validate tag + shell: sh + run: | + if ! printf '%s\n' "$RELEASE_TAG" | grep -Eq '^v[0-9]+[.][0-9]+[.][0-9]+([-+][0-9A-Za-z._-]+)?$'; then + echo "Tag must look like v2024.3.0 or v2024.3.0-rc.1: $RELEASE_TAG" >&2 + exit 1 + fi + + - name: Fetch sources + uses: actions/checkout@v6 + with: + ref: ${{ env.RELEASE_TAG }} + + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 25 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v6 + + - name: Test, verify, and build plugin + shell: sh + run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all + + - name: Validate Marketplace secrets + if: env.DRY_RUN != 'true' + shell: sh + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} + PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} + PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} + run: | + missing="" + if [ -z "$PUBLISH_TOKEN" ]; then + missing="$missing PUBLISH_TOKEN" + fi + if [ -z "$CERTIFICATE_CHAIN" ]; then + missing="$missing CERTIFICATE_CHAIN" + fi + if [ -z "$PRIVATE_KEY" ]; then + missing="$missing PRIVATE_KEY" + fi + if [ -z "$PRIVATE_KEY_PASSWORD" ]; then + missing="$missing PRIVATE_KEY_PASSWORD" + fi + + if [ -n "$missing" ]; then + echo "Missing Marketplace secret(s):$missing" >&2 + exit 1 + fi + + - name: Publish plugin + if: env.DRY_RUN != 'true' + shell: sh + 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 --no-daemon publishPlugin --warning-mode all + + - name: Dry-run summary + if: env.DRY_RUN == 'true' + shell: sh + run: | + echo "Dry run: Marketplace publish skipped for $RELEASE_TAG" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ec345e..1067bf0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,7 @@ on: type: boolean permissions: + actions: write contents: write concurrency: @@ -42,18 +43,18 @@ jobs: fi - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 25 - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v6 - name: Test, verify, and build plugin shell: sh @@ -77,6 +78,7 @@ jobs: shell: sh env: GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | if [ "$DRY_RUN" = "true" ]; then echo "Dry run: would create or update GitHub release $RELEASE_TAG" @@ -89,3 +91,11 @@ jobs: else gh release create "$RELEASE_TAG" ./build/distributions/*.zip --title "$RELEASE_TAG" --notes-file release-notes.md fi + + if [ -z "$RELEASE_TOKEN" ]; then + if gh workflow view publish-marketplace.yml >/dev/null 2>&1; then + gh workflow run publish-marketplace.yml --ref "$RELEASE_TAG" -f tag="$RELEASE_TAG" -f dry_run=false + else + echo "Publish workflow is not on the default branch yet; release event automation will take over after merge." + fi + fi diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index e572d92..60e7099 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -35,19 +35,19 @@ jobs: steps: - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN || github.token }} - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 25 - name: Set up Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v6 - name: Resolve release tag id: tag From 76bda45ff8b87e942f1577b97dfb125332caf7bb Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Wed, 20 May 2026 08:51:13 +0200 Subject: [PATCH 04/17] Harden workflow triggers and issue regressions --- .github/workflows/build.yml | 7 +++++- CHANGELOG.md | 10 ++++++-- README.md | 8 ++++--- build.gradle | 2 +- doc/spec/editor-test-matrix.md | 1 + doc/spec/ux-dx-gaps.md | 23 +++++++++++-------- gradle.properties | 3 +-- .../githubworkflow/model/GitHubAction.java | 2 +- .../services/HighlightAnnotator.java | 19 ++++++++++++--- .../helper/FileDownloaderTest.java | 16 +++++++++++++ .../model/GitHubActionTest.java | 12 +++++++++- .../services/WorkflowHighlightingTest.java | 23 +++++++++++++++++++ 12 files changed, 102 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2116561..2f1ee43 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,8 +4,13 @@ on: workflow_dispatch: push: branches: - - main + - '**' pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review permissions: contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index 326ca32..32701b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,14 @@ - 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 from `Tools > GitHub Workflow` to refresh metadata, clear cached entries, or restore hidden warnings. -- The plugin build, tests, verifier checks, release packaging, and local development setup are ready for the next version. +- 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. +- 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. ## [3.2.1] - 2023-11-04 diff --git a/README.md b/README.md index 4a9cc22..4e8aac1 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,8 @@ _[See Screenshots](https://plugins.jetbrains.com/plugin/21396-github-workflow)_ from [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/21396-github-workflow). * **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 `Tools > GitHub Workflow` to refresh stale remote metadata or clear cached action/workflow metadata. +* **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`. * **Usage**: Enjoy autocomplete, syntax highlighting, and much more as you code your GitHub Workflows and Actions. ## Local Development @@ -58,8 +59,9 @@ Plugin downloads the IDE, bundled plugins, verifier, and test runtime. 2. Run `./gradlew test` for the fast regression suite. 3. Run `./gradlew check verifyPlugin buildPlugin` before publishing or opening a release PR. -For manual IDE testing, run `./gradlew runIde`. The first run downloads IDE artifacts and can take a while. This is -annoying, but at least it is predictable. Progress. +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. This is annoying, but at least it is predictable. Progress. Current UX/DX gaps are tracked in [UX/DX Gaps](doc/spec/ux-dx-gaps.md); editor behavior coverage is tracked in [Editor Test Matrix](doc/spec/editor-test-matrix.md). diff --git a/build.gradle b/build.gradle index 947b678..42d4a55 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ dependencies { testImplementation 'org.assertj:assertj-core:3.26.3' intellijPlatform { - intellijIdeaCommunity(requiredProperty('platformVersion')) + intellijIdea(requiredProperty('platformVersion')) bundledPlugins(csvProperty('platformPlugins')) pluginVerifier() testFramework(TestFrameworkType.Platform.INSTANCE) diff --git a/doc/spec/editor-test-matrix.md b/doc/spec/editor-test-matrix.md index f1f7d50..d28f836 100644 --- a/doc/spec/editor-test-matrix.md +++ b/doc/spec/editor-test-matrix.md @@ -67,6 +67,7 @@ Official syntax references: - 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. diff --git a/doc/spec/ux-dx-gaps.md b/doc/spec/ux-dx-gaps.md index 50b83ba..6f1687b 100644 --- a/doc/spec/ux-dx-gaps.md +++ b/doc/spec/ux-dx-gaps.md @@ -6,16 +6,19 @@ no plugin-owned server settings UI. - Unresolved remote actions now mention common failure modes directly in the editor, including account access, private repository permissions, rate limits, missing refs, and missing metadata. Per-status diagnostics are still missing. -- Cache controls are exposed under `Tools > GitHub Workflow`, with clear and refresh actions for resolved metadata. A - richer cache inspector is still missing. -- Suppressed action/input/output warnings can be restored from `Tools > GitHub Workflow`. A detailed suppression review - panel is still missing. +- Cache controls are exposed as IDE actions discoverable through Find Action and, where the classic Tools menu is + visible, under `Tools > GitHub Workflow`. A richer cache inspector is still missing. +- Suppressed action/input/output warnings can be restored through the `Restore Action Warnings` IDE action. A detailed + suppression review panel is still missing. - Link navigation now covers resolved local/remote `uses` from github.com and GitHub Enterprise, job IDs, `needs`, inputs, secrets, envs, matrix keys, job service IDs/ports, step output references, and reusable workflow job outputs where a local target exists. Remote metadata internals still need explicit data sources. - Quick documentation now covers resolved `uses`, workflow/action parameters, expression variables, and common context segments. The remaining gap is richer rendered Markdown from remote action README files. +- Workflow execution UX is still missing. The preferred shape is a GitHub Workflow run configuration type with gutter + play actions for runnable workflow files, an inputs editor for `workflow_dispatch`, Run tool window output, status + polling, and Stop mapped to canceling the remote workflow run. - Plugin/action/cache-control labels and notifications use resource bundles with top-20 locale files. Most editor inspection, quick-fix, and documentation strings still need extraction before localization can be called complete. - Highlighting, references, styling, documentation, and completion now have focused fixture tests through YAML editor @@ -30,10 +33,10 @@ - Editor fixture coverage has been split into isolated highlighting, reference, and completion tests. The remaining matrix is tracked in [Editor Test Matrix](editor-test-matrix.md). - Bounded large-workflow highlighting performance coverage now runs through `./gradlew performanceTest`. -- A fake HTTP server covers remote metadata behavior deterministically. Remote reload gutter execution still needs a - resolver boundary before it can be exercised without sleeps. -- The release workflow builds, verifies, packages, and uploads the plugin zip to a GitHub release. It still needs one - real dry run before it can be considered boring. -- Tag and GitHub release workflows are main-branch gated. Marketplace publishing/signing is intentionally not wired into - release yet. +- A fake HTTP server covers remote metadata behavior deterministically. Remote reload gutter execution has a resolver + boundary and no-sleep test coverage. +- The release workflow builds, verifies, packages, and uploads the plugin zip to a GitHub release. Disposable tag/release + tests have passed; production release behavior still needs the first real main-branch release. +- Tag and GitHub release workflows are main-branch/tag gated. Marketplace publishing/signing is wired to published + releases and should stay dry-run/manual until repository secrets are confirmed. - UI testing is not configured. The stale UI workflow was removed instead of pretending it worked. Harsh, but fair. diff --git a/gradle.properties b/gradle.properties index 55d66c8..2e1934a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,8 +15,7 @@ pluginSinceBuild = 243 # Not specifying until-build means it will include all future builds (including unreleased IDE versions, which might impact compatibility later). # 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 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 095dd38..5226995 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java @@ -109,7 +109,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)) 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 21343d2..b3f2ed9 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java @@ -157,9 +157,10 @@ 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, @@ -172,6 +173,18 @@ 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) diff --git a/src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java b/src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java index 40e0d31..7bb3f13 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java @@ -62,6 +62,22 @@ public void downloadSyncReturnsEmptyStringForHttpFailures() { 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/model/GitHubActionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java index 5b1df72..eeef4a8 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java @@ -25,12 +25,22 @@ public void createGithubActionBuildsRemoteActionUrls() { 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"); + 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"); diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java index 7074074..7323771 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java @@ -1244,6 +1244,29 @@ public void testUsedJobOutputIsAccepted() { """); } + 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 From 82eedd2ca84b2b9ee6f1e66d63f02936301d27e3 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Wed, 20 May 2026 09:03:19 +0200 Subject: [PATCH 05/17] Update artifact upload action --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f1ee43..f02f662 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,7 +49,7 @@ jobs: - name: Upload test reports if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: test-reports path: | @@ -59,14 +59,14 @@ jobs: - name: Upload verifier reports id: upload if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: plugin-verifier-reports path: build/reports/pluginVerifier - name: Upload plugin archive if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: plugin-archive path: build/distributions/*.zip From 01e7773860b16f687979f7d9eea86e763ce06035 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Fri, 22 May 2026 20:35:52 +0200 Subject: [PATCH 06/17] Harden workflow editing and run UX --- .github/workflows/publish-marketplace.yml | 4 +- .github/workflows/release.yml | 4 +- .github/workflows/tag.yml | 63 +- CHANGELOG.md | 25 +- CONTRIBUTING.md | 13 +- README.md | 42 +- build.gradle | 33 + {docs => doc}/DataPrivacy.md | 12 +- ...001-use-gradle-wrapper-for-plugin-build.md | 2 +- ...-use-date-based-semver-and-242-baseline.md | 34 + ...t-through-editor-and-runtime-boundaries.md | 32 + ...n-configurations-for-workflow-execution.md | 33 + ...t-matched-github-account-authentication.md | 34 + {docs => doc}/ghw_arch.png | Bin {docs => doc}/images/data_privacy.png | Bin doc/navigation.md | 36 + doc/spec/editor-test-matrix.md | 27 +- doc/spec/ux-dx-gaps.md | 33 +- docs/navigation.md | 87 -- gradle.properties | 6 +- .../githubworkflow/helper/FileDownloader.java | 4 +- .../helper/GitHubWorkflowConfig.java | 83 +- .../helper/HighlightAnnotatorHelper.java | 25 +- .../helper/PsiElementHelper.java | 13 +- .../githubworkflow/logic/Action.java | 49 +- .../githubworkflow/logic/JobContext.java | 9 +- .../githubworkflow/logic/Needs.java | 5 +- .../githubworkflow/logic/Secrets.java | 7 +- .../model/CustomClickAction.java | 1 + .../githubworkflow/model/GitHubAction.java | 17 +- .../githubworkflow/model/IconRenderer.java | 34 +- .../model/SyntaxAnnotation.java | 18 +- .../services/ClearActionCacheAction.java | 11 + .../services/CodeCompletion.java | 40 +- .../services/GitHubActionCache.java | 262 +++++- .../services/GitHubRequestAuthorizations.java | 143 ++++ .../services/GitHubWorkflowBundle.java | 19 + .../GitHubWorkflowSettingsConfigurable.java | 295 +++++++ .../services/HighlightAnnotator.java | 35 +- .../services/PluginErrorReportSubmitter.java | 23 +- .../services/PluginSettings.java | 64 ++ .../services/RefreshActionCacheAction.java | 7 + .../services/RemoteActionProviders.java | 51 +- .../services/RemoteServerSettings.java | 15 +- .../services/RestoreActionWarningsAction.java | 7 + .../WorkflowCurrentBranchResolver.java | 86 ++ .../services/WorkflowDispatchInputs.java | 156 ++++ .../WorkflowDocumentationProvider.java | 124 +-- .../services/WorkflowRepository.java | 12 + .../services/WorkflowRepositoryResolver.java | 102 +++ .../services/WorkflowRunClient.java | 464 ++++++++++ .../services/WorkflowRunConfiguration.java | 178 ++++ .../WorkflowRunConfigurationProducer.java | 93 ++ .../WorkflowRunConfigurationType.java | 70 ++ .../services/WorkflowRunConsoleTabs.java | 795 ++++++++++++++++++ .../services/WorkflowRunJobConsole.java | 39 + .../WorkflowRunLineMarkerContributor.java | 87 ++ .../services/WorkflowRunLogRenderer.java | 209 +++++ .../services/WorkflowRunProcessHandler.java | 493 +++++++++++ .../services/WorkflowRunRequest.java | 34 + .../services/WorkflowRunSettingsEditor.java | 94 +++ .../services/WorkflowRunTracker.java | 65 ++ src/main/resources/META-INF/plugin.xml | 10 + .../messages/GitHubWorkflowBundle.properties | 245 ++++++ .../GitHubWorkflowBundle_ar.properties | 247 ++++++ .../GitHubWorkflowBundle_cs.properties | 247 ++++++ .../GitHubWorkflowBundle_de.properties | 247 ++++++ .../GitHubWorkflowBundle_es.properties | 247 ++++++ .../GitHubWorkflowBundle_fr.properties | 247 ++++++ .../GitHubWorkflowBundle_hi.properties | 247 ++++++ .../GitHubWorkflowBundle_id.properties | 247 ++++++ .../GitHubWorkflowBundle_it.properties | 247 ++++++ .../GitHubWorkflowBundle_ja.properties | 247 ++++++ .../GitHubWorkflowBundle_ko.properties | 247 ++++++ .../GitHubWorkflowBundle_nl.properties | 247 ++++++ .../GitHubWorkflowBundle_pl.properties | 247 ++++++ .../GitHubWorkflowBundle_pt_BR.properties | 247 ++++++ .../GitHubWorkflowBundle_ru.properties | 247 ++++++ .../GitHubWorkflowBundle_sv.properties | 247 ++++++ .../GitHubWorkflowBundle_th.properties | 247 ++++++ .../GitHubWorkflowBundle_tr.properties | 247 ++++++ .../GitHubWorkflowBundle_uk.properties | 247 ++++++ .../GitHubWorkflowBundle_vi.properties | 247 ++++++ .../GitHubWorkflowBundle_zh_CN.properties | 247 ++++++ .../services/EditorFeatureTestCase.java | 28 +- .../services/GitHubActionCacheTest.java | 71 ++ .../GitHubRequestAuthorizationsTest.java | 53 ++ .../services/LocalizationResourcesTest.java | 47 ++ .../WorkflowActionRegistrationTest.java | 36 + .../services/WorkflowCompletionTest.java | 13 + .../WorkflowCurrentBranchResolverTest.java | 40 + .../services/WorkflowDispatchInputsTest.java | 54 ++ .../services/WorkflowGutterActionTest.java | 140 +++ .../services/WorkflowQuickFixTest.java | 75 ++ .../WorkflowRepositoryResolverTest.java | 58 ++ .../services/WorkflowRunClientTest.java | 381 +++++++++ .../services/WorkflowRunConsoleTabsTest.java | 22 + .../services/WorkflowRunLogRendererTest.java | 136 +++ .../WorkflowRunProcessHandlerTest.java | 615 ++++++++++++++ 99 files changed, 11486 insertions(+), 336 deletions(-) rename {docs => doc}/DataPrivacy.md (81%) create mode 100644 doc/adr/0007-use-date-based-semver-and-242-baseline.md create mode 100644 doc/adr/0008-test-through-editor-and-runtime-boundaries.md create mode 100644 doc/adr/0009-use-run-configurations-for-workflow-execution.md create mode 100644 doc/adr/0010-use-host-matched-github-account-authentication.md rename {docs => doc}/ghw_arch.png (100%) rename {docs => doc}/images/data_privacy.png (100%) create mode 100644 doc/navigation.md delete mode 100644 docs/navigation.md create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizations.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowSettingsConfigurable.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/PluginSettings.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolver.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepository.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolver.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfiguration.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationProducer.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationType.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLineMarkerContributor.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRenderer.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunRequest.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunTracker.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizationsTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolverTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolverTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabsTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRendererTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java diff --git a/.github/workflows/publish-marketplace.yml b/.github/workflows/publish-marketplace.yml index e8b408a..5ce918c 100644 --- a/.github/workflows/publish-marketplace.yml +++ b/.github/workflows/publish-marketplace.yml @@ -36,8 +36,8 @@ jobs: - name: Validate tag shell: sh run: | - if ! printf '%s\n' "$RELEASE_TAG" | grep -Eq '^v[0-9]+[.][0-9]+[.][0-9]+([-+][0-9A-Za-z._-]+)?$'; then - echo "Tag must look like v2024.3.0 or v2024.3.0-rc.1: $RELEASE_TAG" >&2 + 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1067bf0..1b29079 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,8 +37,8 @@ jobs: - name: Validate tag shell: sh run: | - if ! printf '%s\n' "$RELEASE_TAG" | grep -Eq '^v[0-9]+[.][0-9]+[.][0-9]+([-+][0-9A-Za-z._-]+)?$'; then - echo "Tag must look like v2024.3.0 or v2024.3.0-rc.1: $RELEASE_TAG" >&2 + 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 diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 60e7099..d196791 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: inputs: tag: - description: Release tag to create. Defaults to v. + description: Release tag to create. Defaults to today's vYYYY.M.D. required: false type: string dry_run: @@ -58,27 +58,26 @@ jobs: if [ -n "$INPUT_TAG" ]; then release_tag="$INPUT_TAG" else - version="$(awk -F= '/^[[:space:]]*pluginVersion[[:space:]]*=/{ gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2 }' gradle.properties)" + year="$(date -u +%Y)" + month="$(date -u +%m | sed 's/^0//')" + day="$(date -u +%d | sed 's/^0//')" + version="$year.$month.$day" release_tag="v$version" fi - if ! printf '%s\n' "$release_tag" | grep -Eq '^v[0-9]+[.][0-9]+[.][0-9]+([-+][0-9A-Za-z._-]+)?$'; then - echo "Tag must look like v2024.3.0 or v2024.3.0-rc.1: $release_tag" >&2 + 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 echo "tag=$release_tag" >> "$GITHUB_OUTPUT" + echo "version=${release_tag#v}" >> "$GITHUB_OUTPUT" - - name: Test, verify, and build plugin - shell: sh - run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all - - - name: Create release tag + - name: Prepare release version shell: sh env: DRY_RUN: ${{ inputs.dry_run || 'false' }} - GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + RELEASE_VERSION: ${{ steps.tag.outputs.version }} RELEASE_TAG: ${{ steps.tag.outputs.tag }} run: | git fetch --tags --force @@ -87,18 +86,56 @@ jobs: exit 1 fi + awk -v version="$RELEASE_VERSION" ' + /^[[:space:]]*pluginVersion[[:space:]]*=/ { + print "pluginVersion = " version + next + } + { print } + ' gradle.properties > gradle.properties.tmp + mv gradle.properties.tmp gradle.properties + + if ! git diff --quiet -- gradle.properties; then + git config user.name "Kira" + git config user.email "kira@yuna.berlin" + git add gradle.properties + if [ "$DRY_RUN" = "true" ]; then + echo "Dry run: would commit pluginVersion $RELEASE_VERSION" + else + git commit -m "chore: release $RELEASE_VERSION" + fi + fi + + - name: Test, verify, and build plugin + shell: sh + run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all + + - name: Push release commit and tag + shell: sh + env: + DRY_RUN: ${{ inputs.dry_run || 'false' }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + RELEASE_TAG: ${{ steps.tag.outputs.tag }} + run: | if [ "$DRY_RUN" = "true" ]; then echo "Dry run: would create $RELEASE_TAG" exit 0 fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config user.name "Kira" + git config user.email "kira@yuna.berlin" 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" + if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + target_branch="${GITHUB_BASE_REF:-main}" + fi + git push origin "HEAD:$target_branch" + git tag -a "$RELEASE_TAG" -m "$RELEASE_TAG" git push origin "refs/tags/$RELEASE_TAG" diff --git a/CHANGELOG.md b/CHANGELOG.md index 32701b1..7a4d20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,18 @@ ## [Unreleased] -## [2024.3.0] - 2026-05-19 - ### 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. @@ -18,10 +24,25 @@ - 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 4e8aac1..3b02834 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![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 Is Active Again @@ -32,7 +32,15 @@ _[See Screenshots](https://plugins.jetbrains.com/plugin/21396-github-workflow)_ * 🚀 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 or clear resolved action/workflow metadata from `Tools > GitHub Workflow`. +* 🧹 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 @@ -47,7 +55,17 @@ _[See Screenshots](https://plugins.jetbrains.com/plugin/21396-github-workflow)_ * **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`. + 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 @@ -61,7 +79,9 @@ Plugin downloads the IDE, bundled plugins, verifier, and test runtime. 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. This is annoying, but at least it is predictable. Progress. +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. Current UX/DX gaps are tracked in [UX/DX Gaps](doc/spec/ux-dx-gaps.md); editor behavior coverage is tracked in [Editor Test Matrix](doc/spec/editor-test-matrix.md). @@ -90,7 +110,7 @@ Yuna Morgenstern, your GitHub Jedi. -#### TODO +#### Project Checklist - [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) @@ -102,7 +122,8 @@ Yuna Morgenstern, your GitHub Jedi. ## Learning List - [x] Create Tests -- [ ] Refactor - less custom elements == less memory leaks +- [ ] 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 @@ -116,13 +137,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 index 42d4a55..61aa35e 100644 --- a/build.gradle +++ b/build.gradle @@ -142,6 +142,39 @@ 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.' 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 index 3236b7c..9d52510 100644 --- a/doc/adr/0001-use-gradle-wrapper-for-plugin-build.md +++ b/doc/adr/0001-use-gradle-wrapper-for-plugin-build.md @@ -17,7 +17,7 @@ plugin packaging, signing, publishing, and Plugin Verifier tasks. 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.3 baseline while keeping the +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 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/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 index d28f836..4dc0fb8 100644 --- a/doc/spec/editor-test-matrix.md +++ b/doc/spec/editor-test-matrix.md @@ -18,13 +18,15 @@ Official syntax references: - 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 are registered through `plugin.xml`, localized through resource bundles, and covered by cache summary - and clear behavior tests. -- The default resource bundle and 20 locale resource bundles keep matching key sets with nonblank values. +- 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. - 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. @@ -73,11 +75,30 @@ Official syntax references: - 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. - 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. - 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. - 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 process behavior streams job log deltas without auth-strategy noise, routes each GitHub job to the + Run-window job tree/detail console, prints job URLs before status lines, prints compact ASCII job progress with + elapsed times and `[WAIT]` / `[RUN]` / `[OK]` / `[FAIL]` markers, 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. diff --git a/doc/spec/ux-dx-gaps.md b/doc/spec/ux-dx-gaps.md index 6f1687b..1211de3 100644 --- a/doc/spec/ux-dx-gaps.md +++ b/doc/spec/ux-dx-gaps.md @@ -2,12 +2,16 @@ ## UX -- Remote action resolution uses public GitHub plus GitHub Enterprise accounts from JetBrains GitHub settings. There is - no plugin-owned server settings UI. +- Remote action resolution uses public GitHub plus GitHub Enterprise accounts from JetBrains GitHub settings. Tokens are + only sent to matching GitHub hosts, and anonymous access is the fallback. There is no plugin-owned server settings UI. - Unresolved remote actions now mention common failure modes directly in the editor, including account access, private - repository permissions, rate limits, missing refs, and missing metadata. Per-status diagnostics are still missing. + repository permissions, rate limits, missing refs, and missing metadata. Workflow dispatch failures report 401/rate + limit failures with a GitHub settings hint. Per-status editor diagnostics for remote metadata are still missing. - Cache controls are exposed as IDE actions discoverable through Find Action and, where the classic Tools menu is - visible, under `Tools > GitHub Workflow`. A richer cache inspector is still missing. + visible, under `Tools > GitHub Workflow`. Settings also expose a cache inspector with per-entry review, selected/all + deletion, import/export, and plugin cache size. +- Workflow run logs use one Run tool-window content tab with a JUnit-style execution tree and a detail console so jobs, + timings, warnings, and failures read like normal IDE runs instead of a tab parade. - Suppressed action/input/output warnings can be restored through the `Restore Action Warnings` IDE action. A detailed suppression review panel is still missing. - Link navigation now covers resolved local/remote `uses` from github.com and GitHub Enterprise, job IDs, `needs`, @@ -16,11 +20,20 @@ metadata internals still need explicit data sources. - Quick documentation now covers resolved `uses`, workflow/action parameters, expression variables, and common context segments. The remaining gap is richer rendered Markdown from remote action README files. -- Workflow execution UX is still missing. The preferred shape is a GitHub Workflow run configuration type with gutter - play actions for runnable workflow files, an inputs editor for `workflow_dispatch`, Run tool window output, status - polling, and Stop mapped to canceling the remote workflow run. -- Plugin/action/cache-control labels and notifications use resource bundles with top-20 locale files. Most editor - inspection, quick-fix, and documentation strings still need extraction before localization can be called complete. +- Workflow execution now has a first Run Configuration implementation for `workflow_dispatch`: context-created GitHub + Workflow configurations defaulting to the current Git branch, a gutter play/stop action on `workflow_dispatch`, + `key=value` dispatch inputs, account-first authentication through JetBrains GitHub settings with successful-token reuse + during polling, environment-token fallback before anonymous access, Run tool window status output, one JUnit-style + workflow tree with grouped jobs, selected-node log output, job URLs in the detail console, a thin progress bar, elapsed + job timing, and Stop mapped to canceling the remote workflow run. The workflow tree uses test-style status icons and + compact GitHub timestamps/log commands into named blocks with four-digit line numbers, `run:` command lines, ANSI + cleanup, and warning/error console colors. + Remaining execution UX gaps are richer input widgets for typed choices/booleans, passive run lists for workflows + triggered by non-dispatch events, historical duration estimates, and deeper GitHub-style log rendering such as step + folding and clickable SHAs. +- Plugin/action/cache-control labels, settings, run-console status text, issue reporting, editor inspections, quick fixes, + completion detail text, and workflow documentation labels use resource bundles with top-20 locale files. Remaining + localization work is mostly native translation review for newly added fallback-English keys. - Highlighting, references, styling, documentation, and completion now have focused fixture tests through YAML editor entrypoints. GitHub context/default-env metadata is generated from checked-in documentation snapshots. @@ -39,4 +52,6 @@ tests have passed; production release behavior still needs the first real main-branch release. - Tag and GitHub release workflows are main-branch/tag gated. Marketplace publishing/signing is wired to published releases and should stay dry-run/manual until repository secrets are confirmed. +- The tag workflow now creates a date-based plugin version commit with Kira bot identity, tags that commit, and pushes + both commit and tag. - UI testing is not configured. The stale UI workflow was removed instead of pretending it worked. Harsh, but fair. 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 2e1934a..f2ebc43 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,14 +4,14 @@ pluginId=com.github.yunabraska.githubworkflowplugin pluginGroup = berlin.yuna 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). # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension 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 ea6be32..2bfeb1a 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/FileDownloader.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/FileDownloader.java @@ -19,6 +19,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.util.Optional; +import java.util.Comparator; import java.util.concurrent.Future; import static java.util.Optional.ofNullable; @@ -33,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(PsiElementHelper::hasText) .findFirst() - .orElse(""); + .orElseGet(() -> downloadContent(downloadUrl)); } 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 322d895..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,6 +1,8 @@ package com.github.yunabraska.githubworkflow.helper; +import com.github.yunabraska.githubworkflow.services.GitHubWorkflowBundle; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -27,6 +29,7 @@ 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"; @@ -49,6 +52,7 @@ public class GitHubWorkflowConfig { 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 LinkedHashMap<>(); @@ -62,58 +66,73 @@ private static Map>> initProcessorMap() { return result; } + 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", "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", "The path to the directory containing preinstalled tools for GitHub-hosted runners."); - result.put("debug", "This is set only if debug logging is enabled, and always has the value of 1."); - result.put("environment", "The environment of the runner executing the job. Possible values are github-hosted or self-hosted."); + 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 Map getJobItems() { final Map result = new LinkedHashMap<>(); - result.put("status", "The current status of the job."); - result.put("check_run_id", "The check run ID of the current job."); - result.put("container", "Information about the job's container."); - result.put("services", "The service containers created for a job."); - result.put("workflow_ref", "The full ref of the workflow file that defines the current job."); - result.put("workflow_sha", "The commit SHA of the workflow file that defines the current job."); - result.put("workflow_repository", "The owner/repo of the repository containing the workflow file that defines the current job."); - result.put("workflow_file_path", "The workflow file path, relative to the repository root."); + 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; } private static Map getStrategyItems() { final Map result = new LinkedHashMap<>(); - result.put("fail-fast", "Whether all in-progress jobs are canceled if any matrix job fails."); - result.put("job-index", "The zero-based index of the current job in the matrix."); - result.put("job-total", "The total number of jobs in the matrix."); - result.put("max-parallel", "The maximum number of matrix jobs that can run simultaneously."); + 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; } private static Map getCaretBracketItems() { final Map result = new LinkedHashMap<>(); - result.put(FIELD_INPUTS, "Workflow inputs e.g. from workflow_dispatch, workflow_call"); - result.put(FIELD_SECRETS, "Workflow secrets"); - result.put(FIELD_JOB, "Information about the currently running job"); - result.put(FIELD_JOBS, "Workflow jobs"); - result.put(FIELD_MATRIX, "Matrix properties defined for the current matrix job"); - result.put(FIELD_STRATEGY, "Matrix execution strategy information for the current job"); - 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"); - result.put(FIELD_GITEA, "Information about the Gitea Actions workflow run. Gitea keeps many GitHub-compatible context names and also exposes gitea.* in Gitea workflows."); - result.put(FIELD_RUNNER, "Information about the runner that is executing the current job"); + 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"); } 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 ed6c439..a3160ba 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java @@ -5,6 +5,7 @@ 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; @@ -56,8 +57,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); } } @@ -72,7 +73,7 @@ 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); @@ -80,7 +81,7 @@ public static void ifEnoughItems( final SimpleElement[] tooLongPart = Arrays.copyOfRange(parts, max, parts.length); 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(".")) + "]", + GitHubWorkflowBundle.message("inspection.invalid.suffix.remove", Arrays.stream(tooLongPart).map(SimpleElement::text).collect(Collectors.joining("."))), null, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); @@ -95,7 +96,7 @@ 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 + "]", + GitHubWorkflowBundle.message("inspection.replace.with", item), null, replaceAction(textRange, item) )).toList()); @@ -112,7 +113,7 @@ public static boolean isField2Valid(@NotNull final PsiElement psiElement, @NotNu if (!validFields.contains(itemId.text())) { final TextRange textRange = textRangeIncludingPreviousDot(psiElement, itemId, itemId); new SyntaxAnnotation( - "Remove invalid [" + itemId.text() + "]", + GitHubWorkflowBundle.message("inspection.invalid.remove", itemId.text()), null, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); @@ -125,7 +126,7 @@ 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 + "]", + GitHubWorkflowBundle.message("inspection.replace.with", item), null, replaceAction(textRange, item) )).toList()); @@ -135,7 +136,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, @@ -146,7 +147,7 @@ public static SyntaxAnnotation newReloadAction(final GitHubAction action) { @NotNull public static SyntaxAnnotation newUnresolvedAction(final YAMLKeyValue element) { return new SyntaxAnnotation( - "Unresolved [" + removeQuotes(element.getValueText()) + "] - check GitHub account access, private repository permissions, rate limits, missing refs, or missing action/workflow metadata", + GitHubWorkflowBundle.message("inspection.action.unresolved", removeQuotes(element.getValueText())), SETTINGS, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, @@ -160,7 +161,7 @@ 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() + "]", + GitHubWorkflowBundle.message("inspection.invalid.remove", element.getValueText()), null, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, @@ -172,7 +173,7 @@ public static SyntaxAnnotation deleteInvalidAction(final YAMLKeyValue element) { 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, @@ -244,7 +245,7 @@ private static boolean isEmpty(final Collection items, final SimpleEleme if (itemId != null && items.isEmpty()) { final TextRange textRange = simpleTextRange(psiElement, itemId); createAnnotation(psiElement, textRange, holder, List.of(new SyntaxAnnotation( - "Delete invalid [" + itemId.text() + "]", + GitHubWorkflowBundle.message("inspection.invalid.remove", itemId.text()), null, deleteElementAction(textRange) ))); 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 4e9b2eb..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,6 +8,7 @@ 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; @@ -274,16 +275,16 @@ public static String getDescription(final PsiElement psiElement, final boolean r } final List details = new ArrayList<>(); getText(psiElement, "description").or(() -> getText(psiElement, "desc")) - .ifPresent(description -> details.add("Description: " + description)); + .ifPresent(description -> details.add(GitHubWorkflowBundle.message("documentation.description", description))); getText(psiElement, "type") - .ifPresent(type -> details.add("Type: " + type)); + .ifPresent(type -> details.add(GitHubWorkflowBundle.message("documentation.type", type))); if (requiredField) { - details.add("Required: " + getText(psiElement, "required").map(Boolean::parseBoolean).orElse(false)); + details.add(GitHubWorkflowBundle.message("documentation.required", getText(psiElement, "required").map(Boolean::parseBoolean).orElse(false))); } getText(psiElement, "default") - .ifPresent(defaultValue -> details.add("Default: " + defaultValue)); + .ifPresent(defaultValue -> details.add(GitHubWorkflowBundle.message("documentation.default", defaultValue))); getText(psiElement, "deprecationMessage") - .ifPresent(message -> details.add("Deprecated: " + message)); + .ifPresent(message -> details.add(GitHubWorkflowBundle.message("documentation.deprecated", message))); return String.join("\n", details); } @@ -321,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(", ")) 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 2ee372e..52cc622 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java @@ -5,6 +5,7 @@ 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.AnnotationHolder; import com.intellij.lang.annotation.HighlightSeverity; @@ -33,6 +34,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; @@ -58,6 +60,7 @@ 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)); highlightResolvedActionReference(holder, element, action, result); @@ -87,9 +90,11 @@ private static void highlightCallableParameter(final AnnotationHolder holder, fi 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) ? "secret" : "input"; + final String label = FIELD_SECRETS.equals(parameterType) + ? GitHubWorkflowBundle.message("inspection.parameter.secret") + : GitHubWorkflowBundle.message("inspection.parameter.input"); addAnnotation(holder, item, new SyntaxAnnotation( - "Delete invalid " + label + " [" + id + "]", + GitHubWorkflowBundle.message("inspection.action.delete.invalid", label, id), null, deleteElementAction(item.getTextRange()) )); @@ -140,6 +145,44 @@ private static void highlightResolvedActionReference(final AnnotationHolder hold } } + 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), + null, + 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) @@ -203,7 +246,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/JobContext.java b/src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java index 4896742..1d62895 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java @@ -1,6 +1,7 @@ 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; @@ -58,22 +59,22 @@ public static List codeCompletionJob() { public static List codeCompletionJob(final String parent, final PsiElement position) { return switch (parent) { - case FIELD_CONTAINER -> completionItemsOf(toMap(CONTAINER_FIELDS, "Job container field"), ICON_NODE); - case FIELD_SERVICES -> completionItemsOf(toMap(listServiceIds(position), "Job service"), ICON_NODE); + 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, "Job service field"), ICON_NODE); + 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), "Mapped service port"), ICON_NODE); + return completionItemsOf(toMap(listServicePorts(position, serviceId), GitHubWorkflowBundle.message("completion.job.mappedServicePort")), ICON_NODE); } return List.of(); } 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 d73fbcc..3a6d67f 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java @@ -5,6 +5,7 @@ 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; @@ -76,7 +77,7 @@ 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", + GitHubWorkflowBundle.message("inspection.needs.invalid.job", element.getText()), null, deleteElementAction(psiElement.getTextRange()) )); @@ -120,7 +121,7 @@ public static Optional referenceNeeds(final PsiElement psiElemen 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()) + final String tooltip = GitHubWorkflowBundle.message("documentation.open.declaration", Arrays.stream(KeymapUtil.getActiveKeymapShortcuts("GotoDeclaration").getShortcuts()) .limit(2) .map(KeymapUtil::getShortcutText) .collect(Collectors.joining(", ")) 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 4b33f34..578582d 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; @@ -49,7 +50,7 @@ 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", + GitHubWorkflowBundle.message("inspection.secret.invalid.if", simpleElement.text()), null, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); @@ -58,7 +59,7 @@ 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", + GitHubWorkflowBundle.message("inspection.secret.replace.runtime", secretId.text(), secret), null, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, @@ -83,7 +84,7 @@ public static List listSecrets(final PsiElement psiElement) { LinkedHashMap::new ))) .orElseGet(LinkedHashMap::new); - result.putIfAbsent(GITHUB_TOKEN, "Automatically created token for each workflow run."); + result.putIfAbsent(GITHUB_TOKEN, GitHubWorkflowBundle.message("completion.secret.githubToken")); return completionItemsOf(result, ICON_SECRET_WORKFLOW); } 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 5226995..300046e 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java @@ -44,7 +44,6 @@ 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; @@ -287,11 +286,15 @@ 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()); } /** @@ -300,7 +303,9 @@ public Set ignoredOutputs() { * @return {@code true} when the whole action, at least one input, or at least one output is suppressed */ public boolean hasSuppressedWarnings() { - return isSuppressed() || !ignoredInputs.isEmpty() || !ignoredOutputs.isEmpty(); + return isSuppressed() + || ignoredInputs.stream().anyMatch(PsiElementHelper::hasText) + || ignoredOutputs.stream().anyMatch(PsiElementHelper::hasText); } /** @@ -351,8 +356,8 @@ public Map getMetaData() { public GitHubAction setMetaData(final Map metaData) { ofNullable(metaData).ifPresent(values -> { this.metaData.putAll(values); - this.ignoredInputs.addAll(Arrays.stream(values.getOrDefault("ignoredInputs", "").split(";")).toList()); - this.ignoredOutputs.addAll(Arrays.stream(values.getOrDefault("ignoredOutputs", "").split(";")).toList()); + 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; } 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/SyntaxAnnotation.java b/src/main/java/com/github/yunabraska/githubworkflow/model/SyntaxAnnotation.java index 22038ae..ed7e117 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/SyntaxAnnotation.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/SyntaxAnnotation.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Objects; import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -93,13 +94,24 @@ public static void createAnnotation( final List fixes ) { if (fixes != null && !fixes.isEmpty() && holder != null && psiElement != null && psiElement.isValid()) { + final List gutterFixes = fixes.stream() + .filter(fix -> fix.icon != null) + .toList(); + final SyntaxAnnotation gutterFix = gutterFixes.stream() + .filter(fix -> fix.icon != NodeIcon.EMPTY) + .findFirst() + .or(() -> gutterFixes.stream().findFirst()) + .orElse(null); + final AtomicBoolean gutterIconUsed = new AtomicBoolean(false); fixes.stream().collect(Collectors.groupingBy(f -> f.level)).forEach((level, group) -> { final SyntaxAnnotation firstItem = group.get(0); final AnnotationBuilder annotation = holder.newAnnotation(level, firstItem.showToolTip ? firstItem.text : ""); 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); + if (gutterFix != null && group.contains(gutterFix) && gutterIconUsed.compareAndSet(false, true)) { + annotation.gutterIconRenderer(new IconRenderer(gutterFix, psiElement, gutterFix.icon, fixes)); + } group.forEach(fix -> ofNullable(fix.execute).ifPresent(exec -> annotation.withFix(fix))); annotation.create(); }); @@ -133,6 +145,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/services/ClearActionCacheAction.java b/src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java index ad2ce8f..37ff048 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java @@ -4,6 +4,7 @@ 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; @@ -16,6 +17,11 @@ public void actionPerformed(@NotNull final AnActionEvent event) { 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; @@ -27,4 +33,9 @@ private static void notify(final AnActionEvent event, final String content) { .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 d46084a..caa6298 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java @@ -130,6 +130,13 @@ public void addCompletions( NodeIcon.ICON_NODE, Character.MIN_VALUE ); + } else if (isCompletingShellField(parameters, position)) { + addLookupElements( + resultSet.withPrefixMatcher(getDefaultPrefix(parameters)), + SHELLS, + NodeIcon.ICON_NODE, + Character.MIN_VALUE + ); } else if (isCompletingCallableSecrets(position)) { currentCallableAction(parameters, position) .map(GitHubAction::freshSecrets) @@ -173,6 +180,17 @@ private static boolean isCompletingUsesField(final CompletionParameters paramete 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 int offset = Math.min(parameters.getOffset(), wholeText.length()); + final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; + final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + return beforeCaret.matches("\\s*" + FIELD_SHELL + "\\s*:\\s*.*"); + } + private static Optional remoteUsesRef(final CompletionParameters parameters) { final String wholeText = parameters.getOriginalFile().getText(); final int offset = Math.min(parameters.getOffset(), wholeText.length()); @@ -210,7 +228,9 @@ private static Map localUsesCompletions(final PsiElement positio final Map result = new LinkedHashMap<>(); ProjectFileIndex.getInstance(project).iterateContent(file -> { toLocalUsesValue(project, currentFile, file, reusableWorkflowUse) - .ifPresent(value -> result.putIfAbsent(value, reusableWorkflowUse ? "Local reusable workflow" : "Local action")); + .ifPresent(value -> result.putIfAbsent(value, GitHubWorkflowBundle.message(reusableWorkflowUse + ? "completion.uses.local.workflow" + : "completion.uses.local.action"))); return true; }); return result; @@ -221,14 +241,14 @@ private static Map knownRemoteRefs(final PsiElement position, fi 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, "Known workflow reference")); + .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(), "Known workflow reference")); + .forEach(uses -> result.putIfAbsent(uses.ref(), GitHubWorkflowBundle.message("completion.uses.ref.known"))); GitHubActionCache.getActionCache().remoteRefsFor(usesBase, 10) - .forEach(ref -> result.putIfAbsent(ref, "Remote workflow reference")); + .forEach(ref -> result.putIfAbsent(ref, GitHubWorkflowBundle.message("completion.uses.ref.remote"))); return result; } @@ -237,7 +257,7 @@ private static Map knownRemoteUses(final PsiElement position) { knownRemoteUsesValues(position).stream() .map(CodeCompletion::splitRemoteUses) .flatMap(Optional::stream) - .forEach(uses -> result.putIfAbsent(uses.base(), "Known remote action or reusable workflow")); + .forEach(uses -> result.putIfAbsent(uses.base(), GitHubWorkflowBundle.message("completion.uses.remote.known"))); return result; } @@ -445,13 +465,13 @@ private static void handleFourthItem(final String[] cbi, final int i, final PsiE 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 job.", ICON_OUTPUT), - completionItemOf(FIELD_RESULT, "The result of the job.", 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 -> { 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 2d8956f..6c7e3de 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java @@ -23,13 +23,23 @@ 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; @@ -49,6 +59,8 @@ @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<>(); } @@ -101,15 +113,22 @@ 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(absolutePath -> ofNullable(state.actions.get(absolutePath)).or(() -> ofNullable(state.actions.get(usesCleaned))).orElse(null)) - .map(action -> cachedOrRefresh(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; } @@ -117,12 +136,12 @@ private GitHubAction cachedOrRefresh(final String usesValue, final String path, queueRefresh(action); return action; } - return saveNewAction(usesValue, path, isLocal, action); + return createdOrQueuedRemote(usesValue, path, isLocal, action); } private GitHubAction queueRefresh(final GitHubAction action) { - if (action != null && inFlightResolutions.add(action.usesValue())) { - resolveAsync(List.of(action)); + if (action != null) { + resolveInBackground(List.of(action)); } return action; } @@ -141,6 +160,91 @@ public CacheSummary summary() { 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(); @@ -224,22 +328,35 @@ 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(); try { - indicator.setText("Resolving " + (action.isAction() ? "action" : "workflow") + " " + action.name()); + 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); @@ -247,8 +364,7 @@ public void run(@NotNull final ProgressIndicator indicator) { }); 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; } @@ -256,6 +372,35 @@ 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)); } @@ -292,7 +437,7 @@ private static void triggerSyntaxHighlightingForActiveFiles(final Project projec } public static void resolveActionsAsync(final Collection actions) { - getActionCache().resolveAsync(actions); + getActionCache().resolveInBackground(actions); } public static GitHubAction reloadActionAsync(final Project project, final String usesValue) { @@ -328,8 +473,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) { @@ -356,6 +510,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) @@ -370,6 +540,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 index 55dc3e4..0b0efcb 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowBundle.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowBundle.java @@ -4,6 +4,11 @@ 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 @@ -11,9 +16,23 @@ public final class 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 b3f2ed9..230efe3 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java @@ -162,7 +162,7 @@ private static void outputsHandler(final AnnotationHolder holder, final PsiEleme return workflowOutputs.stream().noneMatch(value -> containsOutputReference(value, reusableOutputReference)) && !containsOutputReference(workflowText, needsOutputReference); }).forEach(output -> new SyntaxAnnotation( - "Unused [" + output.getKeyText() + "]", + GitHubWorkflowBundle.message("inspection.output.unused", output.getKeyText()), null, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.LIKE_UNUSED_SYMBOL, @@ -216,6 +216,9 @@ public static Predicate isElementWithVariables(final YAMLKeyValue pa @NotNull public static List toSimpleElements(final PsiElement element) { + if (getParent(element, FIELD_RUN).isPresent()) { + return toSimpleElementsInExpressions(element); + } final List result = new ArrayList<>(); final String text = element.getText(); int lineStart = 0; @@ -245,6 +248,36 @@ public static List toSimpleElements(final PsiElement element) { 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 List result = new ArrayList<>(); 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 9a6b7d2..f632092 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitter.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitter.java @@ -28,7 +28,7 @@ final class PluginErrorReportSubmitter extends ErrorReportSubmitter { @NotNull @Override public String getReportActionText() { - return "Report Exception"; + return GitHubWorkflowBundle.message("error.report.action"); } @Override @@ -54,23 +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)); + sb.append(URLEncoder.encode("\n\n### " + GitHubWorkflowBundle.message("error.report.runtime") + "\n", UTF_8)); final PluginDescriptor descriptor = getPluginDescriptor(); - 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.OS_NAME + " " + SystemInfo.OS_VERSION, UTF_8)); + 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/RefreshActionCacheAction.java b/src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java index 474b8d4..1e97e39 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java @@ -4,6 +4,7 @@ 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; @@ -18,6 +19,7 @@ public void actionPerformed(@NotNull final AnActionEvent event) { @Override public void update(@NotNull final AnActionEvent event) { + localize(event.getPresentation()); final GitHubActionCache.CacheSummary summary = GitHubActionCache.getActionCache().summary(); event.getPresentation().setEnabled(summary.remote() > 0); } @@ -33,4 +35,9 @@ private static void notify(final AnActionEvent event, final String content) { .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 index d32e217..a0851c9 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionProviders.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionProviders.java @@ -147,29 +147,38 @@ private static Map searchUses(final RemoteServerSettings.Server } private static Optional getJson(final RemoteServerSettings.Server server, final String url) { - 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 (!server.authorizationHeader().isBlank()) { - builder.header("Authorization", server.authorizationHeader()); - } - final HttpResponse response = CLIENT.send(builder.GET().build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); - if (response.statusCode() / 100 != 2) { + 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.of(JsonParser.parseString(response.body())); - } 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) { @@ -211,7 +220,7 @@ private static Map repoCompletionsFromJson(final JsonElement jso if (name.filter(value -> value.startsWith(prefix.repoPrefix())).isPresent()) { result.putIfAbsent( fullName.orElse(prefix.owner() + "/" + name.get()), - stringValue(object, "description").orElse("Remote repository") + stringValue(object, "description").orElse(GitHubWorkflowBundle.message("completion.remote.repository")) ); } } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java index 9647333..6252db9 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java @@ -1,7 +1,8 @@ package com.github.yunabraska.githubworkflow.services; import com.intellij.openapi.application.ApplicationManager; -import org.jetbrains.plugins.github.authentication.accounts.GHPersistentAccounts; +import org.jetbrains.plugins.github.authentication.GHAccountsUtil; +import org.jetbrains.plugins.github.authentication.accounts.GithubAccount; import java.util.LinkedHashMap; import java.util.List; @@ -45,9 +46,11 @@ public static Server defaultGitHub() { private static List jetBrainsGithubServers() { try { - final GHPersistentAccounts accounts = ApplicationManager.getApplication().getService(GHPersistentAccounts.class); - return Optional.ofNullable(accounts).stream() - .flatMap(service -> service.getAccounts().stream()) + 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(), @@ -63,6 +66,10 @@ private static List jetBrainsGithubServers() { } } + 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; diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java b/src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java index 7fcd5a2..8a89844 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java @@ -4,6 +4,7 @@ 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; @@ -17,6 +18,7 @@ public void actionPerformed(@NotNull final AnActionEvent event) { @Override public void update(@NotNull final AnActionEvent event) { + localize(event.getPresentation()); final GitHubActionCache.CacheSummary summary = GitHubActionCache.getActionCache().summary(); event.getPresentation().setEnabled(summary.suppressed() > 0); } @@ -32,4 +34,9 @@ private static void notify(final AnActionEvent event, final String content) { .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/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..33dc850 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java @@ -0,0 +1,156 @@ +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 = ""; + 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(); + } + } + } + return new Input(name, type, Boolean.parseBoolean(required), defaultValue, description); + } + + 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) { + } + + 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 index 4984110..c562639 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationProvider.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationProvider.java @@ -121,11 +121,12 @@ private static Optional parameterDoc(final YAMLKeyValue item, final 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( - (secret ? "Secret " : "Input ") + name, - renderParameter(secret ? "Secret" : "Input", name, text, action.githubUrl()), - plainParameter(secret ? "Secret" : "Input", name, text) + label + " " + name, + renderParameter(label, name, text, action.githubUrl()), + plainParameter(label, name, text) )); } @@ -141,20 +142,20 @@ private static Optional variableDoc(final PsiElement textElement, fi private static DocPayload referenceDoc(final ExpressionReferenceTarget target) { return switch (target.kind()) { - case "input" -> yamlParameterDoc("Input", target.segment().text(), target.target()); - case "secret" -> yamlParameterDoc("Secret", target.segment().text(), target.target()); - case "env" -> yamlValueDoc("Environment variable", target.segment().text(), target.target()); - case "matrix" -> yamlValueDoc("Matrix property", target.segment().text(), target.target()); + 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("Needed job", target.segment().text(), "Direct job dependency."); - case "need-output" -> outputDoc("Needed job output", target.segment().text(), target.target()); - case "job" -> simpleDoc("Reusable workflow job", target.segment().text(), "Job declared in this reusable workflow."); - case "job-output" -> outputDoc("Reusable workflow job output", target.segment().text(), target.target()); - case "service" -> yamlValueDoc("Service container", target.segment().text(), target.target()); - case "service-port" -> yamlValueDoc("Service port", target.segment().text(), target.target()); - case "container" -> yamlValueDoc("Job container", target.segment().text(), target.target()); - default -> simpleDoc("Workflow symbol", target.segment().text(), "Resolved workflow expression."); + 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")); }; } @@ -173,31 +174,35 @@ private static boolean isDirectOutput(final YAMLKeyValue output) { } private static String outputLabel(final YAMLKeyValue output) { - return getParent(output, FIELD_ON).isPresent() ? "Workflow output" : "Job 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() ? "Action" : "Reusable workflow").append(""); + html.append("

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

"); appendParagraph(html, action.description()); appendLink(html, action.githubUrl()); - appendMap(html, "Inputs", action.freshInputs()); - appendMap(html, "Outputs", action.freshOutputs()); - appendMap(html, "Secrets", action.freshSecrets()); + 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 String name, final PsiElement target) { + 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".equals(label)) + ? PsiElementHelper.getDescription(keyValue, input) : ""; return new DocPayload( label + " " + name, @@ -209,7 +214,7 @@ private static DocPayload yamlParameterDoc(final String label, final String name 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() ? "" : "

Value: " + escape(value) + "

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

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

"); return new DocPayload(label + " " + name, html, label + " " + name); } @@ -217,38 +222,41 @@ 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 = "Step " + name; + 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, "Name", value)); - stepItem.flatMap(step -> getChild(step, FIELD_USES)).flatMap(PsiElementHelper::getText).ifPresent(value -> appendDetail(html, "Uses", value)); + 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() ? "Action" : "Reusable workflow", action.displayName()); + 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, "Run", value)); - stepItem.ifPresent(step -> appendList(html, "Outputs", listStepOutputs(step).stream().map(output -> output.key()).toList())); + 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, "Description", value)); + source.flatMap(value -> value.outputDescription(outputName)).ifPresent(value -> appendLine(details, message("documentation.description.label"), value)); source.ifPresent(value -> { - appendLine(details, "Step", value.stepLabel()); - appendLine(details, "Uses", value.usesValue()); - appendLine(details, "Source", value.sourceLabel()); + 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 StringBuilder html = new StringBuilder(renderParameter("Step output", outputName, plainDetails, "")); + 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( - "Step output " + outputName, + label + " " + outputName, html.toString(), - plainParameter("Step output", outputName, plainDetails) + plainParameter(label, outputName, plainDetails) ); } @@ -268,9 +276,9 @@ private static Optional stepOutputSource(final PsiElement targ private static DocPayload outputDoc(final String label, final String name, final PsiElement target) { final StringBuilder details = new StringBuilder(); keyValueAt(target).ifPresent(output -> { - appendLine(details, "Description", getText(output, "description").orElse("")); - appendLine(details, "Value", getText(output, "value").or(() -> getText(output)).orElse("")); - outputSourceDetails(output).ifPresent(source -> appendLine(details, "Source", source)); + 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, @@ -304,23 +312,27 @@ private static void appendLine(final StringBuilder builder, final String name, f 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("github context", "github", "Information about the current workflow run and event.")); - case "gitea" -> Optional.of(simpleDoc("gitea context", "gitea", "Gitea-compatible alias for the GitHub Actions context.")); - case "inputs" -> Optional.of(simpleDoc("inputs context", "inputs", "Workflow, dispatch, or action inputs available here.")); - case "secrets" -> Optional.of(simpleDoc("secrets context", "secrets", "Secret values available to this workflow or reusable workflow call.")); - case "env" -> Optional.of(simpleDoc("env context", "env", "Environment variables visible at this location.")); - case "matrix" -> Optional.of(simpleDoc("matrix context", "matrix", "Matrix values for the current job.")); - case "steps" -> Optional.of(simpleDoc("steps context", "steps", "Previous steps in the current job, including outputs and status.")); - case "needs" -> Optional.of(simpleDoc("needs context", "needs", "Direct job dependencies and their outputs/results.")); - case "jobs" -> Optional.of(simpleDoc("jobs context", "jobs", "Reusable workflow jobs and outputs.")); - case "outputs" -> Optional.of(simpleDoc("outputs", "outputs", "Output values exposed by this step or job.")); - case "result" -> Optional.of(simpleDoc("result", "result", "Job result: success, failure, cancelled, or skipped.")); - case "outcome" -> Optional.of(simpleDoc("outcome", "outcome", "Step result before continue-on-error is applied.")); - case "conclusion" -> Optional.of(simpleDoc("conclusion", "conclusion", "Step result after continue-on-error is applied.")); + 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); @@ -452,7 +464,9 @@ private String sourceLabel() { if (action == null) { return ""; } - final String kind = action.isAction() ? "External action" : "Reusable workflow"; + 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) 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..6fbe202 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java @@ -0,0 +1,464 @@ +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.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); + } + + 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(); + } + + 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 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 { + 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 String responseSummary(final HttpResponse response) { + final String body = Optional.ofNullable(response.body()).orElse("").strip(); + if (body.isEmpty()) { + return ""; + } + final String contentType = response.headers() + .firstValue("Content-Type") + .orElse("") + .toLowerCase(); + if (contentType.contains("text/html") || body.startsWith(" response) { + if (response.statusCode() != 403 && response.statusCode() != 429) { + return false; + } + if (response.headers() + .firstValue("x-ratelimit-remaining") + .map(String::trim) + .filter("0"::equals) + .isPresent()) { + return true; + } + return Optional.ofNullable(response.body()) + .map(body -> body.toLowerCase(Locale.ROOT)) + .filter(body -> body.contains("rate limit")) + .isPresent(); + } + + private static boolean needsAccountAction(final HttpResponse response) { + if (response.statusCode() == 401 || response.statusCode() == 429) { + return true; + } + if (response.statusCode() != 403) { + return false; + } + return !mustHaveAdminRights(response) || rateLimitExceeded(response); + } + + 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 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; + } + + 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)); + } + } + + 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 JobStatus(long id, String name, String status, String conclusion, String htmlUrl) { + } + + 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..748938b --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java @@ -0,0 +1,795 @@ +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.ProcessHandler; +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.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.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; + +/** + * 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 ProcessHandler 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 Timer animationTimer; + + WorkflowRunConsoleTabs(final Project project, final @Nullable Executor executor, final ProcessHandler 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 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))); + + final JPanel detailPanel = new JPanel(new BorderLayout()); + detailPanel.add(createdProgress, 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; + } + 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 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); + return; + } + selectedEntry = entry; + replay(entry); + } + + 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 && !workflow.completed()); + progress.setMaximum(Math.max(1, total)); + progress.setValue(Math.min(completed, Math.max(1, total))); + progress.setVisible(total > 0 || !workflow.completed()); + } + updateAnimationTimer(); + }); + } + + 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() + && (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); + } + + 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 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 (jobs.values().stream().anyMatch(JobNode::failed)) { + return AllIcons.RunConfigurations.TestState.Red2; + } + if (jobs.values().stream().anyMatch(JobNode::skipped)) { + return AllIcons.RunConfigurations.TestState.Yellow2; + } + return AllIcons.RunConfigurations.TestState.Green2; + } + + @Override + public List snapshot() { + synchronized (lock) { + return List.copyOf(output); + } + } + + private boolean completed() { + return !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::failed)) { + return AllIcons.RunConfigurations.TestState.Red2; + } + if (children.stream().anyMatch(JobNode::skipped)) { + return AllIcons.RunConfigurations.TestState.Yellow2; + } + return children.isEmpty() ? AllIcons.RunConfigurations.TestNotRan : AllIcons.RunConfigurations.TestState.Green2; + } + + @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 = 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 = 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".equals(conclusion)) { + return AllIcons.RunConfigurations.TestTerminated; + } + return successful(conclusion) ? AllIcons.RunConfigurations.TestState.Green2 : AllIcons.RunConfigurations.TestState.Red2; + } + 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); + } + + private boolean skipped() { + return completed() && ("skipped".equals(conclusion) || "neutral".equals(conclusion)); + } + + 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/WorkflowRunJobConsole.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java new file mode 100644 index 0000000..99ec990 --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java @@ -0,0 +1,39 @@ +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 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/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..0e2ec5f --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java @@ -0,0 +1,493 @@ +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.util.LinkedHashMap; +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.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +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 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); + } + + @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); + } + poll(id); + terminate(stopping.get() ? 1 : 0); + } catch (final IOException | RuntimeException exception) { + if (exception instanceof WorkflowRunClient.WorkflowRunHttpException httpException && httpException.accountActionRecommended()) { + notifyAuthenticationHelp(); + } + stderr(exception.getMessage() + "\n"); + terminate(1); + } catch (final InterruptedException exception) { + Thread.currentThread().interrupt(); + if (!stopping.get()) { + stderr(GitHubWorkflowBundle.message("workflow.run.interrupted") + "\n"); + } + terminate(1); + } + } + + 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 void 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; + } + streamJobLogs(id, jobLogs, false); + TimeUnit.MILLISECONDS.sleep(pollSettings.statusPollMillis()); + } + } + + 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(); + } + + private void terminate(final int exitCode) { + if (terminated.compareAndSet(false, true)) { + WorkflowRunTracker.getInstance(project).unregister(request.workflowPath(), this); + 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..3fb3cfd --- /dev/null +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java @@ -0,0 +1,94 @@ +package com.github.yunabraska.githubworkflow.services; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SettingsEditor; +import org.jetbrains.annotations.NotNull; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +/** + * 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 JTextArea inputs = new JTextArea(8, 48); + + 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); + + inputs.setLineWrap(false); + final JPanel inputPanel = new JPanel(new BorderLayout(4, 4)); + inputPanel.setBorder(BorderFactory.createTitledBorder(GitHubWorkflowBundle.message("workflow.run.inputs.title"))); + inputPanel.add(new JScrollPane(inputs), 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()); + inputs.setText(configuration.inputsText()); + } + + @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(inputs.getText()); + } + + @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); + } +} 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/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index d2d8962..6597e9a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -31,6 +31,10 @@ implementationClass="com.github.yunabraska.githubworkflow.services.WorkflowDocumentationProvider" order="before YamlJsonSchemaDocumentationProvider"/> + + + @@ -39,6 +43,12 @@ + + + diff --git a/src/main/resources/messages/GitHubWorkflowBundle.properties b/src/main/resources/messages/GitHubWorkflowBundle.properties index eb3599b..9066d4b 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle.properties @@ -11,3 +11,248 @@ action.GitHubWorkflow.ClearActionCache.description=Clear cached GitHub Actions a 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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow 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 for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.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.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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ar.properties b/src/main/resources/messages/GitHubWorkflowBundle_ar.properties index 90f89af..af18553 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ar.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ar.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=مسح بيانات GitHub Ac notification.cache.cleared=تم مسح {0} إدخالات من ذاكرة GitHub Workflow. notification.cache.refresh.started=يتم تحديث {0} إدخالات بعيدة من ذاكرة GitHub Workflow. notification.warnings.restored=تمت استعادة التحذيرات لـ {0} إدخالات GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=احذف {0} غير صالح [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=بدّل التحذيرات [{0}] لـ [{1}] +inspection.warning.on=تشغيل +inspection.warning.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.action.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.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}) +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=الكاش: {0} إدخالات، {1} محلولة، {2} بعيدة، {3} قديمة، {4} مكتومة. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_cs.properties b/src/main/resources/messages/GitHubWorkflowBundle_cs.properties index 9f8835a..ed8bb4d 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_cs.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_cs.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Vymaže uložená metadata Gi notification.cache.cleared=Vymazáno {0} položek cache GitHub Workflow. notification.cache.refresh.started=Obnovuje se {0} vzdálených položek cache GitHub Workflow. notification.warnings.restored=Varování obnovena pro {0} položek GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Smazat neplatné {0} [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Přepnout varování [{0}] pro [{1}] +inspection.warning.on=zapnuto +inspection.warning.off=vypnuto +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.action.reload=Znovu načíst [{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.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}) +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} položek, {1} vyřešeno, {2} vzdálené, {3} zastaralé, {4} ztlumené. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_de.properties b/src/main/resources/messages/GitHubWorkflowBundle_de.properties index 95dfa9f..cee709e 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_de.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_de.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Zwischengespeicherte Metadate notification.cache.cleared={0} zwischengespeicherte GitHub-Workflow-Einträge gelöscht. notification.cache.refresh.started={0} zwischengespeicherte entfernte GitHub-Workflow-Einträge werden aktualisiert. notification.warnings.restored=Warnungen für {0} GitHub-Workflow-Einträge wiederhergestellt. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Ungültige {0} löschen [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Warnungen [{0}] für [{1}] umschalten +inspection.warning.on=an +inspection.warning.off=aus +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.action.reload=Neu laden [{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.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}) +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} Einträge, {1} gelöst, {2} remote, {3} veraltet, {4} stumm. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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=erledigt +workflow.run.tree.failed=fehlgeschlagen +workflow.run.tree.skipped=uebersprungen +workflow.run.tree.warn=Warnung +workflow.run.tree.err=Fehler diff --git a/src/main/resources/messages/GitHubWorkflowBundle_es.properties b/src/main/resources/messages/GitHubWorkflowBundle_es.properties index 90dd9a2..d8b7b4f 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_es.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_es.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Borra los metadatos almacenad notification.cache.cleared=Se borraron {0} entradas en caché de GitHub Workflow. notification.cache.refresh.started=Actualizando {0} entradas remotas en caché de GitHub Workflow. notification.warnings.restored=Advertencias restauradas para {0} entradas de GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Eliminar {0} no válido [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Cambiar avisos [{0}] para [{1}] +inspection.warning.on=activado +inspection.warning.off=desactivado +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.action.reload=Recargar [{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.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}) +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=Caché: {0} entradas, {1} resueltas, {2} remotas, {3} obsoletas, {4} silenciadas. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_fr.properties b/src/main/resources/messages/GitHubWorkflowBundle_fr.properties index f5bdf45..804401b 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_fr.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_fr.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Supprime les métadonnées mi notification.cache.cleared={0} entrées GitHub Workflow en cache supprimées. notification.cache.refresh.started=Actualisation de {0} entrées distantes GitHub Workflow en cache. notification.warnings.restored=Avertissements restaurés pour {0} entrées GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Supprimer {0} invalide [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Basculer les avertissements [{0}] pour [{1}] +inspection.warning.on=activé +inspection.warning.off=désactivé +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.action.reload=Recharger [{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.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}) +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} entrées, {1} résolues, {2} distantes, {3} expirées, {4} muettes. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_hi.properties b/src/main/resources/messages/GitHubWorkflowBundle_hi.properties index 31e72d6..c578d26 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_hi.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_hi.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=कैश किए गए Gi notification.cache.cleared={0} GitHub Workflow cache entries साफ़ किए गए। notification.cache.refresh.started={0} remote GitHub Workflow cache entries रीफ़्रेश हो रहे हैं। notification.warnings.restored={0} GitHub Workflow entries के warnings वापस लाए गए। + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=अमान्य {0} हटाएं [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=[{1}] के लिए चेतावनियां [{0}] करें +inspection.warning.on=चालू +inspection.warning.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.action.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.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}) +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=कैश: {0} एंट्री, {1} हल, {2} रिमोट, {3} पुरानी, {4} शांत. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_id.properties b/src/main/resources/messages/GitHubWorkflowBundle_id.properties index ca4285f..44b17a0 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_id.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_id.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Menghapus metadata GitHub Act notification.cache.cleared={0} entri cache GitHub Workflow dihapus. notification.cache.refresh.started=Menyegarkan {0} entri cache GitHub Workflow jarak jauh. notification.warnings.restored=Peringatan dipulihkan untuk {0} entri GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Hapus {0} tidak valid [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Alihkan peringatan [{0}] untuk [{1}] +inspection.warning.on=aktif +inspection.warning.off=nonaktif +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.action.reload=Muat ulang [{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.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}) +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} entri, {1} selesai, {2} remote, {3} basi, {4} dibisukan. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_it.properties b/src/main/resources/messages/GitHubWorkflowBundle_it.properties index e7daa1a..a23b094 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_it.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_it.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Svuota i metadati in cache di notification.cache.cleared=Rimosse {0} voci dalla cache di GitHub Workflow. notification.cache.refresh.started=Aggiornamento di {0} voci remote nella cache di GitHub Workflow. notification.warnings.restored=Avvisi ripristinati per {0} voci GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Elimina {0} non valido [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Attiva/disattiva avvisi [{0}] per [{1}] +inspection.warning.on=attivo +inspection.warning.off=disattivo +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.action.reload=Ricarica [{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.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}) +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} voci, {1} risolte, {2} remote, {3} obsolete, {4} silenziate. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ja.properties b/src/main/resources/messages/GitHubWorkflowBundle_ja.properties index 4c5ee22..bbaa771 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ja.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ja.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=キャッシュされた GitH notification.cache.cleared={0} 件の GitHub Workflow キャッシュエントリを消去しました。 notification.cache.refresh.started={0} 件の GitHub Workflow リモートキャッシュエントリを更新しています。 notification.warnings.restored={0} 件の GitHub Workflow エントリの警告を復元しました。 + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=無効な {0} を削除 [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=[{1}] の警告を [{0}] に切り替え +inspection.warning.on=オン +inspection.warning.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.action.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.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}) +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=キャッシュ: {0} 件、解決済み {1}、リモート {2}、古い {3}、ミュート {4}。 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ko.properties b/src/main/resources/messages/GitHubWorkflowBundle_ko.properties index 2b720ac..208ac78 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ko.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ko.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=캐시된 GitHub Actions 및 notification.cache.cleared=GitHub Workflow 캐시 항목 {0}개를 지웠습니다. notification.cache.refresh.started=GitHub Workflow 원격 캐시 항목 {0}개를 새로 고치는 중입니다. notification.warnings.restored=GitHub Workflow 항목 {0}개의 경고를 복원했습니다. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=잘못된 {0} 삭제 [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=[{1}] 경고 [{0}] 전환 +inspection.warning.on=켜짐 +inspection.warning.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.action.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.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}) +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=캐시: {0}개, 해결 {1}, 원격 {2}, 오래됨 {3}, 숨김 {4}. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_nl.properties b/src/main/resources/messages/GitHubWorkflowBundle_nl.properties index 5928dcd..659536c 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_nl.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_nl.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Wist gecachte metadata van Gi notification.cache.cleared={0} gecachte GitHub Workflow-items gewist. notification.cache.refresh.started={0} externe gecachte GitHub Workflow-items worden vernieuwd. notification.warnings.restored=Waarschuwingen hersteld voor {0} GitHub Workflow-items. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Ongeldige {0} verwijderen [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Waarschuwingen [{0}] schakelen voor [{1}] +inspection.warning.on=aan +inspection.warning.off=uit +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.action.reload=Opnieuw laden [{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.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}) +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} items, {1} opgelost, {2} extern, {3} verlopen, {4} gedempt. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_pl.properties b/src/main/resources/messages/GitHubWorkflowBundle_pl.properties index 3a2cf81..207b936 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_pl.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_pl.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Czyści zapisane metadane Git notification.cache.cleared=Wyczyszczono {0} wpisów pamięci podręcznej GitHub Workflow. notification.cache.refresh.started=Odświeżanie {0} zdalnych wpisów pamięci podręcznej GitHub Workflow. notification.warnings.restored=Przywrócono ostrzeżenia dla {0} wpisów GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Usuń nieprawidłowe {0} [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Przełącz ostrzeżenia [{0}] dla [{1}] +inspection.warning.on=włączone +inspection.warning.off=wyłączone +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.action.reload=Przeładuj [{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.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}) +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} wpisów, {1} rozwiązane, {2} zdalne, {3} stare, {4} wyciszone. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties b/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties index d023a43..d160b45 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Limpa metadados em cache de a notification.cache.cleared={0} entradas em cache do GitHub Workflow foram limpas. notification.cache.refresh.started=Atualizando {0} entradas remotas em cache do GitHub Workflow. notification.warnings.restored=Avisos restaurados para {0} entradas do GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Excluir {0} inválido [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Alternar avisos [{0}] para [{1}] +inspection.warning.on=ligado +inspection.warning.off=desligado +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.action.reload=Recarregar [{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.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}) +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} entradas, {1} resolvidas, {2} remotas, {3} antigas, {4} silenciadas. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ru.properties b/src/main/resources/messages/GitHubWorkflowBundle_ru.properties index 342c501..d1797be 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ru.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ru.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Очищает кэширов notification.cache.cleared=Очищено записей кэша GitHub Workflow: {0}. notification.cache.refresh.started=Обновляются удалённые записи кэша GitHub Workflow: {0}. notification.warnings.restored=Предупреждения восстановлены для записей GitHub Workflow: {0}. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Удалить недопустимый {0} [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Переключить предупреждения [{0}] для [{1}] +inspection.warning.on=вкл +inspection.warning.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.action.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.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}) +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=Кэш: {0} записей, {1} решено, {2} удаленных, {3} устаревших, {4} скрытых. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_sv.properties b/src/main/resources/messages/GitHubWorkflowBundle_sv.properties index 72a4fc6..ed3702c 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_sv.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_sv.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Rensar cachade metadata för notification.cache.cleared=Rensade {0} cachade GitHub Workflow-poster. notification.cache.refresh.started=Uppdaterar {0} cachade fjärrposter för GitHub Workflow. notification.warnings.restored=Varningar återställda för {0} GitHub Workflow-poster. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Ta bort ogiltig {0} [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Växla varningar [{0}] för [{1}] +inspection.warning.on=på +inspection.warning.off=av +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.action.reload=Läs om [{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.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}) +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} poster, {1} lösta, {2} fjärr, {3} gamla, {4} tystade. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_th.properties b/src/main/resources/messages/GitHubWorkflowBundle_th.properties index 468e1e9..844ea7c 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_th.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_th.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=ล้างเมตาด notification.cache.cleared=ล้างรายการแคช GitHub Workflow แล้ว {0} รายการ notification.cache.refresh.started=กำลังรีเฟรชรายการแคช GitHub Workflow ระยะไกล {0} รายการ notification.warnings.restored=คืนค่าคำเตือนสำหรับรายการ GitHub Workflow แล้ว {0} รายการ + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=ลบ {0} ที่ไม่ถูกต้อง [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=สลับคำเตือน [{0}] สำหรับ [{1}] +inspection.warning.on=เปิด +inspection.warning.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.action.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.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}) +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=แคช: {0} รายการ, แก้แล้ว {1}, ระยะไกล {2}, เก่า {3}, ปิดเสียง {4}. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_tr.properties b/src/main/resources/messages/GitHubWorkflowBundle_tr.properties index 50d7025..e3f33f1 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_tr.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_tr.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Önbelleğe alınmış GitHub notification.cache.cleared={0} GitHub Workflow önbellek kaydı temizlendi. notification.cache.refresh.started={0} uzak GitHub Workflow önbellek kaydı yenileniyor. notification.warnings.restored={0} GitHub Workflow kaydı için uyarılar geri yüklendi. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Geçersiz {0} sil [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=[{1}] için uyarıları [{0}] değiştir +inspection.warning.on=açık +inspection.warning.off=kapalı +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.action.reload=Yeniden yükle [{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.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}) +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=Önbellek: {0} kayıt, {1} çözüldü, {2} uzak, {3} eski, {4} sessiz. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_uk.properties b/src/main/resources/messages/GitHubWorkflowBundle_uk.properties index 8bb8f39..422c58f 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_uk.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_uk.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Очищає кешовані notification.cache.cleared=Очищено {0} кешованих записів GitHub Workflow. notification.cache.refresh.started=Оновлюється {0} віддалених кешованих записів GitHub Workflow. notification.warnings.restored=Попередження відновлено для {0} записів GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Видалити недійсний {0} [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Перемкнути попередження [{0}] для [{1}] +inspection.warning.on=увімкнено +inspection.warning.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.action.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.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}) +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=Кеш: {0} записів, {1} вирішено, {2} віддалених, {3} застарілих, {4} приглушених. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_vi.properties b/src/main/resources/messages/GitHubWorkflowBundle_vi.properties index 2377b05..06b2567 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_vi.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_vi.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=Xóa metadata đã cache củ notification.cache.cleared=Đã xóa {0} mục cache GitHub Workflow. notification.cache.refresh.started=Đang làm mới {0} mục cache GitHub Workflow từ xa. notification.warnings.restored=Đã khôi phục cảnh báo cho {0} mục GitHub Workflow. + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=Xóa {0} không hợp lệ [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=Bật/tắt cảnh báo [{0}] cho [{1}] +inspection.warning.on=bật +inspection.warning.off=tắt +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.action.reload=Tải lại [{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.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}) +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} mục, {1} đã giải quyết, {2} remote, {3} cũ, {4} đã tắt. 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties b/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties index ab5fa6c..e3da0bc 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties @@ -11,3 +11,250 @@ action.GitHubWorkflow.ClearActionCache.description=清除已缓存的 GitHub Act notification.cache.cleared=已清除 {0} 个 GitHub Workflow 缓存条目。 notification.cache.refresh.started=正在刷新 {0} 个 GitHub Workflow 远程缓存条目。 notification.warnings.restored=已恢复 {0} 个 GitHub Workflow 条目的警告。 + +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 GitHub workflow run +workflow.run.gutter.stop.text=Stop GitHub Workflow Run +workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +workflow.run.auth.settings=Settings > Version Control > GitHub +workflow.cache.progress.title=Resolving GitHub actions +workflow.cache.progress.text=Resolving {0} {1} +inspection.action.delete.invalid=删除无效 {0} [{1}] +inspection.action.update.major=Update action [{0}] to [{1}] +inspection.warning.toggle=为 [{1}] 切换警告 [{0}] +inspection.warning.on=开 +inspection.warning.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.action.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.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}) +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=缓存: {0} 项,{1} 已解析,{2} 远程,{3} 过期,{4} 已静音。 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.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 + +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. +workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. +workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. +workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. +workflow.run.cancel.failed=Cancel failed: {0} +workflow.run.interrupted=Interrupted. +workflow.run.link=Run: {0} +workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. +workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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.overview=Workflow run {0} {1}/{2} done, {3} running +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 +inspection.output.unused=Unused [{0}] +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 +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.job.fallbackName=Job {0} +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.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. +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. +workflow.log.command=run: +workflow.log.warning=warning: +workflow.log.error=error: +workflow.cache.kind.action=action +workflow.cache.kind.workflow=workflow +inspection.parameter.input=input +inspection.parameter.secret=secret +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 diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java b/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java index a76d2e9..274a34c 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java @@ -7,6 +7,7 @@ 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; @@ -155,11 +156,12 @@ protected final void clickGutterActionContaining(final String text) { .findFirst() .orElseThrow(() -> new AssertionError("Missing gutter action containing [" + text + "], found " + tooltips)); final AnAction action = renderer.getClickAction(); - if (action == null) { + final AnAction resolvedAction = action == null ? popupActionContaining(renderer, text) : action; + if (resolvedAction == null) { throw new AssertionError("Gutter action has no click action [" + text + "]"); } - action.actionPerformed(AnActionEvent.createEvent( - action, + resolvedAction.actionPerformed(AnActionEvent.createEvent( + resolvedAction, DataContext.EMPTY_CONTEXT, new Presentation(), "GithubWorkflowPluginTest", @@ -168,6 +170,26 @@ protected final void clickGutterActionContaining(final String text) { )); } + 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)) 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 51a3058..1ea9629 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubActionCacheTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubActionCacheTest.java @@ -6,6 +6,9 @@ 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; @@ -95,6 +98,38 @@ public void testClearRemovesCachedActionsAndReturnsEmptySummary() throws IOExcep 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()) @@ -113,6 +148,42 @@ public void testRestoreWarningsClearsActionInputAndOutputSuppressions() throws I 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, """ 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/LocalizationResourcesTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java index 504a2c1..d19a987 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java @@ -7,6 +7,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Locale; import java.util.Properties; import static org.assertj.core.api.Assertions.assertThat; @@ -71,6 +72,52 @@ public void testLocaleBundleValuesAreNotBlank() throws IOException { } } + @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 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"); + } + private static Properties loadBundle(final String suffix) throws IOException { final String path = BUNDLE_PATH + suffix + ".properties"; try (InputStream stream = LocalizationResourcesTest.class.getClassLoader().getResourceAsStream(path)) { diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java index 2729f76..590364a 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java @@ -2,7 +2,12 @@ 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; @@ -43,4 +48,35 @@ public void testRestoreActionWarningsActionIsRegisteredAndLocalized() { 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 index d153d22..cffd72f 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java @@ -1238,4 +1238,17 @@ public void testRunCompletionSuggestsDefaultEnvironmentVariables() { - run: echo "$" """)).contains("GITHUB_TRIGGERING_ACTOR", "GITHUB_REPOSITORY_OWNER_ID", "RUNNER_ENVIRONMENT"); } + + 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"); + } } 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..7519856 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java @@ -0,0 +1,54 @@ +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" + jobs: + build: + runs-on: ubuntu-latest + """)).containsExactly( + new WorkflowDispatchInputs.Input("ref", "string", true, "main", "Branch"), + new WorkflowDispatchInputs.Input("dry_run", "boolean", false, "true", "") + ); + } + + public void testDefaultsTextUsesKeyValueLines() { + final WorkflowDispatchInputs inputs = new WorkflowDispatchInputs(); + + assertThat(inputs.defaultsText(""" + on: + workflow_dispatch: + inputs: + ref: + default: main + """)).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/WorkflowGutterActionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java index f440f76..d9bc10c 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java @@ -1,8 +1,16 @@ 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; @@ -102,4 +110,136 @@ public void testReloadRemoteActionGutterActionUsesResolverBoundaryWithoutSleepin getActionCache().useActionResolverForTests(previous); } } + + public void testResolvedActionShowsOneCombinedGutterIconForAllActionFixes() { + 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).singleElement() + .satisfies(tooltip -> assertThat(tooltip) + .contains("Reload [owner/tool]") + .contains("Toggle warnings [off] for [owner/tool]") + .contains("Update action [owner/tool@v1] to [owner/tool@v2]")); + } + + 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 GitHub 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/WorkflowQuickFixTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java index 978abbb..4f66e54 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java @@ -1,6 +1,7 @@ package com.github.yunabraska.githubworkflow.services; import java.util.Map; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -22,6 +23,32 @@ public void testUnknownActionInputProvidesDeleteQuickFix() { """)).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 [aus] für [owner/tool]")) + .anyMatch(text -> text.contains("Ungültige input löschen [wrong-input]")); + } finally { + settings.languageTag(previousLanguage); + } + } + public void testUnknownWorkflowInputProvidesReplaceQuickFix() { assertThat(quickFixTexts(""" name: Quick Fixes @@ -38,6 +65,23 @@ public void testUnknownWorkflowInputProvidesReplaceQuickFix() { """)).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 @@ -271,4 +315,35 @@ public void testReplaceQuickFixUpdatesJobServicePortReference() { 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/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..1b482e0 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java @@ -0,0 +1,381 @@ +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 String logs = client.logs(request, 42); + + assertThat(status.completed()).isTrue(); + assertThat(status.conclusion()).isEqualTo("success"); + assertThat(cancel.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/jobs", + "/repos/acme/tool/actions/jobs/100/logs" + ); + } + } + + 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 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 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); + 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 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("/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("/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/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..de527b0 --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java @@ -0,0 +1,615 @@ +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 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) + ); + 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 for GitHub workflow run 42."); + } + + 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 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 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 Map output = new HashMap<>(); + + @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; + } + + private void append(final WorkflowRunClient.JobStatus job, final String text) { + output.computeIfAbsent(job.id(), ignored -> new StringBuilder()).append(text); + } + + private String output(final long jobId) { + return output.getOrDefault(jobId, new StringBuilder()).toString(); + } + } +} From 41a70fe9181c3c0dd8237427100fce37b986d3dd Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 00:19:21 +0200 Subject: [PATCH 07/17] Harden workflow dispatch UX --- README.md | 3 - doc/spec/ux-dx-gaps.md | 57 ------ .../services/CodeCompletion.java | 174 +++++++++++++++++- .../services/WorkflowDispatchInputs.java | 74 +++++++- .../services/WorkflowRunClient.java | 25 +++ .../services/WorkflowRunConsoleTabs.java | 128 +++++++++++-- .../services/WorkflowRunJobConsole.java | 12 ++ .../services/WorkflowRunProcessHandler.java | 65 ++++++- .../services/WorkflowRunSettingsEditor.java | 65 ++++++- .../messages/GitHubWorkflowBundle.properties | 7 + .../GitHubWorkflowBundle_ar.properties | 7 + .../GitHubWorkflowBundle_cs.properties | 7 + .../GitHubWorkflowBundle_de.properties | 7 + .../GitHubWorkflowBundle_es.properties | 7 + .../GitHubWorkflowBundle_fr.properties | 7 + .../GitHubWorkflowBundle_hi.properties | 7 + .../GitHubWorkflowBundle_id.properties | 7 + .../GitHubWorkflowBundle_it.properties | 7 + .../GitHubWorkflowBundle_ja.properties | 7 + .../GitHubWorkflowBundle_ko.properties | 7 + .../GitHubWorkflowBundle_nl.properties | 7 + .../GitHubWorkflowBundle_pl.properties | 7 + .../GitHubWorkflowBundle_pt_BR.properties | 7 + .../GitHubWorkflowBundle_ru.properties | 7 + .../GitHubWorkflowBundle_sv.properties | 7 + .../GitHubWorkflowBundle_th.properties | 7 + .../GitHubWorkflowBundle_tr.properties | 7 + .../GitHubWorkflowBundle_uk.properties | 7 + .../GitHubWorkflowBundle_vi.properties | 7 + .../GitHubWorkflowBundle_zh_CN.properties | 7 + .../services/WorkflowCompletionTest.java | 92 +++++++++ .../services/WorkflowDispatchInputsTest.java | 16 +- .../services/WorkflowHighlightingTest.java | 14 ++ .../services/WorkflowRunClientTest.java | 9 + .../WorkflowRunProcessHandlerTest.java | 108 ++++++++++- .../WorkflowRunSettingsEditorTest.java | 25 +++ 36 files changed, 911 insertions(+), 103 deletions(-) delete mode 100644 doc/spec/ux-dx-gaps.md create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditorTest.java diff --git a/README.md b/README.md index 3b02834..a0e3cc7 100644 --- a/README.md +++ b/README.md @@ -83,9 +83,6 @@ take a while. The task also repairs stale custom color-scheme references in the 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. -Current UX/DX gaps are tracked in [UX/DX Gaps](doc/spec/ux-dx-gaps.md); editor behavior coverage is tracked in -[Editor Test Matrix](doc/spec/editor-test-matrix.md). - ## Dependencies This plugin depends on: diff --git a/doc/spec/ux-dx-gaps.md b/doc/spec/ux-dx-gaps.md deleted file mode 100644 index 1211de3..0000000 --- a/doc/spec/ux-dx-gaps.md +++ /dev/null @@ -1,57 +0,0 @@ -# UX/DX Gaps - -## UX - -- Remote action resolution uses public GitHub plus GitHub Enterprise accounts from JetBrains GitHub settings. Tokens are - only sent to matching GitHub hosts, and anonymous access is the fallback. There is no plugin-owned server settings UI. -- Unresolved remote actions now mention common failure modes directly in the editor, including account access, private - repository permissions, rate limits, missing refs, and missing metadata. Workflow dispatch failures report 401/rate - limit failures with a GitHub settings hint. Per-status editor diagnostics for remote metadata are still missing. -- Cache controls are exposed as IDE actions discoverable through Find Action and, where the classic Tools menu is - visible, under `Tools > GitHub Workflow`. Settings also expose a cache inspector with per-entry review, selected/all - deletion, import/export, and plugin cache size. -- Workflow run logs use one Run tool-window content tab with a JUnit-style execution tree and a detail console so jobs, - timings, warnings, and failures read like normal IDE runs instead of a tab parade. -- Suppressed action/input/output warnings can be restored through the `Restore Action Warnings` IDE action. A detailed - suppression review panel is still missing. -- Link navigation now covers resolved local/remote `uses` from github.com and GitHub Enterprise, job IDs, `needs`, - inputs, secrets, envs, matrix keys, - job service IDs/ports, step output references, and reusable workflow job outputs where a local target exists. Remote - metadata internals still need explicit data sources. -- Quick documentation now covers resolved `uses`, workflow/action parameters, expression variables, and common context - segments. The remaining gap is richer rendered Markdown from remote action README files. -- Workflow execution now has a first Run Configuration implementation for `workflow_dispatch`: context-created GitHub - Workflow configurations defaulting to the current Git branch, a gutter play/stop action on `workflow_dispatch`, - `key=value` dispatch inputs, account-first authentication through JetBrains GitHub settings with successful-token reuse - during polling, environment-token fallback before anonymous access, Run tool window status output, one JUnit-style - workflow tree with grouped jobs, selected-node log output, job URLs in the detail console, a thin progress bar, elapsed - job timing, and Stop mapped to canceling the remote workflow run. The workflow tree uses test-style status icons and - compact GitHub timestamps/log commands into named blocks with four-digit line numbers, `run:` command lines, ANSI - cleanup, and warning/error console colors. - Remaining execution UX gaps are richer input widgets for typed choices/booleans, passive run lists for workflows - triggered by non-dispatch events, historical duration estimates, and deeper GitHub-style log rendering such as step - folding and clickable SHAs. -- Plugin/action/cache-control labels, settings, run-console status text, issue reporting, editor inspections, quick fixes, - completion detail text, and workflow documentation labels use resource bundles with top-20 locale files. Remaining - localization work is mostly native translation review for newly added fallback-English keys. -- Highlighting, references, styling, documentation, and completion now have focused fixture tests through YAML editor - entrypoints. GitHub context/default-env metadata is generated from checked-in documentation snapshots. - -## DX - -- Schema updates should be a separate manual task, not a test side effect. -- Verifier reports are now generated, but release decisions still need a documented review checklist. -- JaCoCo reporting is present but currently not reliable with the IntelliJ fixture/runtime class loading; coverage gates - stay off until that is made truthful. -- Editor fixture coverage has been split into isolated highlighting, reference, and completion tests. The remaining - matrix is tracked in [Editor Test Matrix](editor-test-matrix.md). -- Bounded large-workflow highlighting performance coverage now runs through `./gradlew performanceTest`. -- A fake HTTP server covers remote metadata behavior deterministically. Remote reload gutter execution has a resolver - boundary and no-sleep test coverage. -- The release workflow builds, verifies, packages, and uploads the plugin zip to a GitHub release. Disposable tag/release - tests have passed; production release behavior still needs the first real main-branch release. -- Tag and GitHub release workflows are main-branch/tag gated. Marketplace publishing/signing is wired to published - releases and should stay dry-run/manual until repository secrets are confirmed. -- The tag workflow now creates a date-based plugin version commit with Kira bot identity, tags that commit, and pushes - both commit and tag. -- UI testing is not configured. The stale UI workflow was removed instead of pretending it worked. Harsh, but fair. 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 caa6298..b3aa90b 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java @@ -137,16 +137,29 @@ public void addCompletions( NodeIcon.ICON_NODE, Character.MIN_VALUE ); - } else 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, ':')); + final Optional structureCompletion = workflowTriggerStructureCompletion(parameters); + if (structureCompletion.isPresent()) { + final StructureCompletion completion = structureCompletion.get(); + addLookupElements( + resultSet.withPrefixMatcher(getDefaultPrefix(parameters)), + 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, ':')); + } } } }); @@ -191,6 +204,140 @@ private static boolean isCompletingShellField(final CompletionParameters paramet return beforeCaret.matches("\\s*" + FIELD_SHELL + "\\s*:\\s*.*"); } + private static Optional workflowTriggerStructureCompletion(final CompletionParameters parameters) { + final Optional context = yamlKeyContext(parameters); + if (context.isEmpty() || isYamlValueCompletion(context.get().currentLine())) { + return Optional.empty(); + } + final List path = context.get().path(); + 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(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(workflowOutputPropertyKeys(), ':')); + } + return Optional.empty(); + } + + private static Optional yamlKeyContext(final CompletionParameters parameters) { + final String wholeText = parameters.getOriginalFile().getText(); + final int offset = Math.min(parameters.getOffset(), wholeText.length()); + final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; + final String currentLine = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + final int currentIndent = leadingSpaces(currentLine); + final List stack = new ArrayList<>(); + wholeText.substring(0, lineStart).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 stack.isEmpty() + ? Optional.empty() + : Optional.of(new YamlKeyContext(stack.stream().map(YamlAncestor::key).toList(), currentLine)); + } + + 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 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 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 result; + } + + private 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 result; + } + private static Optional remoteUsesRef(final CompletionParameters parameters) { final String wholeText = parameters.getOriginalFile().getText(); final int offset = Math.min(parameters.getOffset(), wholeText.length()); @@ -553,6 +700,15 @@ 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 CompletionPsi(PsiElement position, int offset) { } } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java index 33dc850..e6108f5 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java @@ -67,6 +67,7 @@ private static Input readInput(final List lines, final int inputIndex, fin 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) { @@ -81,10 +82,66 @@ private static Input readInput(final List lines, final int inputIndex, fin 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); + 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) { @@ -145,7 +202,20 @@ private static String stripQuotes(final String value) { return value; } - public record Input(String name, String type, boolean required, String defaultValue, String description) { + 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) { diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java index 6fbe202..ac20750 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java @@ -98,6 +98,26 @@ public CancelResult cancel(final WorkflowRunRequest request, final long runId) t return new CancelResult(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, @@ -247,6 +267,8 @@ private static HttpRequest request( } 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(); } @@ -428,6 +450,9 @@ public boolean completed() { public record CancelResult(int statusCode, boolean accepted) { } + public record DeleteResult(int statusCode, boolean accepted) { + } + public record JobStatus(long id, String name, String status, String conclusion, String htmlUrl) { } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java index 748938b..d15c667 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java @@ -3,7 +3,6 @@ import com.intellij.execution.Executor; import com.intellij.execution.filters.TextConsoleBuilderFactory; import com.intellij.execution.process.ProcessEvent; -import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.process.ProcessListener; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.execution.ui.ConsoleView; @@ -29,6 +28,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.Icon; +import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JProgressBar; @@ -62,7 +62,7 @@ final class WorkflowRunConsoleTabs implements WorkflowRunJobConsole { private final Project project; private final @Nullable Executor executor; - private final ProcessHandler processHandler; + private final WorkflowRunProcessHandler processHandler; private final WorkflowNode workflow = new WorkflowNode(); private final ConcurrentMap jobs = new ConcurrentHashMap<>(); private final ConcurrentMap groups = new ConcurrentHashMap<>(); @@ -84,9 +84,12 @@ public void onTextAvailable(final @NotNull ProcessEvent event, final @NotNull Ke private @Nullable DefaultMutableTreeNode rootNode; private @Nullable Tree tree; private @Nullable JProgressBar progressBar; + private @Nullable JButton deleteRunButton; private @Nullable Timer animationTimer; + private volatile long terminalRunId = -1; + private volatile String terminalConclusion = ""; - WorkflowRunConsoleTabs(final Project project, final @Nullable Executor executor, final ProcessHandler processHandler) { + WorkflowRunConsoleTabs(final Project project, final @Nullable Executor executor, final WorkflowRunProcessHandler processHandler) { this.project = project; this.executor = executor; this.processHandler = processHandler; @@ -120,6 +123,36 @@ public boolean jobStderr(final WorkflowRunClient.JobStatus job, final String tex 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)); + 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) { + ApplicationManager.getApplication().invokeLater(() -> { + final JButton button = deleteRunButton; + if (button != null && terminalRunId == runId && !project.isDisposed()) { + button.setEnabled(true); + } + }); + } + @Override public void close() { if (closed.compareAndSet(false, true)) { @@ -194,8 +227,20 @@ private void attach() { createdProgress.setStringPainted(false); createdProgress.setPreferredSize(new Dimension(0, JBUI.scale(3))); + final JButton createdDeleteRunButton = new JButton(GitHubWorkflowBundle.message("workflow.run.delete.button")); + createdDeleteRunButton.setToolTipText(GitHubWorkflowBundle.message("workflow.run.delete.tooltip")); + createdDeleteRunButton.setVisible(false); + createdDeleteRunButton.addActionListener(ignored -> { + createdDeleteRunButton.setEnabled(false); + processHandler.deleteRemoteRun(); + }); + + final JPanel progressPanel = new JPanel(new BorderLayout()); + progressPanel.add(createdProgress, BorderLayout.CENTER); + progressPanel.add(createdDeleteRunButton, BorderLayout.EAST); + final JPanel detailPanel = new JPanel(new BorderLayout()); - detailPanel.add(createdProgress, BorderLayout.NORTH); + detailPanel.add(progressPanel, BorderLayout.NORTH); detailPanel.add(createdConsole.getComponent(), BorderLayout.CENTER); final OnePixelSplitter splitter = new OnePixelSplitter(false, 0.32f); @@ -223,6 +268,7 @@ private void attach() { treeModel = createdModel; tree = createdTree; progressBar = createdProgress; + deleteRunButton = createdDeleteRunButton; } refreshTree(); createdTree.setSelectionPath(new TreePath(createdRoot.getPath())); @@ -243,6 +289,13 @@ private void keepOnlyWorkflowContent(final RunnerLayoutUi layout, final Content 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; @@ -342,10 +395,15 @@ private void updateContent() { 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 && !workflow.completed()); + progress.setIndeterminate(total == 0 && !terminal()); progress.setMaximum(Math.max(1, total)); - progress.setValue(Math.min(completed, Math.max(1, total))); - progress.setVisible(total > 0 || !workflow.completed()); + progress.setValue(terminal() ? Math.max(1, total) : Math.min(completed, Math.max(1, total))); + progress.setVisible(total > 0 || !terminal()); + } + final JButton deleteButton = deleteRunButton; + if (deleteButton != null && !project.isDisposed()) { + deleteButton.setVisible(terminal() && terminalRunId > 0); + deleteButton.setEnabled(terminal() && terminalRunId > 0); } updateAnimationTimer(); }); @@ -394,6 +452,7 @@ private boolean shouldAnimate() { return !closed.get() && !project.isDisposed() && !processHandler.isProcessTerminated() + && !terminal() && (jobs.isEmpty() || jobs.values().stream().anyMatch(JobNode::running)); } @@ -416,6 +475,20 @@ 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) @@ -483,12 +556,20 @@ 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.RunConfigurations.TestState.Red2; } if (jobs.values().stream().anyMatch(JobNode::skipped)) { return AllIcons.RunConfigurations.TestState.Yellow2; } + if (jobs.isEmpty() && terminal()) { + return successful(terminalConclusion) + ? AllIcons.RunConfigurations.TestState.Green2 + : AllIcons.RunConfigurations.TestState.Red2; + } return AllIcons.RunConfigurations.TestState.Green2; } @@ -500,7 +581,7 @@ public List snapshot() { } private boolean completed() { - return !jobs.isEmpty() && jobs.values().stream().allMatch(JobNode::completed); + return terminal() || (!jobs.isEmpty() && jobs.values().stream().allMatch(JobNode::completed)); } } @@ -544,6 +625,9 @@ public Icon icon() { 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.RunConfigurations.TestState.Red2; } @@ -591,7 +675,7 @@ private JobNode(final WorkflowRunClient.JobStatus job) { this.jobId = job.id(); updateDisplayName(job); this.status = job.status(); - this.conclusion = job.conclusion(); + this.conclusion = normalizeConclusion(job.conclusion()); final long now = System.currentTimeMillis(); this.firstSeenMillis = now; updateTiming(job, now); @@ -631,7 +715,7 @@ private void append(final PrintedText text) { private void update(final WorkflowRunClient.JobStatus job) { updateDisplayName(job); status = job.status(); - conclusion = job.conclusion(); + conclusion = normalizeConclusion(job.conclusion()); updateTiming(job, System.currentTimeMillis()); } @@ -680,7 +764,7 @@ public Icon icon() { if (skipped()) { return AllIcons.RunConfigurations.TestState.Yellow2; } - if ("cancelled".equals(conclusion)) { + if (cancelled()) { return AllIcons.RunConfigurations.TestTerminated; } return successful(conclusion) ? AllIcons.RunConfigurations.TestState.Green2 : AllIcons.RunConfigurations.TestState.Red2; @@ -712,13 +796,33 @@ private boolean running() { } private boolean failed() { - return completed() && !successful(conclusion); + return completed() && !successful(conclusion) && !cancelled(); } private boolean skipped() { return completed() && ("skipped".equals(conclusion) || "neutral".equals(conclusion)); } + 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(); diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java index 99ec990..bf0d200 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java @@ -15,6 +15,18 @@ 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() { } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java index 0e2ec5f..5116448 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java @@ -36,6 +36,7 @@ public final class WorkflowRunProcessHandler extends ProcessHandler { 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 AtomicLong runId = new AtomicLong(-1); private final AtomicReference> task = new AtomicReference<>(); @@ -111,7 +112,7 @@ private void cancelRemoteRun(final long id) { stderr(GitHubWorkflowBundle.message("workflow.run.cancel.failed", exception.getMessage()) + "\n"); } } - terminate(1); + terminate(1, "cancelled"); } @Override @@ -142,20 +143,23 @@ private void runWorkflow() { if (id > 0) { runId.set(id); } - poll(id); - terminate(stopping.get() ? 1 : 0); + 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); + terminate(1, "failure"); } catch (final InterruptedException exception) { Thread.currentThread().interrupt(); if (!stopping.get()) { stderr(GitHubWorkflowBundle.message("workflow.run.interrupted") + "\n"); } - terminate(1); + terminate(1, stopping.get() ? "cancelled" : "failure"); } } @@ -182,9 +186,9 @@ private long resolveRunId(final WorkflowRunClient.DispatchResult dispatch) throw return -1; } - private void poll(final long id) throws IOException, InterruptedException { + private String poll(final long id) throws IOException, InterruptedException { if (id <= 0) { - return; + return ""; } WorkflowRunClient.RunStatus previous = new WorkflowRunClient.RunStatus(id, "", "", ""); final Map jobLogs = new LinkedHashMap<>(); @@ -196,11 +200,12 @@ private void poll(final long id) throws IOException, InterruptedException { } if (status.completed()) { streamJobLogs(id, jobLogs, true); - return; + 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 { @@ -434,9 +439,51 @@ private static boolean hasText(final String value) { return value != null && !value.isBlank(); } - private void terminate(final int exitCode) { + 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); + } + }); + } + + 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); } diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java index 3fb3cfd..adff3ff 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java @@ -2,19 +2,22 @@ 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.JScrollPane; -import javax.swing.JTextArea; 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. @@ -28,7 +31,17 @@ public final class WorkflowRunSettingsEditor extends SettingsEditor addInputRow("", "")) + .setRemoveAction(button -> removeSelectedInputRows()) + .disableUpDownActions() + .createPanel(), BorderLayout.CENTER); panel.add(inputPanel, BorderLayout.CENTER); } @@ -56,7 +72,7 @@ protected void resetEditorFrom(@NotNull final WorkflowRunConfiguration configura workflowPath.setText(configuration.workflowPath()); ref.setText(configuration.ref()); tokenEnvVar.setText(configuration.tokenEnvVar()); - inputs.setText(configuration.inputsText()); + resetInputs(configuration); } @Override @@ -67,7 +83,7 @@ protected void applyEditorTo(@NotNull final WorkflowRunConfiguration configurati .workflowPath(workflowPath.getText()) .ref(ref.getText()) .tokenEnvVar(tokenEnvVar.getText()) - .inputsText(inputs.getText()); + .inputsText(inputsText()); } @Override @@ -91,4 +107,37 @@ private static void addRow(final JPanel panel, final int row, final String label 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/resources/messages/GitHubWorkflowBundle.properties b/src/main/resources/messages/GitHubWorkflowBundle.properties index 9066d4b..d8d1f5e 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle.properties @@ -256,3 +256,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ar.properties b/src/main/resources/messages/GitHubWorkflowBundle_ar.properties index af18553..667c192 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ar.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ar.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_cs.properties b/src/main/resources/messages/GitHubWorkflowBundle_cs.properties index ed8bb4d..45ca3f0 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_cs.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_cs.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_de.properties b/src/main/resources/messages/GitHubWorkflowBundle_de.properties index cee709e..2b8a80f 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_de.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_de.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=fehlgeschlagen workflow.run.tree.skipped=uebersprungen workflow.run.tree.warn=Warnung workflow.run.tree.err=Fehler +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_es.properties b/src/main/resources/messages/GitHubWorkflowBundle_es.properties index d8b7b4f..4f28478 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_es.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_es.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_fr.properties b/src/main/resources/messages/GitHubWorkflowBundle_fr.properties index 804401b..b1ede99 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_fr.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_fr.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_hi.properties b/src/main/resources/messages/GitHubWorkflowBundle_hi.properties index c578d26..a3f3df7 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_hi.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_hi.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_id.properties b/src/main/resources/messages/GitHubWorkflowBundle_id.properties index 44b17a0..812ddf4 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_id.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_id.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_it.properties b/src/main/resources/messages/GitHubWorkflowBundle_it.properties index a23b094..790db6b 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_it.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_it.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ja.properties b/src/main/resources/messages/GitHubWorkflowBundle_ja.properties index bbaa771..ba59dbd 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ja.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ja.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ko.properties b/src/main/resources/messages/GitHubWorkflowBundle_ko.properties index 208ac78..35b8f56 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ko.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ko.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_nl.properties b/src/main/resources/messages/GitHubWorkflowBundle_nl.properties index 659536c..63322e4 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_nl.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_nl.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_pl.properties b/src/main/resources/messages/GitHubWorkflowBundle_pl.properties index 207b936..062131d 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_pl.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_pl.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties b/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties index d160b45..48b9d88 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_ru.properties b/src/main/resources/messages/GitHubWorkflowBundle_ru.properties index d1797be..c63c561 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ru.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ru.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_sv.properties b/src/main/resources/messages/GitHubWorkflowBundle_sv.properties index ed3702c..49e565b 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_sv.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_sv.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_th.properties b/src/main/resources/messages/GitHubWorkflowBundle_th.properties index 844ea7c..9b99e25 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_th.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_th.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_tr.properties b/src/main/resources/messages/GitHubWorkflowBundle_tr.properties index e3f33f1..94751ec 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_tr.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_tr.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_uk.properties b/src/main/resources/messages/GitHubWorkflowBundle_uk.properties index 422c58f..5f047ec 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_uk.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_uk.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_vi.properties b/src/main/resources/messages/GitHubWorkflowBundle_vi.properties index 06b2567..83ec0c6 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_vi.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_vi.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties b/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties index e3da0bc..43985dd 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties @@ -258,3 +258,10 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err +workflow.run.delete.button=Delete run +workflow.run.delete.tooltip=Delete this workflow run from GitHub +workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. +workflow.run.delete.requested=Deleting GitHub workflow run {0}. +workflow.run.delete.done=GitHub workflow run {0} was deleted. +workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. +workflow.run.delete.failed=Delete failed: {0} diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java index cffd72f..3c1d372 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java @@ -273,6 +273,22 @@ public void testInputsCompletionUsesWorkflowCallInputs() { """)).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 @@ -332,6 +348,82 @@ public void testJobStrategyFailFastCompletionSuggestsInputs() { """)).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 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 testBracketInputCompletionUsesWorkflowCallInputs() { assertThat(completeWorkflow(""" name: Completion diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java index 7519856..d9ca479 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java @@ -22,16 +22,23 @@ public void testParseWorkflowDispatchInputsWithDefaults() { 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("dry_run", "boolean", false, "true", ""), + new WorkflowDispatchInputs.Input("environment", "choice", false, "", "Target", java.util.List.of("dev", "prod")) ); } - public void testDefaultsTextUsesKeyValueLines() { + public void testDefaultsTextBuildsPlainKeyValueLines() { final WorkflowDispatchInputs inputs = new WorkflowDispatchInputs(); assertThat(inputs.defaultsText(""" @@ -39,7 +46,11 @@ public void testDefaultsTextUsesKeyValueLines() { workflow_dispatch: inputs: ref: + description: Branch + type: choice + required: true default: main + options: [main, "release, candidate"] """)).isEqualTo("ref=main\n"); } @@ -51,4 +62,5 @@ public void testKeyValueInputTextIgnoresCommentsAndBlankLines() { dry_run=true """)).containsEntry("ref", "main").containsEntry("dry_run", "true"); } + } diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java index 7323771..ec7f89b 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java @@ -627,6 +627,20 @@ public void testUnknownGithubContextIsHighlighted() { """); } + 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 diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java index 1b482e0..beb1312 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java @@ -53,11 +53,13 @@ public void testStatusCancelJobsAndLogsUseRunEndpoints() throws Exception { final WorkflowRunClient.RunStatus status = client.status(request, 42); final WorkflowRunClient.CancelResult cancel = client.cancel(request, 42); + 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(delete.accepted()).isTrue(); assertThat(logs).contains("== build [completed/success]", "hello from job log"); assertThat(server.requests()).contains( "/repos/acme/tool/actions/runs/42", @@ -65,6 +67,7 @@ public void testStatusCancelJobsAndLogsUseRunEndpoints() throws Exception { "/repos/acme/tool/actions/runs/42/jobs", "/repos/acme/tool/actions/jobs/100/logs" ); + assertThat(server.methods()).contains("DELETE"); } } @@ -254,6 +257,7 @@ public void testJobLogHtmlFailureIsSummarized() { private static final class FakeWorkflowRunServer implements AutoCloseable { private final HttpServer server; private final List 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; @@ -274,6 +278,7 @@ private static final class FakeWorkflowRunServer implements AutoCloseable { 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 @@ -302,6 +307,10 @@ List requests() { return List.copyOf(requests); } + List methods() { + return List.copyOf(methods); + } + List bodies() { return List.copyOf(bodies); } diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java index de527b0..81c3159 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java @@ -86,6 +86,7 @@ public void processTerminated(@NotNull final ProcessEvent event) { 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()) @@ -103,7 +104,8 @@ public void testDestroyCancelsRemoteRunAndTerminates() throws Exception { getProject(), request, client, - new WorkflowRunProcessHandler.PollSettings(1_000, 1_000, 10) + new WorkflowRunProcessHandler.PollSettings(1_000, 1_000, 10), + jobConsole ); final CountDownLatch terminated = new CountDownLatch(1); final StringBuilder output = new StringBuilder(); @@ -126,6 +128,49 @@ public void processTerminated(@NotNull final ProcessEvent event) { assertThat(cancelSeen.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(output.toString()).contains("Cancel requested for GitHub workflow run 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, "GitHub workflow run 42 was deleted.")).isTrue(); + assertThat(jobConsole.workflowOutput()).contains("Deleting GitHub workflow run 42.", "GitHub workflow run 42 was deleted."); + assertThat(jobConsole.deleted()).containsExactly(42L); } public void testProcessRoutesEachJobLogToSeparateJobConsole() throws Exception { @@ -452,6 +497,27 @@ private static HttpResponse cancellationResponseFor( 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 adminLiveLogResponseFor( final HttpRequest request, final AtomicInteger statusCalls, @@ -550,6 +616,16 @@ private static Response response(final HttpRequest request, final int status, fi 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 record Response(HttpRequest request, int statusCode, String body) implements HttpResponse { @Override public HttpHeaders headers() { @@ -579,6 +655,9 @@ public Optional sslSession() { private static final class CapturingJobConsole implements WorkflowRunJobConsole { 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) { @@ -604,6 +683,21 @@ public boolean jobLog(final WorkflowRunClient.JobStatus job, final String text) return true; } + @Override + public void workflowStatus(final String text, final boolean error) { + workflowOutput.append(text); + } + + @Override + public void runFinished(final long runId, final String conclusion) { + finished.add(runId + ":" + conclusion); + } + + @Override + public void runDeleted(final long runId) { + deleted.add(runId); + } + private void append(final WorkflowRunClient.JobStatus job, final String text) { output.computeIfAbsent(job.id(), ignored -> new StringBuilder()).append(text); } @@ -611,5 +705,17 @@ private void append(final WorkflowRunClient.JobStatus job, final String text) { private String output(final long jobId) { return output.getOrDefault(jobId, new StringBuilder()).toString(); } + + private String workflowOutput() { + return workflowOutput.toString(); + } + + private List finished() { + return List.copyOf(finished); + } + + private List deleted() { + 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); + } +} From 0558f10b51cc3abc80aaababbf856f9da91809ba Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 15:09:01 +0200 Subject: [PATCH 08/17] Polish workflow UX, docs, and localization --- doc/spec/editor-test-matrix.md | 24 +- .../helper/HighlightAnnotatorHelper.java | 14 +- .../githubworkflow/logic/Action.java | 27 +- .../githubworkflow/logic/Needs.java | 32 +- .../githubworkflow/logic/Secrets.java | 6 +- .../githubworkflow/logic/Steps.java | 3 +- .../githubworkflow/model/GitHubAction.java | 1 - .../githubworkflow/model/NodeIcon.java | 1 + .../model/SyntaxAnnotation.java | 21 +- .../services/CodeCompletion.java | 414 +++++++++-- .../services/HighlightAnnotator.java | 272 ++++++- .../WorkflowAutoPopupEnterHandler.java | 44 ++ .../WorkflowAutoPopupTypedHandler.java | 72 ++ .../WorkflowCompletionConfidence.java | 28 + .../services/WorkflowRunClient.java | 189 ++++- .../services/WorkflowRunConsoleTabs.java | 178 ++++- .../services/WorkflowRunDownloads.java | 73 ++ .../services/WorkflowRunProcessHandler.java | 137 ++++ .../services/WorkflowSyntaxSchema.java | 328 ++++++++ src/main/resources/META-INF/plugin.xml | 6 + .../messages/GitHubWorkflowBundle.properties | 209 +++++- .../GitHubWorkflowBundle_ar.properties | 695 ++++++++++------- .../GitHubWorkflowBundle_cs.properties | 687 ++++++++++------- .../GitHubWorkflowBundle_de.properties | 679 ++++++++++------- .../GitHubWorkflowBundle_es.properties | 695 ++++++++++------- .../GitHubWorkflowBundle_fr.properties | 699 +++++++++++------- .../GitHubWorkflowBundle_hi.properties | 695 ++++++++++------- .../GitHubWorkflowBundle_id.properties | 691 ++++++++++------- .../GitHubWorkflowBundle_it.properties | 691 ++++++++++------- .../GitHubWorkflowBundle_ja.properties | 697 ++++++++++------- .../GitHubWorkflowBundle_ko.properties | 693 ++++++++++------- .../GitHubWorkflowBundle_nl.properties | 689 ++++++++++------- .../GitHubWorkflowBundle_pl.properties | 687 ++++++++++------- .../GitHubWorkflowBundle_pt_BR.properties | 683 ++++++++++------- .../GitHubWorkflowBundle_ru.properties | 691 ++++++++++------- .../GitHubWorkflowBundle_sv.properties | 685 ++++++++++------- .../GitHubWorkflowBundle_th.properties | 689 ++++++++++------- .../GitHubWorkflowBundle_tr.properties | 695 ++++++++++------- .../GitHubWorkflowBundle_uk.properties | 685 ++++++++++------- .../GitHubWorkflowBundle_vi.properties | 697 ++++++++++------- .../GitHubWorkflowBundle_zh_CN.properties | 697 ++++++++++------- .../services/EditorFeatureTestCase.java | 2 +- .../services/LocalizationResourcesTest.java | 194 +++++ .../services/WorkflowCompletionTest.java | 644 +++++++++++++++- .../services/WorkflowGutterActionTest.java | 45 +- .../services/WorkflowHighlightingTest.java | 305 ++++++++ .../services/WorkflowQuickFixTest.java | 66 +- .../services/WorkflowRunClientTest.java | 32 + .../WorkflowRunProcessHandlerTest.java | 120 ++- 49 files changed, 11933 insertions(+), 5374 deletions(-) create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupEnterHandler.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupTypedHandler.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionConfidence.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunDownloads.java create mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowSyntaxSchema.java diff --git a/doc/spec/editor-test-matrix.md b/doc/spec/editor-test-matrix.md index 4dc0fb8..35af12c 100644 --- a/doc/spec/editor-test-matrix.md +++ b/doc/spec/editor-test-matrix.md @@ -27,6 +27,8 @@ Official syntax references: 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. @@ -48,6 +50,8 @@ Official syntax references: - `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. @@ -76,10 +80,16 @@ Official syntax references: - 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 @@ -91,11 +101,13 @@ Official syntax references: 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, prints job URLs before status lines, prints compact ASCII job progress with - elapsed times and `[WAIT]` / `[RUN]` / `[OK]` / `[FAIL]` markers, 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. + 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. @@ -104,10 +116,10 @@ Official syntax references: 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. -- GitHub context/default-env data is generated into checked-in resource snapshots by `./gradlew generateGitHubDocsData`. - Tests ensure completion/highlighting metadata uses exactly those snapshots. 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 a3160ba..225ae93 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java @@ -40,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; @@ -82,7 +83,7 @@ public static void ifEnoughItems( final TextRange textRange = textRangeIncludingPreviousDot(psiElement, tooLongPart[0], tooLongPart[tooLongPart.length - 1]); new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.invalid.suffix.remove", Arrays.stream(tooLongPart).map(SimpleElement::text).collect(Collectors.joining("."))), - null, + SUPPRESS_ON, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); } else { @@ -97,7 +98,7 @@ public static boolean isDefinedItem0(@NotNull final PsiElement psiElement, @NotN final TextRange textRange = simpleTextRange(psiElement, itemId); createAnnotation(psiElement, textRange, holder, items.stream().map(item -> new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.replace.with", item), - null, + RELOAD, replaceAction(textRange, item) )).toList()); return false; @@ -114,7 +115,7 @@ public static boolean isField2Valid(@NotNull final PsiElement psiElement, @NotNu final TextRange textRange = textRangeIncludingPreviousDot(psiElement, itemId, itemId); new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.invalid.remove", itemId.text()), - null, + SUPPRESS_ON, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); return false; @@ -127,7 +128,7 @@ public static void isValidItem3(@NotNull final PsiElement psiElement, @NotNull f final TextRange textRange = simpleTextRange(psiElement, itemId); createAnnotation(psiElement, textRange, holder, outputs.stream().filter(PsiElementHelper::hasText).map(item -> new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.replace.with", item), - null, + RELOAD, replaceAction(textRange, item) )).toList()); } @@ -162,7 +163,7 @@ public static SyntaxAnnotation deleteInvalidAction(final YAMLKeyValue element) { final TextRange textRange = ofNullable(element.getValue()).map(PsiElement::getTextRange).orElseGet(element::getTextRange); return new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.invalid.remove", element.getValueText()), - null, + SUPPRESS_ON, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, deleteElementAction(textRange) @@ -171,7 +172,6 @@ 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( GitHubWorkflowBundle.message("inspection.action.jump", action.name()), JUMP_TO_IMPLEMENTATION, @@ -246,7 +246,7 @@ private static boolean isEmpty(final Collection items, final SimpleEleme final TextRange textRange = simpleTextRange(psiElement, itemId); createAnnotation(psiElement, textRange, holder, List.of(new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.invalid.remove", itemId.text()), - null, + SUPPRESS_ON, deleteElementAction(textRange) ))); return true; 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 52cc622..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,12 +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; @@ -44,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; @@ -67,7 +72,7 @@ public static void highLightAction(final AnnotationHolder holder, final YAMLKeyV 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); } @@ -95,7 +100,7 @@ private static void highlightCallableParameter(final AnnotationHolder holder, fi : GitHubWorkflowBundle.message("inspection.parameter.input"); addAnnotation(holder, item, new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.action.delete.invalid", label, id), - null, + SUPPRESS_ON, deleteElementAction(item.getTextRange()) )); } @@ -133,14 +138,16 @@ private static void highlightResolvedActionReference(final AnnotationHolder hold 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(); + .tooltip(tooltip); if (action.isLocal()) { - result.add(newJumpToFile(action)); + final SyntaxAnnotation jumpToFile = newJumpToFile(action); + result.add(jumpToFile); + annotation.gutterIconRenderer(new IconRenderer(jumpToFile, element, JUMP_TO_IMPLEMENTATION)); } + annotation.create(); }); } } @@ -166,7 +173,7 @@ private static Optional newerMajorActionRef(final YAMLKeyValue final TextRange range = getTextElement(element).map(PsiElement::getTextRange).orElseGet(element::getTextRange); return Optional.of(new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.action.update.major", usesValue, newUsesValue), - null, + RELOAD, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, replaceAction(range, newUsesValue) @@ -203,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 : EMPTY, + suppressed ? SUPPRESS_OFF : SUPPRESS_ON, HighlightSeverity.INFORMATION, suppressed ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.INFORMATION, f -> { @@ -218,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 -> { @@ -234,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 -> { 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 3a6d67f..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,7 +1,6 @@ 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; @@ -9,19 +8,16 @@ 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; @@ -31,18 +27,14 @@ 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 { @@ -57,8 +49,6 @@ public static void highlightNeeds(final AnnotationHolder holder, final LeafPsiEl if (FIELD_RESULT.equals(parts[2].text())) { return; } - // TODO: find target for highlighting reference - // TODO: implement reference ... does highlighting comes after reference which could add an reference indicator? final List outputs = listJobOutputs(listAllJobs(element).stream().filter(job -> job.getKeyText().equals(jobId.text())).findFirst().orElse(null)).stream().map(SimpleElement::key).toList(); if (parts.length > 3) { isValidItem3(element, holder, parts[3], outputs); @@ -78,7 +68,7 @@ public static void highlightNeeds(final AnnotationHolder holder, final PsiElemen // INVALID JOB_ID addAnnotation(holder, psiElement, new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.needs.invalid.job", element.getText()), - null, + SUPPRESS_ON, deleteElementAction(psiElement.getTextRange()) )); } else { @@ -119,24 +109,6 @@ 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 = GitHubWorkflowBundle.message("documentation.open.declaration", 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 currentJob(psiElement).map(job -> listAllJobs(psiElement).stream().takeWhile(j -> !j.getKeyText().equals(job.getKeyText())).toList()).orElseGet(Collections::emptyList); 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 578582d..82022c2 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java @@ -30,6 +30,8 @@ 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; @@ -51,7 +53,7 @@ public static void highLightSecrets( final TextRange textRange = new TextRange(range.getStartOffset() + parts[0].startIndexOffset(), range.getStartOffset() + parts[parts.length - 1].endIndexOffset()); new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.secret.invalid.if", simpleElement.text()), - null, + SUPPRESS_ON, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); } @@ -60,7 +62,7 @@ public static void highLightSecrets( final TextRange textRange = simpleTextRange(element, secretId); createAnnotation(element, textRange, holder, secrets.stream().map(secret -> new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.secret.replace.runtime", secretId.text(), secret), - null, + RELOAD, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, replaceAction(textRange, secret), 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 39ce6b3..4fe4183 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java @@ -109,9 +109,8 @@ public static List listSteps(final PsiElement psiElement) { ); } - //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/model/GitHubAction.java b/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java index 300046e..b1d8a26 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java @@ -133,7 +133,6 @@ public Optional getLocalVirtualFile(final Project project) { } // !!! Performs Network and File Operations !!! - //TODO: get Tags for autocompletion public synchronized GitHubAction resolve() { if ((!isResolved() || System.currentTimeMillis() >= expiryTime()) && !isSuppressed()) { extractParameters(); 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 ed7e117..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,19 +11,19 @@ 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.*; import java.util.List; import java.util.Objects; import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.stream.Collectors; 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; @@ -94,24 +94,12 @@ public static void createAnnotation( final List fixes ) { if (fixes != null && !fixes.isEmpty() && holder != null && psiElement != null && psiElement.isValid()) { - final List gutterFixes = fixes.stream() - .filter(fix -> fix.icon != null) - .toList(); - final SyntaxAnnotation gutterFix = gutterFixes.stream() - .filter(fix -> fix.icon != NodeIcon.EMPTY) - .findFirst() - .or(() -> gutterFixes.stream().findFirst()) - .orElse(null); - final AtomicBoolean gutterIconUsed = new AtomicBoolean(false); fixes.stream().collect(Collectors.groupingBy(f -> f.level)).forEach((level, group) -> { final SyntaxAnnotation firstItem = group.get(0); final AnnotationBuilder annotation = holder.newAnnotation(level, firstItem.showToolTip ? firstItem.text : ""); 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); - if (gutterFix != null && group.contains(gutterFix) && gutterIconUsed.compareAndSet(false, true)) { - annotation.gutterIconRenderer(new IconRenderer(gutterFix, psiElement, gutterFix.icon, fixes)); - } group.forEach(fix -> ofNullable(fix.execute).ifPresent(exec -> annotation.withFix(fix))); annotation.create(); }); @@ -123,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() { 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 b3aa90b..f22d868 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java @@ -25,6 +25,8 @@ 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; @@ -84,6 +86,13 @@ 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<>() { @@ -99,14 +108,27 @@ public void addCompletions( 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")) { + 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(codeCompletionPreviousJobs(position)).filter(cil -> !cil.isEmpty()) @@ -138,11 +160,11 @@ public void addCompletions( Character.MIN_VALUE ); } else { - final Optional structureCompletion = workflowTriggerStructureCompletion(parameters); + final Optional structureCompletion = workflowStructureCompletion(completionPsi); if (structureCompletion.isPresent()) { final StructureCompletion completion = structureCompletion.get(); addLookupElements( - resultSet.withPrefixMatcher(getDefaultPrefix(parameters)), + resultSet.withPrefixMatcher(getDefaultPrefix(completionPsi)), completion.items(), ICON_NODE, completion.suffix() @@ -187,9 +209,7 @@ private static boolean isCompletingUsesField(final CompletionParameters paramete return true; } final String wholeText = parameters.getOriginalFile().getText(); - final int offset = Math.min(parameters.getOffset(), wholeText.length()); - final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; - final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + final String beforeCaret = lineBeforeCaret(wholeText, parameters.getOffset()); return beforeCaret.matches("\\s*-?\\s*" + FIELD_USES + "\\s*:\\s*.*"); } @@ -198,18 +218,37 @@ private static boolean isCompletingShellField(final CompletionParameters paramet return true; } final String wholeText = parameters.getOriginalFile().getText(); - final int offset = Math.min(parameters.getOffset(), wholeText.length()); - final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; - final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + final String beforeCaret = lineBeforeCaret(wholeText, parameters.getOffset()); return beforeCaret.matches("\\s*" + FIELD_SHELL + "\\s*:\\s*.*"); } - private static Optional workflowTriggerStructureCompletion(final CompletionParameters parameters) { - final Optional context = yamlKeyContext(parameters); + 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(), ':')); } @@ -222,25 +261,155 @@ private static Optional workflowTriggerStructureCompletion( } if (isChildOf(path, FIELD_ON, "workflow_dispatch", FIELD_INPUTS) || isChildOf(path, FIELD_ON, "workflow_call", FIELD_INPUTS)) { - return Optional.of(new StructureCompletion(workflowInputPropertyKeys(), ':')); + 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(workflowOutputPropertyKeys(), ':')); + 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 yamlKeyContext(final CompletionParameters parameters) { - final String wholeText = parameters.getOriginalFile().getText(); - final int offset = Math.min(parameters.getOffset(), wholeText.length()); - final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; - final String currentLine = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + 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, lineStart).lines().forEach(raw -> { + 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); @@ -256,9 +425,11 @@ private static Optional yamlKeyContext(final CompletionParameter while (!stack.isEmpty() && stack.get(stack.size() - 1).indent() >= currentIndent) { stack.remove(stack.size() - 1); } - return stack.isEmpty() - ? Optional.empty() - : Optional.of(new YamlKeyContext(stack.stream().map(YamlAncestor::key).toList(), currentLine)); + 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) { @@ -307,6 +478,18 @@ private static boolean isChildOf(final List path, final String... expect 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")); @@ -321,28 +504,9 @@ private static Map workflowCallTriggerKeys() { return result; } - private 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 result; - } - - private 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 result; - } - private static Optional remoteUsesRef(final CompletionParameters parameters) { final String wholeText = parameters.getOriginalFile().getText(); - final int offset = Math.min(parameters.getOffset(), wholeText.length()); - final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; - final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + 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))) @@ -351,9 +515,7 @@ private static Optional remoteUsesRef(final CompletionParameters private static Optional remoteUsesTargetPrefix(final CompletionParameters parameters) { final String wholeText = parameters.getOriginalFile().getText(); - final int offset = Math.min(parameters.getOffset(), wholeText.length()); - final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; - final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + 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(); } @@ -383,6 +545,112 @@ private static Map localUsesCompletions(final PsiElement positio 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() @@ -490,9 +758,9 @@ private static Optional currentCallable(final PsiElement position) private static Optional nearestPreviousUsesValue(final CompletionParameters parameters) { final String wholeText = parameters.getOriginalFile().getText(); - final int offset = Math.min(parameters.getOffset(), wholeText.length()); - final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; - final String beforeCaret = wholeText.substring(0, lineStart); + 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]; @@ -545,9 +813,7 @@ private static boolean isCompletingNeedsField(final CompletionParameters paramet return true; } final String wholeText = parameters.getOriginalFile().getText(); - final int offset = Math.min(parameters.getOffset(), wholeText.length()); - final int lineStart = wholeText.lastIndexOf('\n', Math.max(0, offset - 1)) + 1; - final String beforeCaret = wholeText.substring(lineStart, offset).replace("IntellijIdeaRulezzz", ""); + final String beforeCaret = lineBeforeCaret(wholeText, parameters.getOffset()); return beforeCaret.matches("\\s*" + FIELD_NEEDS + "\\s*:\\s*.*"); } @@ -675,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).replace("IntellijIdeaRulezzz", "").trim(); + 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) { @@ -709,6 +1014,9 @@ 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/HighlightAnnotator.java b/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java index 230efe3..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,13 +1,16 @@ 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.application.ApplicationManager; import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; @@ -18,7 +21,9 @@ import java.util.ArrayList; 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; @@ -28,6 +33,7 @@ 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; @@ -53,7 +59,10 @@ 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; @@ -70,13 +79,13 @@ public void annotate(@NotNull final PsiElement psiElement, @NotNull final Annota 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()) { @@ -96,17 +105,21 @@ 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()) .textAttributes(WorkflowTextAttributes.DECLARATION) - .gutterIconRenderer(new IconRenderer(null, element, ICON_TEXT_VARIABLE)) + .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) @@ -147,6 +160,255 @@ private static void highlightScalarLiterals(final AnnotationHolder holder, final .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(); @@ -163,7 +425,7 @@ private static void outputsHandler(final AnnotationHolder holder, final PsiEleme && !containsOutputReference(workflowText, needsOutputReference); }).forEach(output -> new SyntaxAnnotation( GitHubWorkflowBundle.message("inspection.output.unused", output.getKeyText()), - null, + SUPPRESS_ON, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.LIKE_UNUSED_SYMBOL, deleteElementAction(output.getTextRange()), 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/WorkflowRunClient.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java index ac20750..076d004 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java @@ -9,6 +9,7 @@ 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; @@ -98,6 +99,31 @@ public CancelResult cancel(final WorkflowRunRequest request, final long runId) t 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. * @@ -155,6 +181,63 @@ public String logs(final WorkflowRunRequest request, final long runId) throws IO 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, @@ -228,6 +311,44 @@ private HttpResponse send( : 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 @@ -289,12 +410,31 @@ private static WorkflowRunHttpException failure(final String operation, final Ht ); } + 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) { - final String body = Optional.ofNullable(response.body()).orElse("").strip(); + 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 = response.headers() + final String contentType = headers .firstValue("Content-Type") .orElse("") .toLowerCase(); @@ -310,30 +450,38 @@ private static boolean shouldTryNextAuthorization(final int statusCode) { } private static boolean rateLimitExceeded(final HttpResponse response) { - if (response.statusCode() != 403 && response.statusCode() != 429) { + 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 (response.headers() + if (headers .firstValue("x-ratelimit-remaining") .map(String::trim) .filter("0"::equals) .isPresent()) { return true; } - return Optional.ofNullable(response.body()) - .map(body -> body.toLowerCase(Locale.ROOT)) - .filter(body -> body.contains("rate limit")) + return Optional.ofNullable(body) + .map(value -> value.toLowerCase(Locale.ROOT)) + .filter(value -> value.contains("rate limit")) .isPresent(); } private static boolean needsAccountAction(final HttpResponse response) { - if (response.statusCode() == 401 || response.statusCode() == 429) { + 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 (response.statusCode() != 403) { + if (statusCode != 403) { return false; } - return !mustHaveAdminRights(response) || rateLimitExceeded(response); + return !mustHaveAdminRights(body) || rateLimitExceeded(statusCode, headers, body); } private static boolean mustHaveAdminRights(final HttpResponse response) { @@ -407,6 +555,12 @@ private static Optional longValue(final JsonObject object, final String na .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"); } @@ -425,6 +579,10 @@ private static boolean hasText(final String value) { 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 { @@ -436,6 +594,11 @@ private record JdkHttpTransport(HttpClient client) implements HttpTransport { 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) { @@ -450,12 +613,18 @@ public boolean completed() { 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; diff --git a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java index d15c667..39494eb 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java @@ -11,6 +11,10 @@ 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; @@ -28,7 +32,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.Icon; -import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JProgressBar; @@ -38,6 +41,7 @@ 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; @@ -50,6 +54,8 @@ 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. @@ -84,8 +90,9 @@ public void onTextAvailable(final @NotNull ProcessEvent event, final @NotNull Ke private @Nullable DefaultMutableTreeNode rootNode; private @Nullable Tree tree; private @Nullable JProgressBar progressBar; - private @Nullable JButton deleteRunButton; + private @Nullable RunnerLayoutUi runnerLayoutUi; private @Nullable Timer animationTimer; + private final AtomicBoolean deleteInProgress = new AtomicBoolean(false); private volatile long terminalRunId = -1; private volatile String terminalConclusion = ""; @@ -134,6 +141,8 @@ 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); } @@ -145,12 +154,10 @@ public void runDeleted(final long runId) { @Override public void runDeleteFailed(final long runId) { - ApplicationManager.getApplication().invokeLater(() -> { - final JButton button = deleteRunButton; - if (button != null && terminalRunId == runId && !project.isDisposed()) { - button.setEnabled(true); - } - }); + if (terminalRunId == runId) { + deleteInProgress.set(false); + ApplicationManager.getApplication().invokeLater(this::updateContent); + } } @Override @@ -227,17 +234,10 @@ private void attach() { createdProgress.setStringPainted(false); createdProgress.setPreferredSize(new Dimension(0, JBUI.scale(3))); - final JButton createdDeleteRunButton = new JButton(GitHubWorkflowBundle.message("workflow.run.delete.button")); - createdDeleteRunButton.setToolTipText(GitHubWorkflowBundle.message("workflow.run.delete.tooltip")); - createdDeleteRunButton.setVisible(false); - createdDeleteRunButton.addActionListener(ignored -> { - createdDeleteRunButton.setEnabled(false); - processHandler.deleteRemoteRun(); - }); + layout.getOptions().setTopRightToolbar(runToolbarActions(), "GitHubWorkflowRunToolbar"); final JPanel progressPanel = new JPanel(new BorderLayout()); progressPanel.add(createdProgress, BorderLayout.CENTER); - progressPanel.add(createdDeleteRunButton, BorderLayout.EAST); final JPanel detailPanel = new JPanel(new BorderLayout()); detailPanel.add(progressPanel, BorderLayout.NORTH); @@ -268,7 +268,7 @@ private void attach() { treeModel = createdModel; tree = createdTree; progressBar = createdProgress; - deleteRunButton = createdDeleteRunButton; + runnerLayoutUi = layout; } refreshTree(); createdTree.setSelectionPath(new TreePath(createdRoot.getPath())); @@ -356,10 +356,12 @@ private void selectEntry(final TreePath path) { 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) { @@ -399,16 +401,98 @@ private void updateContent() { 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 JButton deleteButton = deleteRunButton; - if (deleteButton != null && !project.isDisposed()) { - deleteButton.setVisible(terminal() && terminalRunId > 0); - deleteButton.setEnabled(terminal() && terminalRunId > 0); + 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(); @@ -500,6 +584,38 @@ static JobDisplayName splitJobName(final String name) { 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(); @@ -560,17 +676,17 @@ public Icon icon() { return AllIcons.RunConfigurations.TestTerminated; } if (jobs.values().stream().anyMatch(JobNode::failed)) { - return AllIcons.RunConfigurations.TestState.Red2; + return AllIcons.General.Error; } if (jobs.values().stream().anyMatch(JobNode::skipped)) { return AllIcons.RunConfigurations.TestState.Yellow2; } if (jobs.isEmpty() && terminal()) { return successful(terminalConclusion) - ? AllIcons.RunConfigurations.TestState.Green2 - : AllIcons.RunConfigurations.TestState.Red2; + ? AllIcons.General.GreenCheckmark + : AllIcons.General.Error; } - return AllIcons.RunConfigurations.TestState.Green2; + return AllIcons.General.GreenCheckmark; } @Override @@ -629,12 +745,12 @@ public Icon icon() { return AllIcons.RunConfigurations.TestTerminated; } if (children.stream().anyMatch(JobNode::failed)) { - return AllIcons.RunConfigurations.TestState.Red2; + return AllIcons.General.Error; } if (children.stream().anyMatch(JobNode::skipped)) { return AllIcons.RunConfigurations.TestState.Yellow2; } - return children.isEmpty() ? AllIcons.RunConfigurations.TestNotRan : AllIcons.RunConfigurations.TestState.Green2; + return children.isEmpty() ? AllIcons.RunConfigurations.TestNotRan : AllIcons.General.GreenCheckmark; } @Override @@ -767,7 +883,7 @@ public Icon icon() { if (cancelled()) { return AllIcons.RunConfigurations.TestTerminated; } - return successful(conclusion) ? AllIcons.RunConfigurations.TestState.Green2 : AllIcons.RunConfigurations.TestState.Red2; + return successful(conclusion) ? AllIcons.General.GreenCheckmark : AllIcons.General.Error; } return running() ? AnimatedIcon.Default.INSTANCE : AllIcons.RunConfigurations.TestNotRan; } @@ -803,6 +919,12 @@ 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); } 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/WorkflowRunProcessHandler.java b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java index 5116448..1c8fef3 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java @@ -14,14 +14,18 @@ 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; /** @@ -37,6 +41,9 @@ public final class WorkflowRunProcessHandler extends ProcessHandler { 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<>(); @@ -473,6 +480,136 @@ void deleteRemoteRun() { }); } + 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()) { 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/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 6597e9a..df1483b 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -20,6 +20,12 @@ implementationClass="com.github.yunabraska.githubworkflow.services.CodeCompletion"/> + + + diff --git a/src/main/resources/messages/GitHubWorkflowBundle.properties b/src/main/resources/messages/GitHubWorkflowBundle.properties index d8d1f5e..2771e50 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle.properties @@ -26,21 +26,21 @@ 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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 for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} +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=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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} @@ -76,6 +76,15 @@ 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}] @@ -208,6 +217,156 @@ 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. @@ -256,10 +415,28 @@ workflow.run.tree.failed=failed workflow.run.tree.skipped=skipped workflow.run.tree.warn=warn workflow.run.tree.err=err -workflow.run.delete.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 667c192..a371bad 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ar.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ar.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=دعم ملفات سير عمل GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=أدوات إضافة GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=تحديث ذاكرة الإجراءات -action.GitHubWorkflow.RefreshActionCache.description=تحديث بيانات GitHub Actions البعيدة وسير العمل القابلة لإعادة الاستخدام التي تم حلها -action.GitHubWorkflow.RestoreActionWarnings.text=استعادة تحذيرات الإجراءات -action.GitHubWorkflow.RestoreActionWarnings.description=استعادة تحذيرات التحقق المخفية للإجراءات والمدخلات والمخرجات -action.GitHubWorkflow.ClearActionCache.text=مسح ذاكرة الإجراءات -action.GitHubWorkflow.ClearActionCache.description=مسح بيانات GitHub Actions وسير العمل القابلة لإعادة الاستخدام من الذاكرة المؤقتة -notification.cache.cleared=تم مسح {0} إدخالات من ذاكرة GitHub Workflow. -notification.cache.refresh.started=يتم تحديث {0} إدخالات بعيدة من ذاكرة GitHub Workflow. -notification.warnings.restored=تمت استعادة التحذيرات لـ {0} إدخالات GitHub Workflow. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +workflow.run.field.owner=مالك +workflow.run.field.repo=مستودع +workflow.run.field.workflow=ملف سير العمل 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=احذف {0} غير صالح [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=بدّل التحذيرات [{0}] لـ [{1}] -inspection.warning.on=تشغيل -inspection.warning.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.action.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.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}) -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=الكاش: {0} إدخالات، {1} محلولة، {2} بعيدة، {3} قديمة، {4} مكتومة. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 45ca3f0..4e571f2 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_cs.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_cs.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Podpora souborů workflow GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow +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=Obnoví metadata vzdálených GitHub Actions a znovupoužitelných workflow -action.GitHubWorkflow.RestoreActionWarnings.text=Obnovit varování akcí -action.GitHubWorkflow.RestoreActionWarnings.description=Obnoví potlačená validační varování akcí, vstupů a výstupů -action.GitHubWorkflow.ClearActionCache.text=Vymazat cache akcí -action.GitHubWorkflow.ClearActionCache.description=Vymaže uložená metadata GitHub Actions a znovupoužitelných workflow -notification.cache.cleared=Vymazáno {0} položek cache GitHub Workflow. -notification.cache.refresh.started=Obnovuje se {0} vzdálených položek cache GitHub Workflow. -notification.warnings.restored=Varování obnovena pro {0} položek GitHub Workflow. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=Smazat neplatné {0} [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=Přepnout varování [{0}] pro [{1}] -inspection.warning.on=zapnuto +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=Incomplete statement [{0}] -inspection.invalid.suffix.remove=Remove invalid suffix [{0}] -inspection.replace.with=Replace with [{0}] -inspection.invalid.remove=Remove invalid [{0}] +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=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.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}) -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} položek, {1} vyřešeno, {2} vzdálené, {3} zastaralé, {4} ztlumené. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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 -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.job.fallbackName=Job {0} -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.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=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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 2b8a80f..f4997a3 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_de.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_de.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Unterstützung für GitHub-Actions-Workflow-Dateien -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Werkzeuge des GitHub-Workflow-Plugins +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=Action-Warnungen wiederherstellen -action.GitHubWorkflow.RestoreActionWarnings.description=Unterdrückte Validierungswarnungen für Actions, Eingaben und Ausgaben wiederherstellen -action.GitHubWorkflow.ClearActionCache.text=Action-Cache leeren -action.GitHubWorkflow.ClearActionCache.description=Zwischengespeicherte Metadaten von GitHub Actions und wiederverwendbaren Workflows löschen +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={0} zwischengespeicherte entfernte GitHub-Workflow-Einträge werden aktualisiert. -notification.warnings.restored=Warnungen für {0} GitHub-Workflow-Einträge wiederhergestellt. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=Ungültige {0} löschen [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=Warnungen [{0}] für [{1}] umschalten -inspection.warning.on=an -inspection.warning.off=aus -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.action.reload=Neu laden [{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.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}) -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} Einträge, {1} gelöst, {2} remote, {3} veraltet, {4} stumm. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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=Job: {0} {1} [{2}{3}{4}] +workflow.run.job.main=Auftrag: {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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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 -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.job.fallbackName=Job {0} -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.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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -workflow.run.jobs.title=Workflow jobs -workflow.run.jobs.root=Workflow run -workflow.run.jobs.description=GitHub Actions job tree and selected job log +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=uebersprungen -workflow.run.tree.warn=Warnung -workflow.run.tree.err=Fehler -workflow.run.delete.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 4f28478..1a47e7b 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_es.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_es.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Compatibilidad con archivos de flujos de trabajo de GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Herramientas del complemento GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=Actualizar caché de acciones -action.GitHubWorkflow.RefreshActionCache.description=Actualiza los metadatos resueltos de acciones remotas y flujos reutilizables de GitHub -action.GitHubWorkflow.RestoreActionWarnings.text=Restaurar advertencias de acciones -action.GitHubWorkflow.RestoreActionWarnings.description=Restaura advertencias de validación ocultas de acciones, entradas y salidas -action.GitHubWorkflow.ClearActionCache.text=Vaciar caché de acciones -action.GitHubWorkflow.ClearActionCache.description=Borra los metadatos almacenados de acciones y flujos reutilizables de GitHub -notification.cache.cleared=Se borraron {0} entradas en caché de GitHub Workflow. -notification.cache.refresh.started=Actualizando {0} entradas remotas en caché de GitHub Workflow. -notification.warnings.restored=Advertencias restauradas para {0} entradas de GitHub Workflow. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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=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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=Eliminar {0} no válido [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=Cambiar avisos [{0}] para [{1}] -inspection.warning.on=activado -inspection.warning.off=desactivado -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.action.reload=Recargar [{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.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}) -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=Caché: {0} entradas, {1} resueltas, {2} remotas, {3} obsoletas, {4} silenciadas. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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=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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index b1ede99..8a66e19 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_fr.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_fr.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Prise en charge des fichiers de workflow GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Outils du plugin GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=Actualiser le cache des actions -action.GitHubWorkflow.RefreshActionCache.description=Actualise les métadonnées résolues des actions distantes et workflows réutilisables GitHub -action.GitHubWorkflow.RestoreActionWarnings.text=Restaurer les avertissements d'actions -action.GitHubWorkflow.RestoreActionWarnings.description=Restaure les avertissements de validation masqués des actions, entrées et sorties -action.GitHubWorkflow.ClearActionCache.text=Vider le cache des actions -action.GitHubWorkflow.ClearActionCache.description=Supprime les métadonnées mises en cache des actions et workflows réutilisables GitHub -notification.cache.cleared={0} entrées GitHub Workflow en cache supprimées. -notification.cache.refresh.started=Actualisation de {0} entrées distantes GitHub Workflow en cache. -notification.warnings.restored=Avertissements restaurés pour {0} entrées GitHub Workflow. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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=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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=Supprimer {0} invalide [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=Basculer les avertissements [{0}] pour [{1}] -inspection.warning.on=activé -inspection.warning.off=désactivé -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}] +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=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.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}) -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} entrées, {1} résolues, {2} distantes, {3} expirées, {4} muettes. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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 -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.job.fallbackName=Job {0} -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. +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=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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index a3f3df7..1c8debc 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_hi.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_hi.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=GitHub Actions workflow फ़ाइलों के लिए समर्थन -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=GitHub Workflow प्लगइन टूल -action.GitHubWorkflow.RefreshActionCache.text=Action cache रीफ़्रेश करें -action.GitHubWorkflow.RefreshActionCache.description=हल किए गए remote GitHub Actions और reusable workflow metadata को रीफ़्रेश करता है -action.GitHubWorkflow.RestoreActionWarnings.text=Action warnings वापस लाएँ -action.GitHubWorkflow.RestoreActionWarnings.description=छिपाए गए action, input और output validation warnings वापस लाता है -action.GitHubWorkflow.ClearActionCache.text=Action cache साफ़ करें -action.GitHubWorkflow.ClearActionCache.description=कैश किए गए GitHub Actions और reusable workflow metadata को साफ़ करता है -notification.cache.cleared={0} GitHub Workflow cache entries साफ़ किए गए। -notification.cache.refresh.started={0} remote GitHub Workflow cache entries रीफ़्रेश हो रहे हैं। -notification.warnings.restored={0} GitHub Workflow entries के warnings वापस लाए गए। - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +workflow.run.field.owner=मालिक +workflow.run.field.repo=भण्डार +workflow.run.field.workflow=वर्कफ़्लो फ़ाइल 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=अमान्य {0} हटाएं [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=[{1}] के लिए चेतावनियां [{0}] करें -inspection.warning.on=चालू -inspection.warning.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.action.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.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}) -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=कैश: {0} एंट्री, {1} हल, {2} रिमोट, {3} पुरानी, {4} शांत. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 812ddf4..e333d8d 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_id.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_id.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Dukungan untuk file workflow GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Alat plugin GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=Segarkan cache action -action.GitHubWorkflow.RefreshActionCache.description=Menyegarkan metadata GitHub Actions jarak jauh dan workflow pakai ulang yang telah diselesaikan -action.GitHubWorkflow.RestoreActionWarnings.text=Pulihkan peringatan action -action.GitHubWorkflow.RestoreActionWarnings.description=Memulihkan peringatan validasi action, input, dan output yang disembunyikan -action.GitHubWorkflow.ClearActionCache.text=Hapus cache action -action.GitHubWorkflow.ClearActionCache.description=Menghapus metadata GitHub Actions dan workflow pakai ulang dari cache -notification.cache.cleared={0} entri cache GitHub Workflow dihapus. -notification.cache.refresh.started=Menyegarkan {0} entri cache GitHub Workflow jarak jauh. -notification.warnings.restored=Peringatan dipulihkan untuk {0} entri GitHub Workflow. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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=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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=Hapus {0} tidak valid [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=Alihkan peringatan [{0}] untuk [{1}] -inspection.warning.on=aktif -inspection.warning.off=nonaktif -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.action.reload=Muat ulang [{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.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}) -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} entri, {1} selesai, {2} remote, {3} basi, {4} dibisukan. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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=Job: {0} {1} [{2}{3}{4}] +workflow.run.job.main=Pekerjaan: {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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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=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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 790db6b..7822206 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_it.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_it.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Supporto per i file workflow di GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Strumenti del plugin GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=Aggiorna cache azioni -action.GitHubWorkflow.RefreshActionCache.description=Aggiorna i metadati risolti di GitHub Actions remote e workflow riutilizzabili -action.GitHubWorkflow.RestoreActionWarnings.text=Ripristina avvisi azioni -action.GitHubWorkflow.RestoreActionWarnings.description=Ripristina gli avvisi di validazione nascosti per azioni, input e output -action.GitHubWorkflow.ClearActionCache.text=Svuota cache azioni -action.GitHubWorkflow.ClearActionCache.description=Svuota i metadati in cache di GitHub Actions e workflow riutilizzabili -notification.cache.cleared=Rimosse {0} voci dalla cache di GitHub Workflow. -notification.cache.refresh.started=Aggiornamento di {0} voci remote nella cache di GitHub Workflow. -notification.warnings.restored=Avvisi ripristinati per {0} voci GitHub Workflow. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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=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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} +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=Update action [{0}] to [{1}] -inspection.warning.toggle=Attiva/disattiva avvisi [{0}] per [{1}] -inspection.warning.on=attivo -inspection.warning.off=disattivo -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.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=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.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}) -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} voci, {1} risolte, {2} remote, {3} obsolete, {4} silenziate. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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 -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.job.fallbackName=Job {0} -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.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=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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index ba59dbd..9d5c7d9 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ja.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ja.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=GitHub Actions ワークフローファイルをサポートします -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=GitHub Workflow プラグインツール -action.GitHubWorkflow.RefreshActionCache.text=アクションキャッシュを更新 -action.GitHubWorkflow.RefreshActionCache.description=解決済みのリモート GitHub Actions と再利用可能ワークフローのメタデータを更新します -action.GitHubWorkflow.RestoreActionWarnings.text=アクション警告を復元 -action.GitHubWorkflow.RestoreActionWarnings.description=非表示にしたアクション、入力、出力の検証警告を復元します -action.GitHubWorkflow.ClearActionCache.text=アクションキャッシュを消去 -action.GitHubWorkflow.ClearActionCache.description=キャッシュされた GitHub Actions と再利用可能ワークフローのメタデータを消去します -notification.cache.cleared={0} 件の GitHub Workflow キャッシュエントリを消去しました。 -notification.cache.refresh.started={0} 件の GitHub Workflow リモートキャッシュエントリを更新しています。 -notification.warnings.restored={0} 件の GitHub Workflow エントリの警告を復元しました。 - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +workflow.run.field.owner=オーナー +workflow.run.field.repo=リポジトリ +workflow.run.field.workflow=ワークフローファイル 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=無効な {0} を削除 [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=[{1}] の警告を [{0}] に切り替え -inspection.warning.on=オン -inspection.warning.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.action.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.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}) -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=キャッシュ: {0} 件、解決済み {1}、リモート {2}、古い {3}、ミュート {4}。 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {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 index 35b8f56..e7aa7fb 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ko.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ko.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=GitHub Actions 워크플로 파일 지원 -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=GitHub Workflow 플러그인 도구 -action.GitHubWorkflow.RefreshActionCache.text=액션 캐시 새로 고침 -action.GitHubWorkflow.RefreshActionCache.description=해결된 원격 GitHub Actions 및 재사용 가능한 워크플로 메타데이터를 새로 고칩니다 -action.GitHubWorkflow.RestoreActionWarnings.text=액션 경고 복원 -action.GitHubWorkflow.RestoreActionWarnings.description=숨긴 액션, 입력, 출력 검증 경고를 복원합니다 +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 Actions 및 재사용 가능한 워크플로 메타데이터를 지웁니다 -notification.cache.cleared=GitHub Workflow 캐시 항목 {0}개를 지웠습니다. -notification.cache.refresh.started=GitHub Workflow 원격 캐시 항목 {0}개를 새로 고치는 중입니다. -notification.warnings.restored=GitHub Workflow 항목 {0}개의 경고를 복원했습니다. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +workflow.run.field.owner=소유자 +workflow.run.field.repo=저장소 +workflow.run.field.workflow=워크플로 파일 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=잘못된 {0} 삭제 [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=[{1}] 경고 [{0}] 전환 -inspection.warning.on=켜짐 -inspection.warning.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.action.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.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}) -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=캐시: {0}개, 해결 {1}, 원격 {2}, 오래됨 {3}, 숨김 {4}. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 63322e4..cf84812 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_nl.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_nl.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow +plugin.name=GitHub-workflow plugin.description=Ondersteuning voor GitHub Actions-workflowbestanden -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Hulpmiddelen van de GitHub Workflow-plugin -action.GitHubWorkflow.RefreshActionCache.text=Actiecache vernieuwen -action.GitHubWorkflow.RefreshActionCache.description=Vernieuwt opgeloste metadata van externe GitHub Actions en herbruikbare workflows -action.GitHubWorkflow.RestoreActionWarnings.text=Actiewaarschuwingen herstellen -action.GitHubWorkflow.RestoreActionWarnings.description=Herstelt onderdrukte validatiewaarschuwingen voor acties, invoer en uitvoer -action.GitHubWorkflow.ClearActionCache.text=Actiecache wissen -action.GitHubWorkflow.ClearActionCache.description=Wist gecachte metadata van GitHub Actions en herbruikbare workflows -notification.cache.cleared={0} gecachte GitHub Workflow-items gewist. -notification.cache.refresh.started={0} externe gecachte GitHub Workflow-items worden vernieuwd. -notification.warnings.restored=Waarschuwingen hersteld voor {0} GitHub Workflow-items. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=Ongeldige {0} verwijderen [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=Waarschuwingen [{0}] schakelen voor [{1}] -inspection.warning.on=aan -inspection.warning.off=uit -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.action.reload=Opnieuw laden [{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.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}) -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} items, {1} opgelost, {2} extern, {3} verlopen, {4} gedempt. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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=Job: {0} {1} [{2}{3}{4}] +workflow.run.job.main=Taak: {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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 062131d..d3d27a4 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_pl.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_pl.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Obsługa plików workflow GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow +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=Odśwież pamięć podręczną akcji -action.GitHubWorkflow.RefreshActionCache.description=Odświeża metadane zdalnych GitHub Actions i workflow wielokrotnego użytku -action.GitHubWorkflow.RestoreActionWarnings.text=Przywróć ostrzeżenia akcji -action.GitHubWorkflow.RestoreActionWarnings.description=Przywraca ukryte ostrzeżenia walidacji akcji, wejść i wyjść +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=Czyści zapisane metadane GitHub Actions i workflow wielokrotnego użytku -notification.cache.cleared=Wyczyszczono {0} wpisów pamięci podręcznej GitHub Workflow. -notification.cache.refresh.started=Odświeżanie {0} zdalnych wpisów pamięci podręcznej GitHub Workflow. -notification.warnings.restored=Przywrócono ostrzeżenia dla {0} wpisów GitHub Workflow. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} +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=Update action [{0}] to [{1}] +inspection.action.update.major=Zaktualizuj akcję [{0}] do [{1}] inspection.warning.toggle=Przełącz ostrzeżenia [{0}] dla [{1}] -inspection.warning.on=włączone +inspection.warning.on=na inspection.warning.off=wyłączone -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.action.reload=Przeładuj [{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.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}) -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} wpisów, {1} rozwiązane, {2} zdalne, {3} stare, {4} wyciszone. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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=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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 48b9d88..1bcae49 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Suporte a arquivos de workflow do GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Ferramentas do plugin GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=Atualizar cache de ações -action.GitHubWorkflow.RefreshActionCache.description=Atualiza metadados resolvidos de ações remotas e workflows reutilizáveis do GitHub -action.GitHubWorkflow.RestoreActionWarnings.text=Restaurar avisos de ações -action.GitHubWorkflow.RestoreActionWarnings.description=Restaura avisos de validação ocultos de ações, entradas e saídas +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=Limpa metadados em cache de ações e workflows reutilizáveis do GitHub -notification.cache.cleared={0} entradas em cache do GitHub Workflow foram limpas. -notification.cache.refresh.started=Atualizando {0} entradas remotas em cache do GitHub Workflow. -notification.warnings.restored=Avisos restaurados para {0} entradas do GitHub Workflow. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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=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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} +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=Update action [{0}] to [{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=Incomplete statement [{0}] -inspection.invalid.suffix.remove=Remove invalid suffix [{0}] -inspection.replace.with=Replace with [{0}] -inspection.invalid.remove=Remove invalid [{0}] +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=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.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}) -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} entradas, {1} resolvidas, {2} remotas, {3} antigas, {4} silenciadas. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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 -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.job.fallbackName=Job {0} -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.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=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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index c63c561..4a4ca1d 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_ru.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_ru.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Поддержка файлов рабочих процессов GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Инструменты плагина GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=Обновить кэш действий -action.GitHubWorkflow.RefreshActionCache.description=Обновляет метаданные найденных удалённых GitHub Actions и переиспользуемых workflows -action.GitHubWorkflow.RestoreActionWarnings.text=Восстановить предупреждения действий -action.GitHubWorkflow.RestoreActionWarnings.description=Восстанавливает скрытые предупреждения проверки действий, входов и выходов +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 Actions и переиспользуемых workflows -notification.cache.cleared=Очищено записей кэша GitHub Workflow: {0}. -notification.cache.refresh.started=Обновляются удалённые записи кэша GitHub Workflow: {0}. -notification.warnings.restored=Предупреждения восстановлены для записей GitHub Workflow: {0}. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +workflow.run.field.owner=Владелец +workflow.run.field.repo=Репозиторий +workflow.run.field.workflow=Файл рабочего процесса 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=Удалить недопустимый {0} [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] +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=Incomplete statement [{0}] -inspection.invalid.suffix.remove=Remove invalid suffix [{0}] -inspection.replace.with=Replace with [{0}] -inspection.invalid.remove=Remove invalid [{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=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.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}) -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=Кэш: {0} записей, {1} решено, {2} удаленных, {3} устаревших, {4} скрытых. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 49e565b..8949d30 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_sv.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_sv.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Stöd för GitHub Actions-workflowfiler -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Verktyg för GitHub Workflow-pluginen +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=Uppdaterar lösta metadata för fjärrbaserade GitHub Actions och återanvändbara workflows +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äller dolda valideringsvarningar för åtgärder, indata och utdata -action.GitHubWorkflow.ClearActionCache.text=Rensa åtgärdscache -action.GitHubWorkflow.ClearActionCache.description=Rensar cachade metadata för GitHub Actions och återanvändbara workflows -notification.cache.cleared=Rensade {0} cachade GitHub Workflow-poster. -notification.cache.refresh.started=Uppdaterar {0} cachade fjärrposter för GitHub Workflow. -notification.warnings.restored=Varningar återställda för {0} GitHub Workflow-poster. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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=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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=Ta bort ogiltig {0} [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=Växla varningar [{0}] för [{1}] -inspection.warning.on=på -inspection.warning.off=av -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.action.reload=Läs om [{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.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}) -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} poster, {1} lösta, {2} fjärr, {3} gamla, {4} tystade. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. +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=Job: {0} {1} [{2}{3}{4}] +workflow.run.job.main=Jobb: {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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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 -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.job.fallbackName=Job {0} -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.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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 9b99e25..73c798e 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_th.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_th.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=รองรับไฟล์ workflow ของ GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow +plugin.name=เวิร์กโฟลว์ GitHub +plugin.description=รองรับไฟล์เวิร์กโฟลว์ GitHub Actions +group.GitHubWorkflow.Tools.text=เวิร์กโฟลว์ GitHub group.GitHubWorkflow.Tools.description=เครื่องมือปลั๊กอิน GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=รีเฟรชแคช action -action.GitHubWorkflow.RefreshActionCache.description=รีเฟรชเมตาดาตาของ GitHub Actions ระยะไกลและ workflow ที่ใช้ซ้ำได้ซึ่งแก้ไขแล้ว -action.GitHubWorkflow.RestoreActionWarnings.text=คืนค่าคำเตือน action -action.GitHubWorkflow.RestoreActionWarnings.description=คืนค่าคำเตือนการตรวจสอบ action, input และ output ที่ถูกซ่อนไว้ -action.GitHubWorkflow.ClearActionCache.text=ล้างแคช action -action.GitHubWorkflow.ClearActionCache.description=ล้างเมตาดาตาที่แคชไว้ของ GitHub Actions และ workflow ที่ใช้ซ้ำได้ -notification.cache.cleared=ล้างรายการแคช GitHub Workflow แล้ว {0} รายการ -notification.cache.refresh.started=กำลังรีเฟรชรายการแคช GitHub Workflow ระยะไกล {0} รายการ -notification.warnings.restored=คืนค่าคำเตือนสำหรับรายการ GitHub Workflow แล้ว {0} รายการ - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +workflow.run.field.owner=เจ้าของ +workflow.run.field.repo=พื้นที่เก็บข้อมูล +workflow.run.field.workflow=ไฟล์เวิร์กโฟลว์ 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} +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=Update action [{0}] to [{1}] +inspection.action.update.major=อัปเดตการกระทำ [{0}] เป็น [{1}] inspection.warning.toggle=สลับคำเตือน [{0}] สำหรับ [{1}] -inspection.warning.on=เปิด +inspection.warning.on=บน inspection.warning.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.action.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.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}) -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=แคช: {0} รายการ, แก้แล้ว {1}, ระยะไกล {2}, เก่า {3}, ปิดเสียง {4}. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 94751ec..2807735 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_tr.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_tr.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=GitHub Actions iş akışı dosyaları için destek -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=GitHub Workflow eklenti araçları -action.GitHubWorkflow.RefreshActionCache.text=Action önbelleğini yenile -action.GitHubWorkflow.RefreshActionCache.description=Çözümlenmiş uzak GitHub Actions ve yeniden kullanılabilir workflow meta verilerini yeniler -action.GitHubWorkflow.RestoreActionWarnings.text=Action uyarılarını geri yükle -action.GitHubWorkflow.RestoreActionWarnings.description=Gizlenen action, input ve output doğrulama uyarılarını geri yükler -action.GitHubWorkflow.ClearActionCache.text=Action önbelleğini temizle -action.GitHubWorkflow.ClearActionCache.description=Önbelleğe alınmış GitHub Actions ve yeniden kullanılabilir workflow meta verilerini temizler -notification.cache.cleared={0} GitHub Workflow önbellek kaydı temizlendi. -notification.cache.refresh.started={0} uzak GitHub Workflow önbellek kaydı yenileniyor. -notification.warnings.restored={0} GitHub Workflow kaydı için uyarılar geri yüklendi. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +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 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=Geçersiz {0} sil [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=[{1}] için uyarıları [{0}] değiştir -inspection.warning.on=açık -inspection.warning.off=kapalı -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.action.reload=Yeniden yükle [{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.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}) -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=Önbellek: {0} kayıt, {1} çözüldü, {2} uzak, {3} eski, {4} sessiz. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 5f047ec..552f490 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_uk.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_uk.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Підтримка файлів робочих процесів GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Інструменти плагіна GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=Оновити кеш дій -action.GitHubWorkflow.RefreshActionCache.description=Оновлює метадані знайдених віддалених GitHub Actions і повторно використовуваних workflows -action.GitHubWorkflow.RestoreActionWarnings.text=Відновити попередження дій -action.GitHubWorkflow.RestoreActionWarnings.description=Відновлює приховані попередження перевірки дій, входів і виходів +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 Actions і повторно використовуваних workflows -notification.cache.cleared=Очищено {0} кешованих записів GitHub Workflow. -notification.cache.refresh.started=Оновлюється {0} віддалених кешованих записів GitHub Workflow. -notification.warnings.restored=Попередження відновлено для {0} записів GitHub Workflow. - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +workflow.run.field.owner=Власник +workflow.run.field.repo=Репозиторій +workflow.run.field.workflow=Файл робочого процесу 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} +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=Update action [{0}] to [{1}] +inspection.action.update.major=Оновити дію [{0}] до [{1}] inspection.warning.toggle=Перемкнути попередження [{0}] для [{1}] -inspection.warning.on=увімкнено +inspection.warning.on=на inspection.warning.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.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=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.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}) -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=Кеш: {0} записів, {1} вирішено, {2} віддалених, {3} застарілих, {4} приглушених. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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 -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.job.fallbackName=Job {0} -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.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 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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 83ec0c6..6a4382a 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_vi.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_vi.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=Hỗ trợ tệp workflow của GitHub Actions -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=Công cụ của plugin GitHub Workflow -action.GitHubWorkflow.RefreshActionCache.text=Làm mới cache action -action.GitHubWorkflow.RefreshActionCache.description=Làm mới metadata đã phân giải của GitHub Actions từ xa và workflow tái sử dụng -action.GitHubWorkflow.RestoreActionWarnings.text=Khôi phục cảnh báo action -action.GitHubWorkflow.RestoreActionWarnings.description=Khôi phục cảnh báo xác thực action, input và output đã bị ẩn -action.GitHubWorkflow.ClearActionCache.text=Xóa cache action -action.GitHubWorkflow.ClearActionCache.description=Xóa metadata đã cache của GitHub Actions và workflow tái sử dụng -notification.cache.cleared=Đã xóa {0} mục cache GitHub Workflow. -notification.cache.refresh.started=Đang làm mới {0} mục cache GitHub Workflow từ xa. -notification.warnings.restored=Đã khôi phục cảnh báo cho {0} mục GitHub Workflow. - -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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run -workflow.run.auth.settings=Settings > Version Control > GitHub -workflow.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} +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=Update action [{0}] to [{1}] -inspection.warning.toggle=Bật/tắt cảnh báo [{0}] cho [{1}] -inspection.warning.on=bật +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=Incomplete statement [{0}] -inspection.invalid.suffix.remove=Remove invalid suffix [{0}] -inspection.replace.with=Replace with [{0}] -inspection.invalid.remove=Remove invalid [{0}] +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=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.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}) -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} mục, {1} đã giải quyết, {2} remote, {3} cũ, {4} đã tắt. 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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. +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=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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {0} +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 index 43985dd..6273f98 100644 --- a/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties +++ b/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties @@ -1,267 +1,442 @@ -plugin.name=GitHub Workflow -plugin.description=支持 GitHub Actions 工作流文件 -group.GitHubWorkflow.Tools.text=GitHub Workflow -group.GitHubWorkflow.Tools.description=GitHub Workflow 插件工具 -action.GitHubWorkflow.RefreshActionCache.text=刷新 Action 缓存 -action.GitHubWorkflow.RefreshActionCache.description=刷新已解析的远程 GitHub Actions 和可复用工作流元数据 -action.GitHubWorkflow.RestoreActionWarnings.text=恢复 Action 警告 -action.GitHubWorkflow.RestoreActionWarnings.description=恢复已隐藏的 Action、输入和输出校验警告 -action.GitHubWorkflow.ClearActionCache.text=清除 Action 缓存 -action.GitHubWorkflow.ClearActionCache.description=清除已缓存的 GitHub Actions 和可复用工作流元数据 -notification.cache.cleared=已清除 {0} 个 GitHub Workflow 缓存条目。 -notification.cache.refresh.started=正在刷新 {0} 个 GitHub Workflow 远程缓存条目。 -notification.warnings.restored=已恢复 {0} 个 GitHub Workflow 条目的警告。 - -workflow.run.configuration.display=GitHub Workflow -workflow.run.configuration.description=Dispatch and follow GitHub Actions workflow runs -workflow.run.configuration.name=GitHub Workflow: {0} +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=Owner -workflow.run.field.repo=Repository -workflow.run.field.workflow=Workflow file +workflow.run.field.owner=业主 +workflow.run.field.repo=存储库 +workflow.run.field.workflow=工作流程文件 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 GitHub workflow run -workflow.run.gutter.stop.text=Stop GitHub Workflow Run -workflow.run.gutter.stop.description=Cancel the running GitHub workflow run +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.cache.progress.title=Resolving GitHub actions -workflow.cache.progress.text=Resolving {0} {1} -inspection.action.delete.invalid=删除无效 {0} [{1}] -inspection.action.update.major=Update action [{0}] to [{1}] -inspection.warning.toggle=为 [{1}] 切换警告 [{0}] -inspection.warning.on=开 -inspection.warning.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.action.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.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}) -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=缓存: {0} 项,{1} 已解析,{2} 远程,{3} 过期,{4} 已静音。 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.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 - -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. -workflow.run.cancel.requested=Cancel requested for GitHub workflow run {0}. -workflow.run.stop.before.id=Stop requested before GitHub exposed a run id. -workflow.run.cancel.http=GitHub workflow cancel responded with HTTP {0}. -workflow.run.cancel.failed=Cancel failed: {0} -workflow.run.interrupted=Interrupted. -workflow.run.link=Run: {0} -workflow.run.discovery=GitHub accepted the dispatch without a run id. Looking up the newest workflow_dispatch run. -workflow.run.discovery.none=GitHub did not expose a run id yet. Open the Actions tab to follow the run. -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.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=Job: {0} -workflow.run.overview=Workflow run {0} {1}/{2} done, {3} running -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 -inspection.output.unused=Unused [{0}] -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} +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=Stacktrace -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.job.fallbackName=Job {0} -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.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. -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. -workflow.log.command=run: -workflow.log.warning=warning: -workflow.log.error=error: -workflow.cache.kind.action=action -workflow.cache.kind.workflow=workflow -inspection.parameter.input=input -inspection.parameter.secret=secret -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.button=Delete run -workflow.run.delete.tooltip=Delete this workflow run from GitHub -workflow.run.delete.noRun=GitHub has not published a run id yet, so there is nothing to delete. -workflow.run.delete.requested=Deleting GitHub workflow run {0}. -workflow.run.delete.done=GitHub workflow run {0} was deleted. -workflow.run.delete.http=GitHub workflow run delete returned HTTP {0}. -workflow.run.delete.failed=Delete failed: {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/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java b/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java index 274a34c..f1566fd 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java @@ -229,7 +229,7 @@ protected final String applyQuickFix(final String text, final String actionText) return myFixture.getEditor().getDocument().getText(); } - private List allIntentions() { + protected final List allIntentions() { return Stream.concat(myFixture.getAllQuickFixes().stream(), myFixture.getAvailableIntentions().stream()) .toList(); } diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java index d19a987..5ca16a9 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java @@ -9,6 +9,8 @@ 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; @@ -72,6 +74,49 @@ public void testLocaleBundleValuesAreNotBlank() throws IOException { } } + @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) { @@ -99,6 +144,116 @@ public void testEveryConfiguredLocaleResolvesSettingsAndInspectionMessages() { } } + @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"); @@ -118,6 +273,45 @@ public void testGermanInspectionAndCacheMessagesAreNotEnglishFallbacks() { .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)) { diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java index 3c1d372..7ee94f9 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java @@ -1,17 +1,155 @@ 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 @@ -38,6 +176,10 @@ public void testRootCompletionSuggestsAvailableContexts() { """)).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 @@ -362,6 +504,156 @@ public void testWorkflowDispatchTriggerCompletionSuggestsInputs() { """)).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 @@ -424,6 +716,320 @@ public void testWorkflowCallInputDefinitionCompletionSuggestsInputProperties() { """)).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 @@ -1331,6 +1937,18 @@ public void testRunCompletionSuggestsDefaultEnvironmentVariables() { """)).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 @@ -1343,4 +1961,28 @@ public void testShellCompletionSuggestsSupportedGithubShells() { 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/WorkflowGutterActionTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java index d9bc10c..897f7cc 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java @@ -17,11 +17,13 @@ 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 testSuppressActionGutterActionTogglesResolvedAction() { + public void testSuppressActionQuickFixTogglesResolvedAction() { final GitHubAction action = seedRemoteAction("owner/tool@v1", Map.of(), Map.of()); configureWorkflowProjectFile(""" name: Gutter @@ -33,12 +35,12 @@ public void testSuppressActionGutterActionTogglesResolvedAction() { - uses: owner/tool@v1 """); - clickGutterActionContaining("Toggle warnings [off]"); + invokeHighlightFixContaining("Toggle warnings [off]"); assertThat(action.isSuppressed()).isTrue(); } - public void testSuppressInputGutterActionTogglesResolvedInput() { + public void testSuppressInputQuickFixTogglesResolvedInput() { final GitHubAction action = seedRemoteAction("owner/tool@v1", Map.of("known-input", "Known input"), Map.of()); configureWorkflowProjectFile(""" name: Gutter @@ -52,12 +54,12 @@ public void testSuppressInputGutterActionTogglesResolvedInput() { known-input: ok """); - clickGutterActionContaining("known-input"); + invokeHighlightFixContaining("known-input"); assertThat(action.ignoredInputs()).contains("known-input"); } - public void testJumpToFileGutterActionOpensLocalActionFile() { + public void testJumpToFileQuickFixOpensLocalActionFile() { final PsiFile actionFile = myFixture.addFileToProject(".github/actions/local/action.yml", """ name: Local Action runs: @@ -77,10 +79,27 @@ public void testJumpToFileGutterActionOpensLocalActionFile() { - uses: ./.github/actions/local """); - clickGutterActionContaining("Jump to file"); + assertThat(gutterIcons()).anySatisfy(gutter -> assertThat(gutter.getTooltipText()).contains("Jump to file")); + invokeHighlightFixContaining("Jump to file"); } - public void testReloadRemoteActionGutterActionUsesResolverBoundaryWithoutSleeping() throws Exception { + 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); @@ -101,7 +120,7 @@ public void testReloadRemoteActionGutterActionUsesResolverBoundaryWithoutSleepin - uses: owner/tool@v1 """); - clickGutterActionContaining("Reload [owner/tool]"); + invokeHighlightFixContaining("Reload [owner/tool]"); assertThat(resolved.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(calls).hasValue(1); @@ -111,7 +130,7 @@ public void testReloadRemoteActionGutterActionUsesResolverBoundaryWithoutSleepin } } - public void testResolvedActionShowsOneCombinedGutterIconForAllActionFixes() { + public void testResolvedActionFixesStayOutOfTheEditorGutter() { seedRemoteAction("owner/tool@v1", Map.of(), Map.of()).remoteRefs(List.of("v1", "v2")); configureWorkflowProjectFile(""" name: Gutter @@ -128,11 +147,7 @@ public void testResolvedActionShowsOneCombinedGutterIconForAllActionFixes() { .filter(tooltip -> tooltip.contains("owner/tool")) .toList(); - assertThat(tooltips).singleElement() - .satisfies(tooltip -> assertThat(tooltip) - .contains("Reload [owner/tool]") - .contains("Toggle warnings [off] for [owner/tool]") - .contains("Update action [owner/tool@v1] to [owner/tool@v2]")); + assertThat(tooltips).isEmpty(); } public void testWorkflowDispatchShowsRunLineMarker() throws Exception { @@ -214,7 +229,7 @@ public void testWorkflowDispatchLineMarkerSwitchesToStopWhenRunIsTracked() { assertThat(info).isNotNull(); assertThat(info.icon).isEqualTo(AllIcons.Actions.Suspend); assertThat(info.actions).singleElement() - .satisfies(action -> assertThat(action.getTemplatePresentation().getText()).isEqualTo("Stop GitHub Workflow Run")); + .satisfies(action -> assertThat(action.getTemplatePresentation().getText()).isEqualTo("Stop workflow run")); } finally { WorkflowRunTracker.getInstance(getProject()).unregister(workflowPath, processHandler); } diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java index ec7f89b..04d9c07 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java @@ -4,6 +4,311 @@ 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()); diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java index 4f66e54..8f8bd8f 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java @@ -1,5 +1,8 @@ package com.github.yunabraska.githubworkflow.services; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.util.Iconable; + import java.util.Map; import java.util.List; @@ -42,13 +45,59 @@ public void testInspectionQuickFixTextsUseConfiguredPluginLanguage() { wrong-input: no """)) .anyMatch(text -> text.contains("Neu laden [owner/tool]")) - .anyMatch(text -> text.contains("Warnungen [aus] für [owner/tool]")) - .anyMatch(text -> text.contains("Ungültige input löschen [wrong-input]")); + .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 @@ -202,6 +251,19 @@ public void testInvalidNeedsMemberQuickFixRemovesMember() { 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 diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java index beb1312..c5b7b62 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java @@ -53,17 +53,23 @@ public void testStatusCancelJobsAndLogsUseRunEndpoints() throws Exception { 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" ); @@ -98,6 +104,23 @@ public void testLatestRunDiscoversNewestWorkflowDispatchRun() throws Exception { } } + 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(); @@ -338,12 +361,21 @@ private Response responseFor(final String path) { 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"); } diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java index 81c3159..123c14d 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java @@ -127,7 +127,7 @@ public void processTerminated(@NotNull final ProcessEvent event) { assertThat(cancelSeen.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(terminated.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(output.toString()).contains("Cancel requested for GitHub workflow run 42."); + assertThat(output.toString()).contains("Cancel requested: 42."); assertThat(jobConsole.finished()).containsExactly("42:cancelled"); } @@ -168,11 +168,56 @@ public void processTerminated(@NotNull final ProcessEvent event) { handler.deleteRemoteRun(); assertThat(deleteSeen.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(waitForWorkflowOutput(jobConsole, "GitHub workflow run 42 was deleted.")).isTrue(); - assertThat(jobConsole.workflowOutput()).contains("Deleting GitHub workflow run 42.", "GitHub workflow run 42 was deleted."); + 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(); @@ -518,6 +563,32 @@ private static HttpResponse completedRunWithDeleteResponseFor( 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, @@ -626,6 +697,16 @@ private static boolean waitForWorkflowOutput(final CapturingJobConsole console, 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() { @@ -654,6 +735,7 @@ public Optional sslSession() { } 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<>(); @@ -685,37 +767,53 @@ public boolean jobLog(final WorkflowRunClient.JobStatus job, final String text) @Override public void workflowStatus(final String text, final boolean error) { - workflowOutput.append(text); + synchronized (lock) { + workflowOutput.append(text); + } } @Override public void runFinished(final long runId, final String conclusion) { - finished.add(runId + ":" + conclusion); + synchronized (lock) { + finished.add(runId + ":" + conclusion); + } } @Override public void runDeleted(final long runId) { - deleted.add(runId); + synchronized (lock) { + deleted.add(runId); + } } private void append(final WorkflowRunClient.JobStatus job, final String text) { - output.computeIfAbsent(job.id(), ignored -> new StringBuilder()).append(text); + synchronized (lock) { + output.computeIfAbsent(job.id(), ignored -> new StringBuilder()).append(text); + } } private String output(final long jobId) { - return output.getOrDefault(jobId, new StringBuilder()).toString(); + synchronized (lock) { + return output.getOrDefault(jobId, new StringBuilder()).toString(); + } } private String workflowOutput() { - return workflowOutput.toString(); + synchronized (lock) { + return workflowOutput.toString(); + } } private List finished() { - return List.copyOf(finished); + synchronized (lock) { + return List.copyOf(finished); + } } private List deleted() { - return List.copyOf(deleted); + synchronized (lock) { + return List.copyOf(deleted); + } } } } From cbe1fb0b2fa08de0e4f1a979a9c28de2b3ce471d Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 15:14:16 +0200 Subject: [PATCH 09/17] Ensure release notes publish from changelog --- .github/workflows/release.yml | 8 ++++++++ .github/workflows/tag.yml | 23 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b29079..4fef1d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,6 +70,14 @@ jobs: 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 diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index d196791..2b41acc 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -95,12 +95,29 @@ jobs: ' gradle.properties > gradle.properties.tmp mv gradle.properties.tmp gradle.properties - if ! git diff --quiet -- gradle.properties; then + 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 + + 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 + git add gradle.properties CHANGELOG.md if [ "$DRY_RUN" = "true" ]; then - echo "Dry run: would commit pluginVersion $RELEASE_VERSION" + echo "Dry run: would commit release metadata $RELEASE_VERSION" else git commit -m "chore: release $RELEASE_VERSION" fi From 731b5e047cbbc605b68daca5a7d6f00fbf29c965 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 15:20:44 +0200 Subject: [PATCH 10/17] Trigger releases from main merges --- .github/workflows/tag.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 2b41acc..6386396 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -1,9 +1,7 @@ name: Tag on: - pull_request: - types: - - closed + push: branches: - main workflow_dispatch: @@ -23,13 +21,13 @@ permissions: contents: write concurrency: - group: tag-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} + group: tag-${{ github.event_name }}-${{ github.ref || github.sha }} cancel-in-progress: false jobs: tag: name: Test, Verify, Build, Tag - if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true + if: "${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main' && !startsWith(github.event.head_commit.message || '', 'chore: release ')) }}" runs-on: ubuntu-latest timeout-minutes: 60 @@ -147,10 +145,7 @@ jobs: git remote set-url origin "https://x-access-token:${RELEASE_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" fi - target_branch="$GITHUB_REF_NAME" - if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - target_branch="${GITHUB_BASE_REF:-main}" - fi + target_branch="${GITHUB_REF_NAME:-main}" git push origin "HEAD:$target_branch" git tag -a "$RELEASE_TAG" -m "$RELEASE_TAG" From b4b61665b42d8546aee225dafcaaf890b72f7977 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 15:33:09 +0200 Subject: [PATCH 11/17] Stabilize cache refresh during editor tests --- .../services/GitHubActionCache.java | 7 +++++- .../services/WorkflowQuickFixTest.java | 24 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) 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 6c7e3de..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; @@ -417,7 +418,11 @@ private static void jitterBeforeRemoteRequest(final GitHubAction action) { } public static void triggerSyntaxHighlightingForActiveFiles() { - ApplicationManager.getApplication().invokeLater(() -> + final Application application = ApplicationManager.getApplication(); + if (application.isUnitTestMode()) { + return; + } + application.invokeLater(() -> Stream.of(ProjectManager.getInstance().getOpenProjects()).forEach(GitHubActionCache::triggerSyntaxHighlightingForActiveFiles) ); } diff --git a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java index 8f8bd8f..c15ffc1 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java @@ -2,11 +2,13 @@ import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.openapi.util.Iconable; +import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; -import java.util.Map; 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 { @@ -163,6 +165,26 @@ public void testUnusedJobOutputProvidesDeleteQuickFix() { """)).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 From 44c8cb0f3dd5efb7a1e1953f0d06044b50a5ab2e Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 16:09:33 +0200 Subject: [PATCH 12/17] Speed up plugin workflows with caches --- .github/actions/setup-plugin-build/action.yml | 53 +++++++++++++++++++ .github/workflows/build.yml | 28 +++++----- .github/workflows/publish-marketplace.yml | 10 ++-- .github/workflows/release.yml | 10 ++-- .github/workflows/tag.yml | 22 +++++--- build.gradle | 6 +++ gradle.properties | 4 ++ 7 files changed, 100 insertions(+), 33 deletions(-) create mode 100644 .github/actions/setup-plugin-build/action.yml diff --git a/.github/actions/setup-plugin-build/action.yml b/.github/actions/setup-plugin-build/action.yml new file mode 100644 index 0000000..71ea633 --- /dev/null +++ b/.github/actions/setup-plugin-build/action.yml @@ -0,0 +1,53 @@ +name: Setup Plugin Build +description: Set up Java, IntelliJ Platform caches, and Gradle for plugin workflows. + +inputs: + gradle-cache-read-only: + description: Whether Gradle User Home cache should be read-only. + required: false + default: 'true' + +outputs: + intellij-cache-key: + description: Primary key used for the IntelliJ Platform cache. + value: ${{ steps.intellij-cache-key.outputs.key }} + intellij-cache-hit: + description: Whether the IntelliJ Platform cache had an exact hit. + value: ${{ steps.intellij-cache.outputs.cache-hit }} + +runs: + using: composite + steps: + - name: Resolve IntelliJ cache key + id: intellij-cache-key + shell: sh + run: | + { + sed '/^[[:space:]]*pluginVersion[[:space:]]*=/d' gradle.properties + cat build.gradle settings.gradle gradle/wrapper/gradle-wrapper.properties + } | sha256sum | awk '{ print "key=intellij-platform-${{ runner.os }}-" $1 }' >> "$GITHUB_OUTPUT" + + - name: Restore IntelliJ Platform cache + id: intellij-cache + uses: actions/cache/restore@v4 + with: + path: | + .intellijPlatform/ides + .intellijPlatform/localPlatformArtifacts + .intellijPlatform/layoutIndex + .intellijPlatform/*.jar + key: ${{ steps.intellij-cache-key.outputs.key }} + restore-keys: | + intellij-platform-${{ runner.os }}- + + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 25 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v6 + with: + cache-read-only: ${{ inputs.gradle-cache-read-only }} + add-job-summary: on-failure diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f02f662..f029105 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,22 +30,26 @@ jobs: - name: Fetch sources uses: actions/checkout@v6 - - name: Set up Java - uses: actions/setup-java@v5 + - name: Set up plugin build + id: plugin-build + uses: ./.github/actions/setup-plugin-build with: - distribution: temurin - java-version: 25 + gradle-cache-read-only: ${{ github.event_name == 'pull_request' }} - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v6 - - - name: Test and verify plugin + - name: Test, verify, and build plugin shell: sh - run: ./gradlew --no-daemon check verifyPlugin --warning-mode all + run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all - - name: Build plugin archive - shell: sh - run: ./gradlew --no-daemon buildPlugin --warning-mode all + - name: Save IntelliJ Platform cache + if: success() && steps.plugin-build.outputs.intellij-cache-hit != 'true' && github.event_name != 'pull_request' + uses: actions/cache/save@v4 + with: + path: | + .intellijPlatform/ides + .intellijPlatform/localPlatformArtifacts + .intellijPlatform/layoutIndex + .intellijPlatform/*.jar + key: ${{ steps.plugin-build.outputs.intellij-cache-key }} - name: Upload test reports if: failure() diff --git a/.github/workflows/publish-marketplace.yml b/.github/workflows/publish-marketplace.yml index 5ce918c..4c92241 100644 --- a/.github/workflows/publish-marketplace.yml +++ b/.github/workflows/publish-marketplace.yml @@ -46,14 +46,10 @@ jobs: with: ref: ${{ env.RELEASE_TAG }} - - name: Set up Java - uses: actions/setup-java@v5 + - name: Set up plugin build + uses: ./.github/actions/setup-plugin-build with: - distribution: temurin - java-version: 25 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v6 + gradle-cache-read-only: 'true' - name: Test, verify, and build plugin shell: sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4fef1d9..c4500c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,14 +47,10 @@ jobs: with: ref: ${{ env.RELEASE_TAG }} - - name: Set up Java - uses: actions/setup-java@v5 + - name: Set up plugin build + uses: ./.github/actions/setup-plugin-build with: - distribution: temurin - java-version: 25 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v6 + gradle-cache-read-only: 'true' - name: Test, verify, and build plugin shell: sh diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 6386396..ba1edf9 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -38,14 +38,11 @@ jobs: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN || github.token }} - - name: Set up Java - uses: actions/setup-java@v5 + - name: Set up plugin build + id: plugin-build + uses: ./.github/actions/setup-plugin-build with: - distribution: temurin - java-version: 25 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v6 + gradle-cache-read-only: ${{ github.event_name != 'push' || github.ref != 'refs/heads/main' }} - name: Resolve release tag id: tag @@ -125,6 +122,17 @@ jobs: shell: sh run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all + - name: Save IntelliJ Platform cache + if: success() && steps.plugin-build.outputs.intellij-cache-hit != 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/cache/save@v4 + with: + path: | + .intellijPlatform/ides + .intellijPlatform/localPlatformArtifacts + .intellijPlatform/layoutIndex + .intellijPlatform/*.jar + key: ${{ steps.plugin-build.outputs.intellij-cache-key }} + - name: Push release commit and tag shell: sh env: diff --git a/build.gradle b/build.gradle index 61aa35e..723b584 100644 --- a/build.gradle +++ b/build.gradle @@ -203,6 +203,12 @@ jacocoTestReport { } intellijPlatform { + caching { + ides { + enabled = true + } + } + pluginConfiguration { id = requiredProperty('pluginId') name = requiredProperty('pluginName') diff --git a/gradle.properties b/gradle.properties index f2ebc43..9d175f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,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 From 218d3f803479a04fef7a3a76756bacf8295ecff3 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 18:20:12 +0200 Subject: [PATCH 13/17] Simplify release pipeline --- .github/actions/setup-plugin-build/action.yml | 53 --- .github/workflows/build.yml | 362 ++++++++++++++++-- .github/workflows/publish-marketplace.yml | 100 ----- .github/workflows/release.yml | 105 ----- .github/workflows/tag.yml | 164 -------- README.md | 25 ++ build.gradle | 47 +++ .../0011-release-once-publish-signed-zip.md | 35 ++ 8 files changed, 445 insertions(+), 446 deletions(-) delete mode 100644 .github/actions/setup-plugin-build/action.yml delete mode 100644 .github/workflows/publish-marketplace.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/tag.yml create mode 100644 doc/adr/0011-release-once-publish-signed-zip.md diff --git a/.github/actions/setup-plugin-build/action.yml b/.github/actions/setup-plugin-build/action.yml deleted file mode 100644 index 71ea633..0000000 --- a/.github/actions/setup-plugin-build/action.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Setup Plugin Build -description: Set up Java, IntelliJ Platform caches, and Gradle for plugin workflows. - -inputs: - gradle-cache-read-only: - description: Whether Gradle User Home cache should be read-only. - required: false - default: 'true' - -outputs: - intellij-cache-key: - description: Primary key used for the IntelliJ Platform cache. - value: ${{ steps.intellij-cache-key.outputs.key }} - intellij-cache-hit: - description: Whether the IntelliJ Platform cache had an exact hit. - value: ${{ steps.intellij-cache.outputs.cache-hit }} - -runs: - using: composite - steps: - - name: Resolve IntelliJ cache key - id: intellij-cache-key - shell: sh - run: | - { - sed '/^[[:space:]]*pluginVersion[[:space:]]*=/d' gradle.properties - cat build.gradle settings.gradle gradle/wrapper/gradle-wrapper.properties - } | sha256sum | awk '{ print "key=intellij-platform-${{ runner.os }}-" $1 }' >> "$GITHUB_OUTPUT" - - - name: Restore IntelliJ Platform cache - id: intellij-cache - uses: actions/cache/restore@v4 - with: - path: | - .intellijPlatform/ides - .intellijPlatform/localPlatformArtifacts - .intellijPlatform/layoutIndex - .intellijPlatform/*.jar - key: ${{ steps.intellij-cache-key.outputs.key }} - restore-keys: | - intellij-platform-${{ runner.os }}- - - - name: Set up Java - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: 25 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v6 - with: - cache-read-only: ${{ inputs.gradle-cache-read-only }} - add-job-summary: on-failure diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f029105..13f53e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,17 @@ -name: Build +name: Pipeline on: workflow_dispatch: + 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: - '**' @@ -13,45 +23,350 @@ on: - ready_for_review permissions: - contents: read + actions: write + contents: write + packages: write + +concurrency: + group: pipeline-${{ github.event.pull_request.number || github.ref || github.run_id }} + cancel-in-progress: false jobs: - build: - name: Test, Verify, Package + pipeline: + name: Test, Build, Release runs-on: ubuntu-latest timeout-minutes: 60 + env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - - name: Free disk space + - name: 🧹 Free disk space shell: sh run: | sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc - - name: Fetch sources + - name: 📥 Fetch sources uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.RELEASE_TOKEN || github.token }} + + - name: 🧭 Decide mode + id: mode + shell: sh + env: + HEAD_MESSAGE: ${{ github.event.head_commit.message || '' }} + INPUT_DRY_RUN: ${{ inputs.dry_run || 'false' }} + INPUT_TAG: ${{ inputs.tag || '' }} + run: | + release="false" + dry_run="false" + + 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 + + release_tag="" + release_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 - - name: Set up plugin build - id: plugin-build - uses: ./.github/actions/setup-plugin-build + 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 + release_version="${release_tag#v}" + fi + + echo "release=$release" >> "$GITHUB_OUTPUT" + echo "dry_run=$dry_run" >> "$GITHUB_OUTPUT" + echo "tag=$release_tag" >> "$GITHUB_OUTPUT" + echo "version=$release_version" >> "$GITHUB_OUTPUT" + + - name: 🗝️ Resolve cache key + id: cache-key + shell: sh + run: | + { + 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" + + - name: ♻️ Restore cache + id: cache + uses: actions/cache/restore@v4 with: - gradle-cache-read-only: ${{ github.event_name == 'pull_request' }} + path: | + ~/.gradle/caches/transforms-* + ~/.gradle/caches/*/transforms + ~/.gradle/wrapper + key: ${{ steps.cache-key.outputs.key }} + restore-keys: | + plugin-ci-${{ runner.os }}- + + - name: ☕ Set up Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 25 + + - name: 🏷️ Prepare release metadata + if: steps.mode.outputs.release == 'true' + shell: sh + env: + DRY_RUN: ${{ steps.mode.outputs.dry_run }} + RELEASE_TAG: ${{ steps.mode.outputs.tag }} + RELEASE_VERSION: ${{ steps.mode.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 + + 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 + + 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 + + - name: 🔐 Validate release secrets + if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + shell: sh + env: + CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} + PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} + PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + run: | + missing="" + if [ -z "$CERTIFICATE_CHAIN" ]; then + missing="$missing CERTIFICATE_CHAIN" + fi + if [ -z "$PRIVATE_KEY" ]; then + missing="$missing PRIVATE_KEY" + fi + if [ -z "$PRIVATE_KEY_PASSWORD" ]; then + missing="$missing PRIVATE_KEY_PASSWORD" + fi + if [ -z "$PUBLISH_TOKEN" ]; then + missing="$missing PUBLISH_TOKEN" + fi + + if [ -n "$missing" ]; then + echo "Missing release secret(s):$missing" >&2 + exit 1 + fi + + - name: 🧪 Test and package + shell: sh + env: + CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} + PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} + PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} + RELEASE: ${{ steps.mode.outputs.release }} + DRY_RUN: ${{ steps.mode.outputs.dry_run }} + run: | + if [ "$RELEASE" = "true" ] && [ "$DRY_RUN" != "true" ]; then + ./gradlew --no-daemon check verifyPlugin signPlugin verifyPluginSignature --warning-mode all + elif [ "$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.mode.outputs.release == 'true' + shell: sh + env: + DRY_RUN: ${{ steps.mode.outputs.dry_run }} + run: | + if [ "$DRY_RUN" = "true" ]; then + archive="$(find build/distributions -maxdepth 1 -type f -name '*.zip' | sort | head -n 1)" + else + archive="$(find build/distributions -maxdepth 1 -type f -name '*-signed.zip' | sort | head -n 1)" + fi + + if [ -z "$archive" ]; then + echo "No release archive found." >&2 + exit 1 + fi + + echo "path=$archive" >> "$GITHUB_OUTPUT" + echo "name=$(basename "$archive")" >> "$GITHUB_OUTPUT" + + - name: 📝 Extract release notes + if: steps.mode.outputs.release == 'true' + shell: sh + env: + RELEASE_TAG: ${{ steps.mode.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: Test, verify, and build plugin + - name: 🚀 Create GitHub release + if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' shell: sh - run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all + env: + ARCHIVE_PATH: ${{ steps.archive.outputs.path }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} + RELEASE_TAG: ${{ steps.mode.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 + fi - - name: Save IntelliJ Platform cache - if: success() && steps.plugin-build.outputs.intellij-cache-hit != 'true' && github.event_name != 'pull_request' + - name: 📚 Publish GitHub package + if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + shell: sh + env: + CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ github.token }} + PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} + PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} + run: | + ./gradlew --no-daemon publishPluginZipPublicationToGitHubPackagesRepository --warning-mode all + + - name: 🛒 Publish Marketplace + if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + shell: sh + env: + ARCHIVE_PATH: ${{ steps.archive.outputs.path }} + MARKETPLACE_CHANNEL: ${{ vars.MARKETPLACE_CHANNEL || '' }} + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + run: | + plugin_id="$(sed -n 's/^[[:space:]]*pluginId[[:space:]]*=[[:space:]]*//p' gradle.properties | head -n 1 | tr -d '\r')" + if [ -z "$plugin_id" ]; then + echo "Missing pluginId in gradle.properties." >&2 + exit 1 + fi + + if [ -n "$MARKETPLACE_CHANNEL" ]; then + curl --fail-with-body --retry 3 --retry-delay 10 \ + --header "Authorization: Bearer $PUBLISH_TOKEN" \ + -F "xmlId=$plugin_id" \ + -F "file=@$ARCHIVE_PATH" \ + -F "channel=$MARKETPLACE_CHANNEL" \ + https://plugins.jetbrains.com/api/updates/upload + else + curl --fail-with-body --retry 3 --retry-delay 10 \ + --header "Authorization: Bearer $PUBLISH_TOKEN" \ + -F "xmlId=$plugin_id" \ + -F "file=@$ARCHIVE_PATH" \ + https://plugins.jetbrains.com/api/updates/upload + fi + + - name: 📤 Push release commit and tag + if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + shell: sh + env: + RELEASE_TAG: ${{ steps.mode.outputs.tag }} + RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + run: | + git config user.name "Kira" + git config user.email "kira@yuna.berlin" + + 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: 💾 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: | - .intellijPlatform/ides - .intellijPlatform/localPlatformArtifacts - .intellijPlatform/layoutIndex - .intellijPlatform/*.jar - key: ${{ steps.plugin-build.outputs.intellij-cache-key }} + ~/.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: + CACHE_KEY: ${{ steps.cache-key.outputs.key }} + GH_TOKEN: ${{ github.token }} + run: | + 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 + - name: 📊 Upload test reports if: failure() uses: actions/upload-artifact@v7 with: @@ -60,15 +375,14 @@ jobs: build/reports/tests build/reports/jacoco - - name: Upload verifier reports - id: upload - if: always() + - name: 🧾 Upload verifier reports + if: always() && steps.mode.outputs.release == 'true' uses: actions/upload-artifact@v7 with: name: plugin-verifier-reports path: build/reports/pluginVerifier - - name: Upload plugin archive + - name: 📮 Upload plugin archive if: success() uses: actions/upload-artifact@v7 with: diff --git a/.github/workflows/publish-marketplace.yml b/.github/workflows/publish-marketplace.yml deleted file mode 100644 index 4c92241..0000000 --- a/.github/workflows/publish-marketplace.yml +++ /dev/null @@ -1,100 +0,0 @@ -name: Publish Marketplace - -on: - release: - types: - - published - workflow_dispatch: - inputs: - tag: - description: Release tag to publish or dry-run. - required: true - type: string - dry_run: - description: Build and validate without publishing to JetBrains Marketplace. - required: true - default: true - type: boolean - -permissions: - contents: read - -concurrency: - group: publish-marketplace-${{ github.event.release.tag_name || inputs.tag }} - cancel-in-progress: false - -jobs: - publish: - name: Publish JetBrains Marketplace - runs-on: ubuntu-latest - timeout-minutes: 60 - env: - RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }} - DRY_RUN: ${{ inputs.dry_run || 'false' }} - - steps: - - name: Validate tag - shell: sh - run: | - 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 - - - name: Fetch sources - uses: actions/checkout@v6 - with: - ref: ${{ env.RELEASE_TAG }} - - - name: Set up plugin build - uses: ./.github/actions/setup-plugin-build - with: - gradle-cache-read-only: 'true' - - - name: Test, verify, and build plugin - shell: sh - run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all - - - name: Validate Marketplace secrets - if: env.DRY_RUN != 'true' - shell: sh - env: - PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} - CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} - run: | - missing="" - if [ -z "$PUBLISH_TOKEN" ]; then - missing="$missing PUBLISH_TOKEN" - fi - if [ -z "$CERTIFICATE_CHAIN" ]; then - missing="$missing CERTIFICATE_CHAIN" - fi - if [ -z "$PRIVATE_KEY" ]; then - missing="$missing PRIVATE_KEY" - fi - if [ -z "$PRIVATE_KEY_PASSWORD" ]; then - missing="$missing PRIVATE_KEY_PASSWORD" - fi - - if [ -n "$missing" ]; then - echo "Missing Marketplace secret(s):$missing" >&2 - exit 1 - fi - - - name: Publish plugin - if: env.DRY_RUN != 'true' - shell: sh - 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 --no-daemon publishPlugin --warning-mode all - - - name: Dry-run summary - if: env.DRY_RUN == 'true' - shell: sh - run: | - echo "Dry run: Marketplace publish skipped for $RELEASE_TAG" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index c4500c8..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,105 +0,0 @@ -name: Release - -on: - push: - tags: - - v* - workflow_dispatch: - inputs: - tag: - description: Existing tag to publish as a GitHub release. - required: true - type: string - dry_run: - description: Build and validate without creating or updating a GitHub release. - required: true - default: false - type: boolean - -permissions: - actions: write - contents: write - -concurrency: - group: release-${{ github.ref_name || inputs.tag }} - cancel-in-progress: false - -jobs: - release: - name: Build GitHub Release - runs-on: ubuntu-latest - timeout-minutes: 60 - env: - RELEASE_TAG: ${{ inputs.tag || github.ref_name }} - DRY_RUN: ${{ inputs.dry_run || 'false' }} - - steps: - - name: Validate tag - shell: sh - run: | - 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 - - - name: Fetch sources - uses: actions/checkout@v6 - with: - ref: ${{ env.RELEASE_TAG }} - - - name: Set up plugin build - uses: ./.github/actions/setup-plugin-build - with: - gradle-cache-read-only: 'true' - - - name: Test, verify, and build plugin - shell: sh - run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all - - - name: Extract release notes - shell: sh - 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: Create or update GitHub release - shell: sh - env: - GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} - run: | - if [ "$DRY_RUN" = "true" ]; then - echo "Dry run: would create or update GitHub release $RELEASE_TAG" - exit 0 - fi - - if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then - gh release upload "$RELEASE_TAG" ./build/distributions/*.zip --clobber - gh release edit "$RELEASE_TAG" --title "$RELEASE_TAG" --notes-file release-notes.md - else - gh release create "$RELEASE_TAG" ./build/distributions/*.zip --title "$RELEASE_TAG" --notes-file release-notes.md - fi - - if [ -z "$RELEASE_TOKEN" ]; then - if gh workflow view publish-marketplace.yml >/dev/null 2>&1; then - gh workflow run publish-marketplace.yml --ref "$RELEASE_TAG" -f tag="$RELEASE_TAG" -f dry_run=false - else - echo "Publish workflow is not on the default branch yet; release event automation will take over after merge." - fi - fi diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml deleted file mode 100644 index ba1edf9..0000000 --- a/.github/workflows/tag.yml +++ /dev/null @@ -1,164 +0,0 @@ -name: Tag - -on: - push: - branches: - - main - workflow_dispatch: - inputs: - tag: - description: Release tag to create. Defaults to today's vYYYY.M.D. - required: false - type: string - dry_run: - description: Build and validate without pushing a tag. - required: true - default: true - type: boolean - -permissions: - actions: write - contents: write - -concurrency: - group: tag-${{ github.event_name }}-${{ github.ref || github.sha }} - cancel-in-progress: false - -jobs: - tag: - name: Test, Verify, Build, Tag - if: "${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main' && !startsWith(github.event.head_commit.message || '', 'chore: release ')) }}" - runs-on: ubuntu-latest - timeout-minutes: 60 - - steps: - - name: Fetch sources - uses: actions/checkout@v6 - with: - fetch-depth: 0 - token: ${{ secrets.RELEASE_TOKEN || github.token }} - - - name: Set up plugin build - id: plugin-build - uses: ./.github/actions/setup-plugin-build - with: - gradle-cache-read-only: ${{ github.event_name != 'push' || github.ref != 'refs/heads/main' }} - - - name: Resolve release tag - id: tag - shell: sh - env: - INPUT_TAG: ${{ inputs.tag }} - run: | - 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//')" - version="$year.$month.$day" - release_tag="v$version" - 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 - - echo "tag=$release_tag" >> "$GITHUB_OUTPUT" - echo "version=${release_tag#v}" >> "$GITHUB_OUTPUT" - - - name: Prepare release version - shell: sh - env: - DRY_RUN: ${{ inputs.dry_run || 'false' }} - RELEASE_VERSION: ${{ steps.tag.outputs.version }} - RELEASE_TAG: ${{ steps.tag.outputs.tag }} - 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 - - 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 - - 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" - fi - fi - - - name: Test, verify, and build plugin - shell: sh - run: ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all - - - name: Save IntelliJ Platform cache - if: success() && steps.plugin-build.outputs.intellij-cache-hit != 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: actions/cache/save@v4 - with: - path: | - .intellijPlatform/ides - .intellijPlatform/localPlatformArtifacts - .intellijPlatform/layoutIndex - .intellijPlatform/*.jar - key: ${{ steps.plugin-build.outputs.intellij-cache-key }} - - - name: Push release commit and tag - shell: sh - env: - DRY_RUN: ${{ inputs.dry_run || 'false' }} - GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} - RELEASE_TAG: ${{ steps.tag.outputs.tag }} - run: | - if [ "$DRY_RUN" = "true" ]; then - echo "Dry run: would create $RELEASE_TAG" - exit 0 - fi - - git config user.name "Kira" - git config user.email "kira@yuna.berlin" - - 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" - git push origin "refs/tags/$RELEASE_TAG" - - if [ -z "$RELEASE_TOKEN" ]; then - gh workflow run release.yml --ref "$RELEASE_TAG" -f tag="$RELEASE_TAG" - fi diff --git a/README.md b/README.md index a0e3cc7..483997d 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,31 @@ Plugin downloads the IDE, bundled plugins, verifier, and test runtime. 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, signs the ZIP, creates the GitHub release, publishes the signed ZIP to GitHub +Packages, and uploads the same signed ZIP to JetBrains Marketplace. + +The workflow prunes old GitHub Actions caches after a successful non-PR run so only the current pipeline cache remains. + +Required repository secrets: + +* `CERTIFICATE_CHAIN` +* `PRIVATE_KEY` +* `PRIVATE_KEY_PASSWORD` +* `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 diff --git a/build.gradle b/build.gradle index 723b584..6d79306 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,7 @@ 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' } @@ -75,6 +76,52 @@ repositories { } } +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('signPlugin').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.26.3' diff --git a/doc/adr/0011-release-once-publish-signed-zip.md b/doc/adr/0011-release-once-publish-signed-zip.md new file mode 100644 index 0000000..d0e573e --- /dev/null +++ b/doc/adr/0011-release-once-publish-signed-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, signs the plugin ZIP, creates or +updates the GitHub release, publishes the signed ZIP to GitHub Packages, and uploads the same signed ZIP directly to +JetBrains Marketplace. + +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 signed artifact attached to the GitHub release. +- Release publishing requires signing secrets and `PUBLISH_TOKEN`. +- The workflow has more conditional shell logic, but fewer moving GitHub Actions parts. From 00638f5f4811823648a7195ae529c1365336a35b Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 19:01:34 +0200 Subject: [PATCH 14/17] Release without signing secrets --- .github/workflows/build.yml | 56 ++++++------------- README.md | 7 +-- build.gradle | 8 +-- ...ip.md => 0011-release-once-publish-zip.md} | 10 ++-- 4 files changed, 25 insertions(+), 56 deletions(-) rename doc/adr/{0011-release-once-publish-signed-zip.md => 0011-release-once-publish-zip.md} (74%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13f53e2..af660f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -177,21 +177,9 @@ jobs: if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' shell: sh env: - CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} run: | missing="" - if [ -z "$CERTIFICATE_CHAIN" ]; then - missing="$missing CERTIFICATE_CHAIN" - fi - if [ -z "$PRIVATE_KEY" ]; then - missing="$missing PRIVATE_KEY" - fi - if [ -z "$PRIVATE_KEY_PASSWORD" ]; then - missing="$missing PRIVATE_KEY_PASSWORD" - fi if [ -z "$PUBLISH_TOKEN" ]; then missing="$missing PUBLISH_TOKEN" fi @@ -204,14 +192,11 @@ jobs: - name: 🧪 Test and package shell: sh env: - CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} RELEASE: ${{ steps.mode.outputs.release }} DRY_RUN: ${{ steps.mode.outputs.dry_run }} run: | if [ "$RELEASE" = "true" ] && [ "$DRY_RUN" != "true" ]; then - ./gradlew --no-daemon check verifyPlugin signPlugin verifyPluginSignature --warning-mode all + ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all elif [ "$RELEASE" = "true" ]; then ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all else @@ -225,11 +210,7 @@ jobs: env: DRY_RUN: ${{ steps.mode.outputs.dry_run }} run: | - if [ "$DRY_RUN" = "true" ]; then - archive="$(find build/distributions -maxdepth 1 -type f -name '*.zip' | sort | head -n 1)" - else - archive="$(find build/distributions -maxdepth 1 -type f -name '*-signed.zip' | sort | head -n 1)" - fi + archive="$(find build/distributions -maxdepth 1 -type f -name '*.zip' | sort | head -n 1)" if [ -z "$archive" ]; then echo "No release archive found." >&2 @@ -264,30 +245,12 @@ jobs: printf 'Plugin release %s\n' "$RELEASE_TAG" > release-notes.md fi - - name: 🚀 Create GitHub release - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' - shell: sh - env: - ARCHIVE_PATH: ${{ steps.archive.outputs.path }} - GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} - RELEASE_TAG: ${{ steps.mode.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 - fi - - name: 📚 Publish GitHub package if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' shell: sh env: - CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} run: | ./gradlew --no-daemon publishPluginZipPublicationToGitHubPackagesRepository --warning-mode all @@ -339,6 +302,21 @@ jobs: git tag -a "$RELEASE_TAG" -m "$RELEASE_TAG [skip ci]" git push origin "refs/tags/$RELEASE_TAG" + - name: 🚀 Create GitHub release + if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + shell: sh + env: + ARCHIVE_PATH: ${{ steps.archive.outputs.path }} + GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} + RELEASE_TAG: ${{ steps.mode.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 diff --git a/README.md b/README.md index 483997d..a6da033 100644 --- a/README.md +++ b/README.md @@ -81,16 +81,13 @@ Plugin downloads the IDE, bundled plugins, verifier, and test runtime. 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, signs the ZIP, creates the GitHub release, publishes the signed ZIP to GitHub -Packages, and uploads the same signed ZIP to JetBrains Marketplace. +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: -* `CERTIFICATE_CHAIN` -* `PRIVATE_KEY` -* `PRIVATE_KEY_PASSWORD` * `PUBLISH_TOKEN` Optional repository secret: diff --git a/build.gradle b/build.gradle index 6d79306..2a8d98a 100644 --- a/build.gradle +++ b/build.gradle @@ -93,7 +93,7 @@ publishing { groupId = requiredProperty('pluginGroup') artifactId = rootProject.name version = requiredProperty('pluginVersion') - artifact(tasks.named('signPlugin').flatMap { it.archiveFile }) { + artifact(tasks.named('buildPlugin').flatMap { it.archiveFile }) { extension = 'zip' } pom { @@ -293,12 +293,6 @@ intellijPlatform { publishing { token = providers.environmentVariable('PUBLISH_TOKEN') } - - signing { - certificateChain = providers.environmentVariable('CERTIFICATE_CHAIN') - privateKey = providers.environmentVariable('PRIVATE_KEY') - password = providers.environmentVariable('PRIVATE_KEY_PASSWORD') - } } changelog { diff --git a/doc/adr/0011-release-once-publish-signed-zip.md b/doc/adr/0011-release-once-publish-zip.md similarity index 74% rename from doc/adr/0011-release-once-publish-signed-zip.md rename to doc/adr/0011-release-once-publish-zip.md index d0e573e..bdcb73a 100644 --- a/doc/adr/0011-release-once-publish-signed-zip.md +++ b/doc/adr/0011-release-once-publish-zip.md @@ -20,9 +20,9 @@ The same job handles all modes: - 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, signs the plugin ZIP, creates or -updates the GitHub release, publishes the signed ZIP to GitHub Packages, and uploads the same signed ZIP directly to -JetBrains Marketplace. +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. @@ -30,6 +30,6 @@ After a successful non-PR run, the job prunes every GitHub Actions cache entry e - 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 signed artifact attached to the GitHub release. -- Release publishing requires signing secrets and `PUBLISH_TOKEN`. +- 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. From a26298c023114f51b8fae85ddec1efe3575fedd4 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 19:01:50 +0200 Subject: [PATCH 15/17] chore: release revert failed merge [skip ci] --- .github/dependabot.yml | 8 +- .github/workflows/build.yml | 501 ++-- .github/workflows/release.yml | 97 + .github/workflows/run-ui-tests.yml | 59 + .github/workflows/show_case.yml | 116 + CHANGELOG.md | 39 - CONTRIBUTING.md | 13 +- README.md | 118 +- build.gradle | 307 --- build.gradle.kts | 120 + ...001-use-gradle-wrapper-for-plugin-build.md | 33 - ...02-configurable-remote-action-providers.md | 35 - ...e-verifier-clean-documentation-provider.md | 36 - ...for-cache-controls-and-resource-bundles.md | 35 - ...nd-remote-discovery-and-testable-reload.md | 34 - ...se-generated-github-docs-data-snapshots.md | 21 - ...-use-date-based-semver-and-242-baseline.md | 34 - ...t-through-editor-and-runtime-boundaries.md | 32 - ...n-configurations-for-workflow-execution.md | 33 - ...t-matched-github-account-authentication.md | 34 - .../0011-release-once-publish-signed-zip.md | 35 - doc/navigation.md | 36 - doc/spec/editor-test-matrix.md | 125 - {doc => docs}/DataPrivacy.md | 12 +- {doc => docs}/ghw_arch.png | Bin {doc => docs}/images/data_privacy.png | Bin docs/navigation.md | 87 + gradle.properties | 18 +- gradle/wrapper/gradle-wrapper.properties | 3 +- qodana.yml | 2 +- settings.gradle | 1 - settings.gradle.kts | 1 + .../helper/AutoPopupInsertHandler.java | 6 +- .../githubworkflow/helper/FileDownloader.java | 90 +- .../helper/GitHubWorkflowConfig.java | 228 +- .../helper/GitHubWorkflowHelper.java | 122 +- .../helper/HighlightAnnotatorHelper.java | 66 +- .../helper/PsiElementHelper.java | 118 +- .../githubworkflow/logic/Action.java | 117 +- .../yunabraska/githubworkflow/logic/Envs.java | 2 + .../githubworkflow/logic/GitHub.java | 9 - .../githubworkflow/logic/JobContext.java | 163 -- .../yunabraska/githubworkflow/logic/Jobs.java | 12 +- .../githubworkflow/logic/Matrix.java | 61 - .../githubworkflow/logic/Needs.java | 62 +- .../githubworkflow/logic/Secrets.java | 33 +- .../githubworkflow/logic/Steps.java | 15 +- .../githubworkflow/logic/Strategy.java | 30 - .../model/CustomClickAction.java | 1 - .../githubworkflow/model/GitHubAction.java | 211 +- .../githubworkflow/model/IconRenderer.java | 34 +- .../model/LocalActionReferenceResolver.java | 9 +- .../githubworkflow/model/NodeIcon.java | 1 - .../model/SyntaxAnnotation.java | 13 +- .../model/VariableReferenceResolver.java | 55 +- .../services/ClearActionCacheAction.java | 41 - .../services/CodeCompletion.java | 848 +------ .../services/ExpressionReferenceTarget.java | 7 - .../services/ExpressionReferenceTargets.java | 314 --- .../services/GitHubActionCache.java | 432 +--- .../services/GitHubRequestAuthorizations.java | 143 -- .../services/GitHubWorkflowBundle.java | 39 - .../GitHubWorkflowSettingsConfigurable.java | 295 --- .../services/HighlightAnnotator.java | 618 +---- .../services/PluginErrorReportSubmitter.java | 34 +- .../services/PluginSettings.java | 64 - .../services/ProjectStartup.java | 58 +- .../services/ReferenceContributor.java | 35 +- .../services/RefreshActionCacheAction.java | 43 - .../services/RemoteActionProviders.java | 337 --- .../services/RemoteActionResolution.java | 14 - .../services/RemoteServerSettings.java | 137 -- .../services/RestoreActionWarningsAction.java | 42 - .../WorkflowAutoPopupEnterHandler.java | 44 - .../WorkflowAutoPopupTypedHandler.java | 72 - .../WorkflowCompletionConfidence.java | 28 - .../WorkflowCurrentBranchResolver.java | 86 - .../services/WorkflowDispatchInputs.java | 226 -- .../WorkflowDocumentationProvider.java | 552 ----- .../services/WorkflowRepository.java | 12 - .../services/WorkflowRepositoryResolver.java | 102 - .../services/WorkflowRunClient.java | 658 ----- .../services/WorkflowRunConfiguration.java | 178 -- .../WorkflowRunConfigurationProducer.java | 93 - .../WorkflowRunConfigurationType.java | 70 - .../services/WorkflowRunConsoleTabs.java | 1021 -------- .../services/WorkflowRunDownloads.java | 73 - .../services/WorkflowRunJobConsole.java | 51 - .../services/WorkflowRunLanguageInjector.java | 221 -- .../WorkflowRunLineMarkerContributor.java | 87 - .../services/WorkflowRunLogRenderer.java | 209 -- .../services/WorkflowRunProcessHandler.java | 677 ------ .../services/WorkflowRunRequest.java | 34 - .../services/WorkflowRunSettingsEditor.java | 143 -- .../services/WorkflowRunTracker.java | 65 - .../services/WorkflowSyntaxSchema.java | 328 --- .../services/WorkflowTextAttributes.java | 31 - src/main/resources/META-INF/plugin.xml | 41 - .../resources/github-docs/default-env.tsv | 45 - .../resources/github-docs/github-context.tsv | 40 - .../messages/GitHubWorkflowBundle.properties | 442 ---- .../GitHubWorkflowBundle_ar.properties | 442 ---- .../GitHubWorkflowBundle_cs.properties | 442 ---- .../GitHubWorkflowBundle_de.properties | 442 ---- .../GitHubWorkflowBundle_es.properties | 442 ---- .../GitHubWorkflowBundle_fr.properties | 442 ---- .../GitHubWorkflowBundle_hi.properties | 442 ---- .../GitHubWorkflowBundle_id.properties | 442 ---- .../GitHubWorkflowBundle_it.properties | 442 ---- .../GitHubWorkflowBundle_ja.properties | 442 ---- .../GitHubWorkflowBundle_ko.properties | 442 ---- .../GitHubWorkflowBundle_nl.properties | 442 ---- .../GitHubWorkflowBundle_pl.properties | 442 ---- .../GitHubWorkflowBundle_pt_BR.properties | 442 ---- .../GitHubWorkflowBundle_ru.properties | 442 ---- .../GitHubWorkflowBundle_sv.properties | 442 ---- .../GitHubWorkflowBundle_th.properties | 442 ---- .../GitHubWorkflowBundle_tr.properties | 442 ---- .../GitHubWorkflowBundle_uk.properties | 442 ---- .../GitHubWorkflowBundle_vi.properties | 442 ---- .../GitHubWorkflowBundle_zh_CN.properties | 442 ---- .../resources/messages/MyBundle.properties | 4 + .../helper/FileDownloaderTest.java | 85 - .../helper/GitHubWorkflowConfigTest.java | 158 -- .../helper/GitHubWorkflowHelperTest.java | 50 - .../model/GitHubActionTest.java | 104 - .../services/DownloadSchemasTest.java | 41 + .../services/EditorFeatureTestCase.java | 240 -- .../services/FakeRemoteServer.java | 162 -- .../services/GitHubActionCacheTest.java | 217 +- .../GitHubRequestAuthorizationsTest.java | 53 - .../services/HighlightAnnotatorTest.java | 30 + .../services/LocalizationResourcesTest.java | 324 --- .../PluginErrorReportSubmitterTest.java | 25 - .../services/RemoteActionProvidersTest.java | 224 -- .../services/SchemaResourcesTest.java | 38 - .../WorkflowActionRegistrationTest.java | 82 - .../services/WorkflowCompletionTest.java | 1988 --------------- .../WorkflowCurrentBranchResolverTest.java | 40 - .../services/WorkflowDispatchInputsTest.java | 66 - .../services/WorkflowDocumentationTest.java | 288 --- .../services/WorkflowGutterActionTest.java | 260 -- .../services/WorkflowHighlightingTest.java | 2124 ----------------- .../services/WorkflowPerformanceTest.java | 49 - .../services/WorkflowQuickFixTest.java | 433 ---- .../services/WorkflowReferenceTest.java | 737 ------ .../WorkflowRepositoryResolverTest.java | 58 - .../services/WorkflowRunClientTest.java | 422 ---- .../services/WorkflowRunConsoleTabsTest.java | 22 - .../WorkflowRunLanguageInjectionTest.java | 53 - .../services/WorkflowRunLogRendererTest.java | 136 -- .../WorkflowRunProcessHandlerTest.java | 819 ------- .../WorkflowRunSettingsEditorTest.java | 25 - .../services/WorkflowShowcaseTest.java | 172 -- .../services/WorkflowStylingTest.java | 404 ---- .../resources/testdata/.github/action.yml | 86 + .../resources/testdata/.github/issue_06.yml | 105 + .../resources/testdata/.github/issue_10.yml | 131 + .../resources/testdata/.github/issue_24.yml | 17 + .../resources/testdata/.github/issue_25.yml | 16 + .../resources/testdata/.github/issue_29.yml | 34 + .../testdata/.github/local_references.yml | 39 + .../testdata/.github/my_action/action.yml | 86 + .../resources/testdata/.github/show_case.yml | 123 + 164 files changed, 1962 insertions(+), 30061 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/run-ui-tests.yml create mode 100644 .github/workflows/show_case.yml delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 doc/adr/0001-use-gradle-wrapper-for-plugin-build.md delete mode 100644 doc/adr/0002-configurable-remote-action-providers.md delete mode 100644 doc/adr/0003-use-verifier-clean-documentation-provider.md delete mode 100644 doc/adr/0004-use-actions-for-cache-controls-and-resource-bundles.md delete mode 100644 doc/adr/0005-bound-remote-discovery-and-testable-reload.md delete mode 100644 doc/adr/0006-use-generated-github-docs-data-snapshots.md delete mode 100644 doc/adr/0007-use-date-based-semver-and-242-baseline.md delete mode 100644 doc/adr/0008-test-through-editor-and-runtime-boundaries.md delete mode 100644 doc/adr/0009-use-run-configurations-for-workflow-execution.md delete mode 100644 doc/adr/0010-use-host-matched-github-account-authentication.md delete mode 100644 doc/adr/0011-release-once-publish-signed-zip.md delete mode 100644 doc/navigation.md delete mode 100644 doc/spec/editor-test-matrix.md rename {doc => docs}/DataPrivacy.md (81%) rename {doc => docs}/ghw_arch.png (100%) rename {doc => docs}/images/data_privacy.png (100%) create mode 100644 docs/navigation.md delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/logic/Matrix.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/logic/Strategy.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTarget.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTargets.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizations.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowBundle.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowSettingsConfigurable.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/PluginSettings.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionProviders.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionResolution.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupEnterHandler.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupTypedHandler.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionConfidence.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolver.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationProvider.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepository.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolver.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfiguration.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationProducer.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationType.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunDownloads.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjector.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLineMarkerContributor.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRenderer.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunRequest.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunTracker.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowSyntaxSchema.java delete mode 100644 src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowTextAttributes.java delete mode 100644 src/main/resources/github-docs/default-env.tsv delete mode 100644 src/main/resources/github-docs/github-context.tsv delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_ar.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_cs.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_de.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_es.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_fr.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_hi.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_id.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_it.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_ja.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_ko.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_nl.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_pl.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_ru.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_sv.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_th.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_tr.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_uk.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_vi.properties delete mode 100644 src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties create mode 100644 src/main/resources/messages/MyBundle.properties delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfigTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelperTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/DownloadSchemasTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/FakeRemoteServer.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizationsTest.java create mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotatorTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitterTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/RemoteActionProvidersTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/SchemaResourcesTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolverTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowPerformanceTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowReferenceTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolverTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabsTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjectionTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRendererTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditorTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowShowcaseTest.java delete mode 100644 src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowStylingTest.java create mode 100644 src/test/resources/testdata/.github/action.yml create mode 100644 src/test/resources/testdata/.github/issue_06.yml create mode 100644 src/test/resources/testdata/.github/issue_10.yml create mode 100644 src/test/resources/testdata/.github/issue_24.yml create mode 100644 src/test/resources/testdata/.github/issue_25.yml create mode 100644 src/test/resources/testdata/.github/issue_29.yml create mode 100644 src/test/resources/testdata/.github/local_references.yml create mode 100644 src/test/resources/testdata/.github/my_action/action.yml create mode 100644 src/test/resources/testdata/.github/show_case.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index af6a9d9..9be205f 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: "main" + target-branch: "next" schedule: - interval: "weekly" + interval: "daily" # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" - target-branch: "main" + target-branch: "next" schedule: - interval: "weekly" + interval: "daily" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13f53e2..c09b3e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,390 +1,173 @@ -name: Pipeline - +# 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 on: workflow_dispatch: - 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 - -permissions: - actions: write - contents: write - packages: write - -concurrency: - group: pipeline-${{ github.event.pull_request.number || github.ref || github.run_id }} - cancel-in-progress: false + # 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: jobs: - pipeline: - name: Test, Build, Release - runs-on: ubuntu-latest - timeout-minutes: 60 - env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + # 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 + runs-on: ubuntu-latest + outputs: + version: ${{ steps.properties.outputs.version }} + changelog: ${{ steps.properties.outputs.changelog }} 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 }} - - - name: 🧭 Decide mode - id: mode - shell: sh - env: - HEAD_MESSAGE: ${{ github.event.head_commit.message || '' }} - INPUT_DRY_RUN: ${{ inputs.dry_run || 'false' }} - INPUT_TAG: ${{ inputs.tag || '' }} + # Free GitHub Actions Environment Disk Space + - name: Maximize Build Space run: | - release="false" - dry_run="false" - - 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 + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc - release_tag="" - release_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 + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v4 - 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 - release_version="${release_tag#v}" - fi + # Validate wrapper + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v3.5.0 - echo "release=$release" >> "$GITHUB_OUTPUT" - echo "dry_run=$dry_run" >> "$GITHUB_OUTPUT" - echo "tag=$release_tag" >> "$GITHUB_OUTPUT" - echo "version=$release_version" >> "$GITHUB_OUTPUT" + # Setup Java 17 environment for the next steps + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 17 - - name: 🗝️ Resolve cache key - id: cache-key - shell: sh + # Set environment variables + - name: Export Properties + id: properties + shell: bash run: | - { - 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" - - - name: ♻️ Restore cache - id: cache - uses: actions/cache/restore@v4 + 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: - path: | - ~/.gradle/caches/transforms-* - ~/.gradle/caches/*/transforms - ~/.gradle/wrapper - key: ${{ steps.cache-key.outputs.key }} - restore-keys: | - plugin-ci-${{ runner.os }}- + name: tests-result + path: ${{ github.workspace }}/build/reports/tests - - name: ☕ Set up Java - uses: actions/setup-java@v5 + # Upload Kover report to CodeCov + - name: Upload Code Coverage Report + uses: codecov/codecov-action@v4 with: - distribution: temurin - java-version: 25 - - - name: 🏷️ Prepare release metadata - if: steps.mode.outputs.release == 'true' - shell: sh - env: - DRY_RUN: ${{ steps.mode.outputs.dry_run }} - RELEASE_TAG: ${{ steps.mode.outputs.tag }} - RELEASE_VERSION: ${{ steps.mode.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 - - 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 + files: ${{ github.workspace }}/build/reports/kover/xml/report.xml - 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 - - - name: 🔐 Validate release secrets - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' - shell: sh - env: - CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} - PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} - run: | - missing="" - if [ -z "$CERTIFICATE_CHAIN" ]; then - missing="$missing CERTIFICATE_CHAIN" - fi - if [ -z "$PRIVATE_KEY" ]; then - missing="$missing PRIVATE_KEY" - fi - if [ -z "$PRIVATE_KEY_PASSWORD" ]; then - missing="$missing PRIVATE_KEY_PASSWORD" - fi - if [ -z "$PUBLISH_TOKEN" ]; then - missing="$missing PUBLISH_TOKEN" - fi - - if [ -n "$missing" ]; then - echo "Missing release secret(s):$missing" >&2 - exit 1 - fi - - - name: 🧪 Test and package - shell: sh - env: - CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} - RELEASE: ${{ steps.mode.outputs.release }} - DRY_RUN: ${{ steps.mode.outputs.dry_run }} - run: | - if [ "$RELEASE" = "true" ] && [ "$DRY_RUN" != "true" ]; then - ./gradlew --no-daemon check verifyPlugin signPlugin verifyPluginSignature --warning-mode all - elif [ "$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.mode.outputs.release == 'true' - shell: sh - env: - DRY_RUN: ${{ steps.mode.outputs.dry_run }} - run: | - if [ "$DRY_RUN" = "true" ]; then - archive="$(find build/distributions -maxdepth 1 -type f -name '*.zip' | sort | head -n 1)" - else - archive="$(find build/distributions -maxdepth 1 -type f -name '*-signed.zip' | sort | head -n 1)" - fi - - if [ -z "$archive" ]; then - echo "No release archive found." >&2 - exit 1 - fi + # Cache Plugin Verifier IDEs + - name: Setup Plugin Verifier IDEs Cache + uses: actions/cache@v4 + with: + path: ${{ steps.properties.outputs.pluginVerifierHomeDir }}/ides + key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} - echo "path=$archive" >> "$GITHUB_OUTPUT" - echo "name=$(basename "$archive")" >> "$GITHUB_OUTPUT" + # 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: 📝 Extract release notes - if: steps.mode.outputs.release == 'true' - shell: sh - env: - RELEASE_TAG: ${{ steps.mode.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 + # 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 - if [ ! -s release-notes.md ]; then - printf 'Plugin release %s\n' "$RELEASE_TAG" > release-notes.md - fi +# # Run Qodana inspections +# - name: Qodana - Code Inspection +# uses: JetBrains/qodana-action@v2022.3.4 - - name: 🚀 Create GitHub release - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' - shell: sh - env: - ARCHIVE_PATH: ${{ steps.archive.outputs.path }} - GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }} - RELEASE_TAG: ${{ steps.mode.outputs.tag }} + # Prepare plugin archive content for creating artifact + - name: Prepare Plugin Artifact + id: artifact + shell: bash 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 - fi + cd ${{ github.workspace }}/build/distributions + FILENAME=`ls *.zip` + unzip "$FILENAME" -d content - - name: 📚 Publish GitHub package - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' - shell: sh - env: - CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} - GITHUB_ACTOR: ${{ github.actor }} - GITHUB_TOKEN: ${{ github.token }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} - run: | - ./gradlew --no-daemon publishPluginZipPublicationToGitHubPackagesRepository --warning-mode all + echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT - - name: 🛒 Publish Marketplace - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' - shell: sh - env: - ARCHIVE_PATH: ${{ steps.archive.outputs.path }} - MARKETPLACE_CHANNEL: ${{ vars.MARKETPLACE_CHANNEL || '' }} - PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} - run: | - plugin_id="$(sed -n 's/^[[:space:]]*pluginId[[:space:]]*=[[:space:]]*//p' gradle.properties | head -n 1 | tr -d '\r')" - if [ -z "$plugin_id" ]; then - echo "Missing pluginId in gradle.properties." >&2 - exit 1 - fi + # 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: - if [ -n "$MARKETPLACE_CHANNEL" ]; then - curl --fail-with-body --retry 3 --retry-delay 10 \ - --header "Authorization: Bearer $PUBLISH_TOKEN" \ - -F "xmlId=$plugin_id" \ - -F "file=@$ARCHIVE_PATH" \ - -F "channel=$MARKETPLACE_CHANNEL" \ - https://plugins.jetbrains.com/api/updates/upload - else - curl --fail-with-body --retry 3 --retry-delay 10 \ - --header "Authorization: Bearer $PUBLISH_TOKEN" \ - -F "xmlId=$plugin_id" \ - -F "file=@$ARCHIVE_PATH" \ - https://plugins.jetbrains.com/api/updates/upload - fi + # Check out current repository + - name: Fetch Sources + uses: actions/checkout@v4 - - name: 📤 Push release commit and tag - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' - shell: sh + # Remove old release drafts by using the curl request for the available releases with a draft flag + - name: Remove Old Release Drafts env: - RELEASE_TAG: ${{ steps.mode.outputs.tag }} - RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - git config user.name "Kira" - git config user.email "kira@yuna.berlin" - - 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: 💾 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 }} + gh api repos/{owner}/{repo}/releases \ + --jq '.[] | select(.draft == true) | .id' \ + | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} - - name: 🧽 Prune old caches - if: success() && github.event_name != 'pull_request' - continue-on-error: true - shell: sh + # Create a new release draft which is not publicly visible and requires manual acceptance + - name: Create Release Draft env: - CACHE_KEY: ${{ steps.cache-key.outputs.key }} - GH_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - 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.mode.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 + gh release create v${{ needs.build.outputs.version }} \ + --draft \ + --title "v${{ needs.build.outputs.version }}" \ + --notes "$(cat << 'EOM' + ${{ needs.build.outputs.changelog }} + EOM + )" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9e1d109 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,97 @@ +# 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 new file mode 100644 index 0000000..8f44ca2 --- /dev/null +++ b/.github/workflows/run-ui-tests.yml @@ -0,0 +1,59 @@ +# 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 new file mode 100644 index 0000000..b67dfe7 --- /dev/null +++ b/.github/workflows/show_case.yml @@ -0,0 +1,116 @@ +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 7a4d20f..ae3dc19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,45 +4,6 @@ ## [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 c8194d8..6d96edc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,11 +3,8 @@ 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 `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. +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 diff --git a/README.md b/README.md index 483997d..606a842 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,18 @@ [![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)](doc/DataPrivacy.md) +[![](https://img.shields.io/static/v1?label=DataPrivacy&message=%F0%9F%94%92&logo=springsecurity&color=%#6DB33F)](docs/DataPrivacy.md) -## Development Is Active Again +## ⚠️ Development Pause Notice ⚠️ -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. +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. -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. +🔥 **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! --- @@ -30,17 +33,6 @@ _[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 @@ -52,62 +44,9 @@ _[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 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. +* **Configuration**: Add your GitHub account via `File > Settings > Version Control > GitHub`. * **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, signs the ZIP, creates the GitHub release, publishes the signed ZIP to GitHub -Packages, and uploads the same signed ZIP to JetBrains Marketplace. - -The workflow prunes old GitHub Actions caches after a successful non-PR run so only the current pipeline cache remains. - -Required repository secrets: - -* `CERTIFICATE_CHAIN` -* `PRIVATE_KEY` -* `PRIVATE_KEY_PASSWORD` -* `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: @@ -132,25 +71,25 @@ Yuna Morgenstern, your GitHub Jedi. -#### Project Checklist +#### TODO -- [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) +- [ ] 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) e.g. (https://github.com/cunla/ghactions-manager/blob/master/src/main/kotlin/com/dsoftware/ghmanager/api/Workflows.kt) -- [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) +- [ ] 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) ## Learning List -- [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 +- [ ] 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 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). @@ -159,12 +98,13 @@ 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. -- [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) +- [ ] 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) related [secrets](https://github.com/JetBrains/intellij-platform-plugin-template#environment-variables). -- [ ] Confirm +- [ ] Set the [Deployment Token](https://plugins.jetbrains.com/docs/marketplace/plugin-upload.html?from=IJPluginTemplate). -- [ ] Watch IntelliJ Platform Gradle Plugin and template releases during maintenance rounds. +- [ ] Click the Watch button on the top of the [IntelliJ Platform Plugin Template][template] to be notified + about releases containing new features and fixes. Plugin based on the [IntelliJ Platform Plugin Template][template]. diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 6d79306..0000000 --- a/build.gradle +++ /dev/null @@ -1,307 +0,0 @@ -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('signPlugin').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.26.3' - - 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') - } - - signing { - certificateChain = providers.environmentVariable('CERTIFICATE_CHAIN') - privateKey = providers.environmentVariable('PRIVATE_KEY') - password = providers.environmentVariable('PRIVATE_KEY_PASSWORD') - } -} - -changelog { - groups.empty() - repositoryUrl = requiredProperty('pluginUrl') -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..8e4c19f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,120 @@ +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/doc/adr/0001-use-gradle-wrapper-for-plugin-build.md b/doc/adr/0001-use-gradle-wrapper-for-plugin-build.md deleted file mode 100644 index 9d52510..0000000 --- a/doc/adr/0001-use-gradle-wrapper-for-plugin-build.md +++ /dev/null @@ -1,33 +0,0 @@ -# 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 deleted file mode 100644 index 6c01879..0000000 --- a/doc/adr/0002-configurable-remote-action-providers.md +++ /dev/null @@ -1,35 +0,0 @@ -# 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 deleted file mode 100644 index c1516cf..0000000 --- a/doc/adr/0003-use-verifier-clean-documentation-provider.md +++ /dev/null @@ -1,36 +0,0 @@ -# 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 deleted file mode 100644 index 1344c35..0000000 --- a/doc/adr/0004-use-actions-for-cache-controls-and-resource-bundles.md +++ /dev/null @@ -1,35 +0,0 @@ -# 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 deleted file mode 100644 index ec627a1..0000000 --- a/doc/adr/0005-bound-remote-discovery-and-testable-reload.md +++ /dev/null @@ -1,34 +0,0 @@ -# 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 deleted file mode 100644 index 09328f1..0000000 --- a/doc/adr/0006-use-generated-github-docs-data-snapshots.md +++ /dev/null @@ -1,21 +0,0 @@ -# 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 deleted file mode 100644 index 9bbc202..0000000 --- a/doc/adr/0007-use-date-based-semver-and-242-baseline.md +++ /dev/null @@ -1,34 +0,0 @@ -# 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 deleted file mode 100644 index f69ef99..0000000 --- a/doc/adr/0008-test-through-editor-and-runtime-boundaries.md +++ /dev/null @@ -1,32 +0,0 @@ -# 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 deleted file mode 100644 index ff6153e..0000000 --- a/doc/adr/0009-use-run-configurations-for-workflow-execution.md +++ /dev/null @@ -1,33 +0,0 @@ -# 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 deleted file mode 100644 index 1e0be16..0000000 --- a/doc/adr/0010-use-host-matched-github-account-authentication.md +++ /dev/null @@ -1,34 +0,0 @@ -# 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-signed-zip.md b/doc/adr/0011-release-once-publish-signed-zip.md deleted file mode 100644 index d0e573e..0000000 --- a/doc/adr/0011-release-once-publish-signed-zip.md +++ /dev/null @@ -1,35 +0,0 @@ -# 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, signs the plugin ZIP, creates or -updates the GitHub release, publishes the signed ZIP to GitHub Packages, and uploads the same signed ZIP directly to -JetBrains Marketplace. - -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 signed artifact attached to the GitHub release. -- Release publishing requires signing secrets and `PUBLISH_TOKEN`. -- The workflow has more conditional shell logic, but fewer moving GitHub Actions parts. diff --git a/doc/navigation.md b/doc/navigation.md deleted file mode 100644 index caf8c5d..0000000 --- a/doc/navigation.md +++ /dev/null @@ -1,36 +0,0 @@ -# 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 deleted file mode 100644 index 35af12c..0000000 --- a/doc/spec/editor-test-matrix.md +++ /dev/null @@ -1,125 +0,0 @@ -# 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/doc/DataPrivacy.md b/docs/DataPrivacy.md similarity index 81% rename from doc/DataPrivacy.md rename to docs/DataPrivacy.md index 5158a17..9b1010c 100644 --- a/doc/DataPrivacy.md +++ b/docs/DataPrivacy.md @@ -22,7 +22,7 @@ From version 3.0.0 onwards, significant improvements have been made in terms of ### What Data is Stored? -The plugin caches the following data for each workflow and action: +As of version 3.0.0, 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,16 +41,6 @@ The plugin caches the following data for each workflow and action: 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/ghw_arch.png b/docs/ghw_arch.png similarity index 100% rename from doc/ghw_arch.png rename to docs/ghw_arch.png diff --git a/doc/images/data_privacy.png b/docs/images/data_privacy.png similarity index 100% rename from doc/images/data_privacy.png rename to docs/images/data_privacy.png diff --git a/docs/navigation.md b/docs/navigation.md new file mode 100644 index 0000000..b19a32e --- /dev/null +++ b/docs/navigation.md @@ -0,0 +1,87 @@ +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 9d175f1..150abd2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,26 +2,28 @@ pluginId=com.github.yunabraska.githubworkflowplugin pluginGroup = berlin.yuna -pluginName = GitHub Workflow +pluginName = Github-Workflow-Plugin pluginDescription = Your Ultimate Wingman for GitHub Workflows and Actions -# Date-based SemVer: YYYY.M.D. The tag workflow updates this before creating vYYYY.M.D tags. -pluginVersion = 2026.5.20 +# SemVer format -> https://semver.org +pluginVersion = 2024.3.0 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 = 242 +pluginSinceBuild = 243 # 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 -platformVersion = 2026.1.2 +platformType = IC +platformVersion = 2024.3 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,com.jetbrains.sh +platformPlugins = org.jetbrains.plugins.github,org.jetbrains.plugins.yaml # Gradle Releases -> https://github.com/gradle/gradle/releases #gradleVersion = 8.9.0 @@ -33,7 +35,3 @@ 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 4bdb2a9..e2847c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/qodana.yml b/qodana.yml index 7763e4b..c09f8da 100644 --- a/qodana.yml +++ b/qodana.yml @@ -4,7 +4,7 @@ version: 1.0 profile: name: qodana.recommended - projectJDK: 25 + projectJDK: 17 exclude: - name: All paths: diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index 2daf929..0000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'github-workflow-plugin' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ae12a1f --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +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 dde14ba..67a68f9 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()).scheduleAutoPopup(context.getEditor()); + AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(context.getEditor(), null); } 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 isNextCharSpace = tailOffset < documentChars.length() && documentChars.charAt(tailOffset) == ' '; - if (suffix != '.' && !isNextCharSpace) { + final boolean isNextChatSpace = tailOffset < documentChars.length() && documentChars.charAt(tailOffset + 1) == ' '; + if (suffix != '.' && !isNextChatSpace) { 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 2bfeb1a..a1e542e 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/FileDownloader.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/FileDownloader.java @@ -2,10 +2,12 @@ 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.concurrency.AppExecutorUtil; +import com.intellij.util.io.HttpRequests; 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; @@ -18,8 +20,9 @@ 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; @@ -34,11 +37,10 @@ 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(PsiElementHelper::hasText) + .filter(Objects::nonNull) .findFirst() - .orElseGet(() -> downloadContent(downloadUrl)); + .orElse(null); } @@ -47,8 +49,7 @@ public static String downloadContent(final String urlString) { LOG.info("Download [" + urlString + "]"); try { final ApplicationInfo applicationInfo = ApplicationInfo.getInstance(); - final Future future = AppExecutorUtil.getAppExecutorService() - .submit(() -> downloadSync(urlString, applicationInfo.getBuild().getProductCode() + "/" + applicationInfo.getFullVersion())); + final Future future = ApplicationManager.getApplication().executeOnPooledThread(() -> 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()) + "]"); @@ -56,43 +57,62 @@ public static String downloadContent(final String urlString) { return ""; } - 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"); +// @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"); - if (connection.getResponseCode() / 100 != 2) { - throw new IOException("HTTP error code: " + connection.getResponseCode()); - } + // Check for successful response code or throw error + if (connection.getResponseCode() / 100 != 2) { + throw new IOException("HTTP error code: " + connection.getResponseCode()); + } - 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(); + // 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()); } + 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(""); + .orElse(null); } private static String downloadContent(final String downloadUrl, final GithubAccount account, final String token) { @@ -112,12 +132,12 @@ public String extractResult(final @NotNull GithubApiResponse response) { } }); } catch (final IOException ignored) { - return ""; + return null; } } }); } catch (final Exception ignored) { - return ""; + return null; } } } 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 db65eb8..664881f 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfig.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfig.java @@ -1,15 +1,7 @@ package com.github.yunabraska.githubworkflow.helper; -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.HashMap; import java.util.Map; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -17,11 +9,8 @@ @SuppressWarnings("java:S2386") public class GitHubWorkflowConfig { - 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 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 long CACHE_ONE_DAY = 24L * 60 * 60 * 1000; public static final String FIELD_ON = "on"; public static final String FIELD_IF = "if"; @@ -29,146 +18,149 @@ 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 LinkedHashMap<>(); + final Map>> result = new HashMap<>(); 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 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")); + 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\"."); return result; } - 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")); + 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"); return result; } - 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")); + //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."); return result; } - 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")); + //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"); 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 e68cec7..cafbdaf 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelper.java @@ -8,67 +8,34 @@ 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 PsiElement context = completionContextElement(position, offset); - final String rawText = context.getText(); - if (rawText == null) { + final String wholeText = position.getText(); + if (wholeText == null) { return Optional.empty(); } - 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 int cursorRel = offset - position.getTextRange().getStartOffset(); final String offsetText = wholeText.substring(0, cursorRel); final int bracketStart = offsetText.lastIndexOf("${{"); - if (cursorRel > 2 && isInBrackets(offsetText, bracketStart) || PsiElementHelper.getParent(context, FIELD_IF).isPresent()) { + if (cursorRel > 2 && isInBrackets(offsetText, bracketStart) || PsiElementHelper.getParent(position, 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 @@ -87,82 +54,6 @@ 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) { @@ -235,8 +126,7 @@ 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(".gitea")); + && path.getName(path.getNameCount() - 3).toString().equalsIgnoreCase(".github"); } public static boolean isWorkflowTemplatePropertiesFile(final Path path) { @@ -256,7 +146,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("ISSUE_TEMPLATE") + && path.getName(path.getNameCount() - 2).toString().equalsIgnoreCase("workflow-templates") && (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 225ae93..2ef15a6 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/HighlightAnnotatorHelper.java @@ -5,10 +5,8 @@ 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; @@ -21,7 +19,6 @@ 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; @@ -40,7 +37,6 @@ 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; @@ -58,8 +54,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 && element != null && result != null && !result.isEmpty()) { - createAnnotation(element, element.getTextRange(), holder, result); + if (holder != null) { + result.forEach(annotation -> annotation.createAnnotation(element, holder)); } } @@ -74,16 +70,17 @@ public static void ifEnoughItems( if (parts.length < min || parts.length < 2) { final TextRange range = psiElement.getTextRange(); new SyntaxAnnotation( - GitHubWorkflowBundle.message("inspection.statement.incomplete", Arrays.stream(parts).map(SimpleElement::text).collect(Collectors.joining("."))), + "Incomplete statement [" + 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 = textRangeIncludingPreviousDot(psiElement, tooLongPart[0], tooLongPart[tooLongPart.length - 1]); + final TextRange textRange = new TextRange(range.getStartOffset() + tooLongPart[0].startIndexOffset(), range.getStartOffset() + tooLongPart[tooLongPart.length - 1].endIndexOffset()); new SyntaxAnnotation( - GitHubWorkflowBundle.message("inspection.invalid.suffix.remove", Arrays.stream(tooLongPart).map(SimpleElement::text).collect(Collectors.joining("."))), - SUPPRESS_ON, + "Remove invalid suffix [" + Arrays.stream(tooLongPart).map(SimpleElement::text).collect(Collectors.joining(".")) + "]", + null, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); } else { @@ -97,8 +94,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( - GitHubWorkflowBundle.message("inspection.replace.with", item), - RELOAD, + "Replace with [" + item + "]", + null, replaceAction(textRange, item) )).toList()); return false; @@ -112,10 +109,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 = textRangeIncludingPreviousDot(psiElement, itemId, itemId); + final TextRange textRange = simpleTextRange(psiElement, itemId); new SyntaxAnnotation( - GitHubWorkflowBundle.message("inspection.invalid.remove", itemId.text()), - SUPPRESS_ON, + "Remove invalid [" + itemId + "]", + null, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); return false; @@ -127,8 +124,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( - GitHubWorkflowBundle.message("inspection.replace.with", item), - RELOAD, + "Replace with [" + item + "]", + null, replaceAction(textRange, item) )).toList()); } @@ -137,7 +134,7 @@ public static void isValidItem3(@NotNull final PsiElement psiElement, @NotNull f @NotNull public static SyntaxAnnotation newReloadAction(final GitHubAction action) { return new SyntaxAnnotation( - GitHubWorkflowBundle.message("inspection.action.reload", action.name()), + "Reload [" + action.name() + "]", RELOAD, HighlightSeverity.INFORMATION, ProblemHighlightType.INFORMATION, @@ -148,7 +145,7 @@ public static SyntaxAnnotation newReloadAction(final GitHubAction action) { @NotNull public static SyntaxAnnotation newUnresolvedAction(final YAMLKeyValue element) { return new SyntaxAnnotation( - GitHubWorkflowBundle.message("inspection.action.unresolved", removeQuotes(element.getValueText())), + "Unresolved [" + removeQuotes(element.getValueText()) + "] - you may need to connect your GitHub", SETTINGS, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, @@ -162,8 +159,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( - GitHubWorkflowBundle.message("inspection.invalid.remove", element.getValueText()), - SUPPRESS_ON, + "Remove invalid [" + element.getValueText() + "]", + null, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, deleteElementAction(textRange) @@ -172,8 +169,9 @@ 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( - GitHubWorkflowBundle.message("inspection.action.jump", action.name()), + "Jump to file [" + action.name() + "]", JUMP_TO_IMPLEMENTATION, HighlightSeverity.INFORMATION, ProblemHighlightType.INFORMATION, @@ -202,8 +200,7 @@ public static Consumer replaceAction(final TextRange textRang return fix -> { final PsiElement psiElement = fix.file().findElementAt(fix.editor().getCaretModel().getOffset()); if (psiElement != null) { - final PsiFile topLevelFile = InjectedLanguageManager.getInstance(fix.project()).getTopLevelFile(psiElement); - final Document document = PsiDocumentManager.getInstance(fix.project()).getDocument(topLevelFile); + final Document document = PsiDocumentManager.getInstance(fix.project()).getDocument(psiElement.getContainingFile()); if (document != null) { WriteCommandAction.runWriteCommandAction(fix.project(), () -> document.replaceString(textRange.getStartOffset(), textRange.getEndOffset(), newValue)); } @@ -224,29 +221,12 @@ 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( - GitHubWorkflowBundle.message("inspection.invalid.remove", itemId.text()), - SUPPRESS_ON, + "Delete invalid [" + itemId.text() + "]", + null, 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 a75f2ec..bff6093 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/helper/PsiElementHelper.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/helper/PsiElementHelper.java @@ -8,13 +8,9 @@ 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; @@ -27,7 +23,6 @@ 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; @@ -40,10 +35,7 @@ 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; @@ -135,7 +127,7 @@ public static void getTextElements(final List result, final PsiEleme } public static boolean isTextElement(final PsiElement element) { - return element instanceof YAMLScalarText || element instanceof YAMLPlainTextImpl || element instanceof YAMLQuotedText; + return element instanceof YAMLPlainTextImpl || element instanceof YAMLQuotedText; } public static List getAllElements(final PsiElement psiElement, final String keyName) { @@ -174,7 +166,6 @@ 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()) @@ -182,51 +173,6 @@ 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) @@ -270,22 +216,9 @@ public static Optional toYAMLKeyValue(final PsiElement psiElement) } public static String getDescription(final PsiElement psiElement, final boolean requiredField) { - 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); + 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(""); } public static Optional toPath(final VirtualFile virtualFile) { @@ -294,19 +227,16 @@ public static Optional toPath(final VirtualFile virtualFile) { public static Optional toPath(final String path) { try { - return ofNullable(path) - .map(String::trim) - .filter(PsiElementHelper::looksLikePathText) - .map(Paths::get) - .filter(p -> Files.exists(p) || ApplicationManager.getApplication().isUnitTestMode()); + return ofNullable(path).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(); } } - private static boolean looksLikePathText(final String path) { - return hasText(path) && !path.startsWith("{") && !path.matches("^<[0-9a-fA-F-]{36}>.*"); + @NotNull + private static String requiredString(final PsiElement psiElement, final boolean requiredField) { + return requiredField ? "r[" + getText(psiElement, "required").map(Boolean::parseBoolean).orElse(false) + "] " : ""; } public static Project getProject(final PsiElement psiElement) { @@ -322,7 +252,7 @@ public static boolean hasText(final String str) { } public static String goToDeclarationString() { - return GitHubWorkflowBundle.message("documentation.open.declaration", Arrays.stream(KeymapUtil.getActiveKeymapShortcuts("GotoDeclaration").getShortcuts()) + return String.format("Open declaration (%s)", Arrays.stream(KeymapUtil.getActiveKeymapShortcuts("GotoDeclaration").getShortcuts()) .limit(2) .map(KeymapUtil::getShortcutText) .collect(Collectors.joining(", ")) @@ -332,9 +262,12 @@ public static String goToDeclarationString() { private static Map toGithubOutputs(final String text) { final Map variables = new HashMap<>(); if (text.contains("GITHUB_OUTPUT")) { - 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); + final Matcher matcher = PATTERN_GITHUB_OUTPUT.matcher(text); + while (matcher.find()) { + if (matcher.groupCount() >= 2) { + variables.put(matcher.group(1), matcher.group(2)); + } + } } return variables; } @@ -342,18 +275,14 @@ private static Map toGithubOutputs(final String text) { private static Map toGithubEnvs(final String text) { final Map variables = new HashMap<>(); if (text.contains("GITHUB_ENV")) { - 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)); + final Matcher matcher = PATTERN_GITHUB_ENV.matcher(text); + while (matcher.find()) { + if (matcher.groupCount() >= 2) { + variables.put(matcher.group(1), matcher.group(2)); + } } } + return variables; } private static String removeBrackets(final String text, final char... chars) { @@ -402,10 +331,7 @@ private static List parseVariables(final LeafPsiElement element, private static List parseVariables(final PsiElement psiElement, final Function> method) { final List lineElements = getLineElements(psiElement); - 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()); + return lineElements.stream().flatMap(line -> method.apply(line.text()).entrySet().stream().map(env -> new SimpleElement(env.getKey(), env.getValue(), line.range()))).toList(); } 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 f64eaaa..611fe36 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Action.java @@ -1,14 +1,11 @@ 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; @@ -27,8 +24,6 @@ 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; @@ -36,7 +31,6 @@ 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; @@ -46,9 +40,6 @@ 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; @@ -65,46 +56,34 @@ 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)); - highlightResolvedActionReference(holder, element, action, result); + highlightLocalActions(holder, element, action, result); if (element != null && !action.isResolved() && (!action.isSuppressed())) { result.add(action.isLocal() ? deleteInvalidAction(element) : newUnresolvedAction(element)); } - }, () -> ofNullable(element).ifPresent(value -> result.add(newUnresolvedAction(value)))); + }, () -> result.add(newUnresolvedAction(element))); //FIXME: is this a valid state? 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 -> 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()) - )); - } - }); + .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()) + )); + } + + })); } public static List highlightActionOutputs(final YAMLSequenceItem stepItem, final SimpleElement part) { @@ -134,62 +113,20 @@ public static Optional referenceGithubAction(final PsiElement ps ); } - private static void highlightResolvedActionReference(final AnnotationHolder holder, final YAMLKeyValue element, final GitHubAction action, final List result) { - if (action.isResolved() && !action.isSuppressed()) { + private static void highlightLocalActions(final AnnotationHolder holder, final YAMLKeyValue element, final GitHubAction action, final List result) { + if (action.isResolved() && action.isLocal()) { final String tooltip = goToDeclarationString(); getTextElement(element).ifPresent(textElement -> { - final AnnotationBuilder annotation = holder.newAnnotation(HighlightSeverity.INFORMATION, tooltip) + holder.newAnnotation(HighlightSeverity.INFORMATION, tooltip) .range(textElement) .textAttributes(DefaultLanguageHighlighterColors.HIGHLIGHTED_REFERENCE) - .tooltip(tooltip); - if (action.isLocal()) { - final SyntaxAnnotation jumpToFile = newJumpToFile(action); - result.add(jumpToFile); - annotation.gutterIconRenderer(new IconRenderer(jumpToFile, element, JUMP_TO_IMPLEMENTATION)); - } - annotation.create(); + .tooltip(tooltip) + .create(); + result.add(newJumpToFile(action)); }); } } - 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) @@ -210,7 +147,7 @@ private static SyntaxAnnotation newSuppressAction(final GitHubAction action) { final boolean suppressed = action.isSuppressed(); return new SyntaxAnnotation( toggleText(action.name(), suppressed), - suppressed ? SUPPRESS_OFF : SUPPRESS_ON, + suppressed ? SUPPRESS_OFF : null, HighlightSeverity.INFORMATION, suppressed ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.INFORMATION, f -> { @@ -225,7 +162,7 @@ private static SyntaxAnnotation newSuppressInput(final GitHubAction action, fina final boolean suppressed = action.ignoredInputs().contains(id); return new SyntaxAnnotation( toggleText(id, suppressed), - suppressed ? IGNORED : SUPPRESS_ON, + suppressed ? IGNORED : EMPTY, HighlightSeverity.INFORMATION, suppressed ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.INFORMATION, f -> { @@ -241,7 +178,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), - suppressed ? IGNORED : SUPPRESS_ON, + null, level, suppressed ? ProblemHighlightType.WEAK_WARNING : ProblemHighlightType.INFORMATION, f -> { @@ -253,7 +190,7 @@ private static SyntaxAnnotation newSuppressOutput(final GitHubAction action, fin @NotNull private static String toggleText(final String id, final boolean suppressed) { - return GitHubWorkflowBundle.message("inspection.warning.toggle", GitHubWorkflowBundle.message(suppressed ? "inspection.warning.on" : "inspection.warning.off"), id); + return "Toggle warnings [" + (suppressed ? "on" : "off") + "] for [" + 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 4589358..a1d6621 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Envs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Envs.java @@ -25,6 +25,7 @@ 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; @@ -100,6 +101,7 @@ 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 cac16b0..4713c04 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/GitHub.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/GitHub.java @@ -8,7 +8,6 @@ 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; @@ -25,14 +24,6 @@ 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 deleted file mode 100644 index 1d62895..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/JobContext.java +++ /dev/null @@ -1,163 +0,0 @@ -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 d7455ae..8a3ccf2 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Jobs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Jobs.java @@ -17,7 +17,6 @@ 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; @@ -36,16 +35,11 @@ public class Jobs { public static void highLightJobs(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { - ifEnoughItems(holder, element, parts, 3, 4, jobId -> { + ifEnoughItems(holder, element, parts, 4, 4, jobId -> { final List jobs = listJobs(element); - 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; - } + if (isDefinedItem0(element, holder, jobId, jobs.stream().map(YAMLKeyValue::getKeyText).toList()) && isField2Valid(element, holder, parts[2])) { final List outputs = listJobOutputs(jobs.stream().filter(job -> job.getKeyText().equals(jobId.text())).findFirst().orElse(null)).stream().map(SimpleElement::key).toList(); - if (parts.length > 3) { - isValidItem3(element, holder, parts[3], outputs); - } + 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 deleted file mode 100644 index 0c6bf69..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Matrix.java +++ /dev/null @@ -1,61 +0,0 @@ -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 6012c35..e0fbf3f 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Needs.java @@ -1,40 +1,46 @@ 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 { @@ -43,16 +49,13 @@ public class Needs { // variable field public static void highlightNeeds(final AnnotationHolder holder, final LeafPsiElement element, final SimpleElement[] parts) { - ifEnoughItems(holder, element, parts, 3, 4, jobId -> { + ifEnoughItems(holder, element, parts, 4, 4, jobId -> { final List jobIds = listJobNeeds(element); - if (isDefinedItem0(element, holder, jobId, jobIds) && isField2Valid(element, holder, parts[2], List.of("outputs", FIELD_RESULT))) { - if (FIELD_RESULT.equals(parts[2].text())) { - return; - } + 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? final List outputs = listJobOutputs(listAllJobs(element).stream().filter(job -> job.getKeyText().equals(jobId.text())).findFirst().orElse(null)).stream().map(SimpleElement::key).toList(); - if (parts.length > 3) { - isValidItem3(element, holder, parts[3], outputs); - } + isValidItem3(element, holder, parts[3], outputs); } }); } @@ -67,8 +70,8 @@ public static void highlightNeeds(final AnnotationHolder holder, final PsiElemen if (!jobsNames.contains(element.getText())) { // INVALID JOB_ID addAnnotation(holder, psiElement, new SyntaxAnnotation( - GitHubWorkflowBundle.message("inspection.needs.invalid.job", element.getText()), - SUPPRESS_ON, + "Remove invalid jobId [" + element.getText() + "] - this jobId doesn't match any previous job", + null, deleteElementAction(psiElement.getTextRange()) )); } else { @@ -83,10 +86,6 @@ 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() @@ -109,18 +108,27 @@ public static Optional referenceNeeds(final PsiElement psiElemen .map(job -> new PsiReference[]{new LocalReferenceResolver(psiElement, job)}); } - // ########## COMMONS ########## - private static List listJobs(final PsiElement psiElement) { - return currentJob(psiElement).map(job -> listAllJobs(psiElement).stream().takeWhile(j -> !j.getKeyText().equals(job.getKeyText())).toList()).orElseGet(Collections::emptyList); + 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)); + }); + } } - 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))); + // ########## 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); } 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 82022c2..6bc9a0e 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Secrets.java @@ -2,7 +2,6 @@ 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; @@ -11,10 +10,9 @@ 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; @@ -30,15 +28,11 @@ 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, @@ -52,8 +46,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( - GitHubWorkflowBundle.message("inspection.secret.invalid.if", simpleElement.text()), - SUPPRESS_ON, + "Remove [" + simpleElement.text() + "] - Secrets are not valid in `if` statements", + null, deleteElementAction(textRange) ).createAnnotation(psiElement, textRange, holder); } @@ -61,8 +55,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( - GitHubWorkflowBundle.message("inspection.secret.replace.runtime", secretId.text(), secret), - RELOAD, + "Replace [" + secretId.text() + "] with [" + secret + "] - if it is not provided at runtime", + null, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.WEAK_WARNING, replaceAction(textRange, secret), @@ -74,20 +68,11 @@ public static void highLightSecrets( public static List listSecrets(final PsiElement psiElement) { //WORKFLOW SECRETS - if (getParent(psiElement, FIELD_IF).isPresent()) { - return Collections.emptyList(); - } - final Map result = getChild(psiElement.getContainingFile(), FIELD_ON) + return getParent(psiElement, FIELD_IF).isPresent() ? Collections.emptyList() : 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, - LinkedHashMap::new - ))) - .orElseGet(LinkedHashMap::new); - result.putIfAbsent(GITHUB_TOKEN, GitHubWorkflowBundle.message("completion.secret.githubToken")); - return completionItemsOf(result, ICON_SECRET_WORKFLOW); + .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); } 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 4fe4183..d5d5d0e 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/logic/Steps.java @@ -91,26 +91,19 @@ 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_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) + }).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) { - // Run file-command outputs and action metadata outputs are both valid step outputs. + //STEP RUN & ACTION 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 deleted file mode 100644 index 0eec442..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/logic/Strategy.java +++ /dev/null @@ -1,30 +0,0 @@ -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 bc72f83..d4be1ac 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/CustomClickAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/CustomClickAction.java @@ -13,7 +13,6 @@ 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 b1d8a26..54a5feb 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/GitHubAction.java @@ -1,13 +1,12 @@ package com.github.yunabraska.githubworkflow.model; +import com.esotericsoftware.kryo.kryo5.minlog.Log; import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; -import com.github.yunabraska.githubworkflow.services.RemoteActionProviders; -import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.application.ApplicationManager; 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; @@ -21,29 +20,31 @@ 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; @@ -59,7 +60,6 @@ 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 ? !isWorkflowFile(usesValue) : (!absolutePath.contains(".yaml") && !absolutePath.contains(".yml") && !absolutePath.contains(".action.y")); + final boolean isAction = isLocal || (!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 + ofNullable(tmpSub).orElse("") : tmpName) + .name(slug != null ? slug : tmpName) .usesValue(usesValue) .downloadUrl(isLocal ? absolutePath : toRemoteDownloadUrl(isAction, ref, slug, tmpSub, tmpName)) .githubUrl(isAction ? toGitHubActionUrl(ref, slug, tmpSub) : toGitHubWorkflowUrl(ref, slug, tmpName)) @@ -120,21 +120,16 @@ 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)) - .or(() -> ofNullable(downloadUrl()).map(path -> LocalFileSystem.getInstance().refreshAndFindFileByPath(path))) : Optional.empty(); + .map(VirtualFile::getPath) : Optional.empty(); } // !!! Performs Network and File Operations !!! + //TODO: get Tags for autocompletion public synchronized GitHubAction resolve() { - if ((!isResolved() || System.currentTimeMillis() >= expiryTime()) && !isSuppressed()) { + if (!isResolved() && !isSuppressed()) { extractParameters(); } return this; @@ -158,13 +153,6 @@ 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", ""); } @@ -174,24 +162,6 @@ 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", ""); } @@ -285,48 +255,20 @@ public GitHubAction suppressOutput(final String id, final boolean supress) { } public Set ignoredInputs() { - return ignoredInputs.stream() - .filter(PsiElementHelper::hasText) - .collect(Collectors.toUnmodifiableSet()); + return unmodifiableSet(ignoredInputs); } public Set 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; + return unmodifiableSet(ignoredOutputs); } + public Map getInputs() { return unmodifiableMap(inputs); } public GitHubAction setInputs(final Map inputs) { - ofNullable(inputs).ifPresent(this.inputs::putAll); + this.inputs.putAll(inputs); return this; } @@ -335,16 +277,7 @@ public Map getOutputs() { } public GitHubAction setOutputs(final Map 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); + this.outputs.putAll(outputs); return this; } @@ -353,43 +286,20 @@ public Map getMetaData() { } public GitHubAction setMetaData(final Map metaData) { - 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()); - }); + this.metaData.putAll(metaData); + this.ignoredInputs.addAll(Arrays.stream(metaData.getOrDefault("ignoredInputs", "").split(";")).toList()); + this.ignoredOutputs.addAll(Arrays.stream(metaData.getOrDefault("ignoredOutputs", "").split(";")).toList()); return this; } public static VirtualFile findActionYaml(final String subPath, final VirtualFile projectDir) { - 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())) + 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())) .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(); @@ -398,31 +308,17 @@ private void extractParameters() { } } catch (final Exception e) { LOG.warn("Failed to set parameters [" + this.name() + "]", e); - if (wasResolved) { - expiryTime(System.currentTimeMillis() + CACHE_ONE_DAY); - } else { - isResolved(false); - } + isResolved(false); } } private void extractRemoteParameters() { - 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); - } - }); + 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); + } } private void extractLocalParameters() { @@ -432,38 +328,16 @@ private void extractLocalParameters() { } catch (final IOException ignored) { return null; } - }).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(); - } + }).ifPresent(this::setParameters); } 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())); }); } @@ -494,20 +368,6 @@ 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) { @@ -528,22 +388,20 @@ 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(on -> getChild(on, "workflow_call")) - .flatMap(workflowCall -> getChild(workflowCall, fieldName)) + .flatMap(keyValue -> getChild(psiElement.getContainingFile(), 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) { - ReadAction.nonBlocking(() -> { + ApplicationManager.getApplication().runReadAction(() -> { 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) { @@ -569,7 +427,6 @@ 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 4832069..3444154 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/IconRenderer.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/IconRenderer.java @@ -1,51 +1,29 @@ 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 List quickFixes; + private final SyntaxAnnotation quickFix; 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.quickFixes = quickFixes == null ? List.of() : quickFixes.stream() - .filter(Objects::nonNull) - .filter(SyntaxAnnotation::hasExecution) - .distinct() - .toList(); + this.quickFix = quickFix; this.psiElement = psiElement; } @Nullable @Override public AnAction getClickAction() { - 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; + return icon != null && quickFix != null ? new CustomClickAction(quickFix, psiElement) : null; } @NotNull @@ -56,15 +34,13 @@ public Icon getIcon() { @Override public boolean isNavigateAction() { - return quickFixes.size() == 1; + return quickFix != null; } @Override @Nullable public String getTooltipText() { - return quickFixes.isEmpty() - ? null - : quickFixes.stream().map(SyntaxAnnotation::getText).distinct().reduce((left, right) -> left + "\n" + right).orElse(null); + return quickFix == null ? null : quickFix.getText(); } @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 da76f68..d86145d 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.VirtualFile; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementResolveResult; import com.intellij.psi.PsiManager; @@ -23,13 +23,12 @@ public LocalActionReferenceResolver(@NotNull final PsiElement element) { @Override public @NotNull ResolveResult @NotNull [] multiResolve(final boolean incompleteCode) { - return ofNullable(myElement.getUserData(ACTION_KEY)).flatMap(cachedAction -> { + return ofNullable(myElement.getUserData(ACTION_KEY)).flatMap(action -> { final Project project = getProject(myElement); return ofNullable(project) - .flatMap(cachedAction::getLocalVirtualFile) - .filter(VirtualFile::isValid) + .flatMap(action::getLocalPath) + .map(path -> LocalFileSystem.getInstance().findFileByPath(path)) .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 25c152e..1a396a8 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/NodeIcon.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/NodeIcon.java @@ -26,7 +26,6 @@ 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 dccbb52..22038ae 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/SyntaxAnnotation.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/SyntaxAnnotation.java @@ -11,7 +11,6 @@ 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.*; @@ -23,7 +22,7 @@ import static java.util.Optional.ofNullable; -public class SyntaxAnnotation implements IntentionAction, Iconable { +public class SyntaxAnnotation implements IntentionAction { private final String text; private final NodeIcon icon; @@ -100,6 +99,7 @@ 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,11 +111,6 @@ public Icon icon() { return icon.icon(); } - @Override - public Icon getIcon(final int flags) { - return icon == null ? null : icon.icon(); - } - @NotNull @Override public String getText() { @@ -138,10 +133,6 @@ 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 1488b46..9898646 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/model/VariableReferenceResolver.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/model/VariableReferenceResolver.java @@ -1,35 +1,62 @@ 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 { - private final PsiElement targetElement; - - public VariableReferenceResolver( - @NotNull final PsiElement element, - @NotNull final TextRange rangeInElement, - @NotNull final PsiElement targetElement - ) { - super(element, rangeInElement); - this.targetElement = targetElement; + public VariableReferenceResolver(@NotNull final PsiElement element) { + super(element); } @Override public @NotNull ResolveResult @NotNull [] multiResolve(final boolean incompleteCode) { - return ofNullable(targetElement) - .map(PsiElementResolveResult::new) - .map(result -> new ResolveResult[]{result}) - .orElse(ResolveResult.EMPTY_ARRAY); + 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); } @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 deleted file mode 100644 index 37ff048..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/ClearActionCacheAction.java +++ /dev/null @@ -1,41 +0,0 @@ -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 f22d868..cafbdb7 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/CodeCompletion.java @@ -12,63 +12,37 @@ 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; @@ -79,20 +53,10 @@ 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<>() { @@ -102,86 +66,32 @@ public void addCompletions( @NotNull final ProcessingContext processingContext, @NotNull final CompletionResultSet resultSet ) { - final CompletionPsi completionPsi = completionPsi(parameters); - final PsiElement position = completionPsi.position(); + final PsiElement position = parameters.getPosition(); getWorkflowFile(position).ifPresent(file -> { - final int offset = completionPsi.offset(); + final int offset = parameters.getOffset(); 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 (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)) { + 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()) { //[jobs.job_name.needs] list previous jobs - Optional.of(codeCompletionPreviousJobs(position)).filter(cil -> !cil.isEmpty()) + Optional.of(codeCompletionNeeds(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 { - 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, ':')); - } + //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, ':')); } } }); @@ -189,598 +99,13 @@ 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); @@ -800,93 +125,33 @@ 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, position, completionItemMap); + handleSecondItem(cbi, i, 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 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) { + private static void handleSecondItem(final String[] cbi, final int i, final Map> completionItemMap) { switch (cbi[0]) { case FIELD_STEPS -> completionItemMap.put(i, List.of( - 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) + 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) )); case FIELD_JOBS, FIELD_NEEDS -> completionItemMap.put(i, List.of( - completionItemOf(FIELD_OUTPUTS, GitHubWorkflowBundle.message("completion.jobs.outputs"), ICON_OUTPUT), - completionItemOf(FIELD_RESULT, GitHubWorkflowBundle.message("completion.jobs.result"), ICON_OUTPUT) + completionItemOf(FIELD_OUTPUTS, "The set of outputs defined for the step.", ICON_OUTPUT) )); - case FIELD_JOB -> completionItemMap.put(i, codeCompletionJob(cbi[1], position)); default -> { // ignored } @@ -899,11 +164,7 @@ 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)); @@ -929,7 +190,6 @@ 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; @@ -941,47 +201,8 @@ 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(); - 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())); + final int indexStart = getStartIndex(wholeText, caretOffset - 1); + return wholeText.substring(indexStart, caretOffset); } private static void addLookupElements(final CompletionResultSet resultSet, final Map map, final NodeIcon icon, final char suffix) { @@ -998,25 +219,4 @@ 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 deleted file mode 100644 index fe19fde..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTarget.java +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index b67f324..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/ExpressionReferenceTargets.java +++ /dev/null @@ -1,314 +0,0 @@ -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 89b1cfc..5258a99 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubActionCache.java @@ -4,7 +4,6 @@ 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; @@ -24,30 +23,15 @@ 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; @@ -60,29 +44,11 @@ @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); @@ -114,37 +80,12 @@ public void cleanUp() { protected GitHubAction get(final Project project, final String usesValue) { final String usesCleaned = usesValue.replace("IntellijIdeaRulezzz", ""); - final boolean isLocal = isLocalUses(usesCleaned); - final String normalizedUses = normalizeUsesValue(usesCleaned, isLocal); - final String path = getAbsolutePath(isLocal, normalizedUses, project); + final boolean isLocal = !usesCleaned.contains("@"); + final String path = getAbsolutePath(isLocal, usesCleaned, project); return ofNullable(path) - .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; + .map(state.actions::get) + .map(action -> System.currentTimeMillis() < action.expiryTime() ? action : saveNewAction(usesCleaned, path, isLocal, action)) + .orElseGet(() -> saveNewAction(usesCleaned, path, isLocal, null)); } public String remove(final String usesValue) { @@ -152,171 +93,13 @@ 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, () -> { - actionResolver.get().resolve(action); + action.resolve(); triggerSyntaxHighlightingForActiveFiles(); }); return action; @@ -329,43 +112,23 @@ public void resolveAsync(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; - } - new Task.Backgroundable(null, GitHubWorkflowBundle.message("workflow.cache.progress.title"), false) { + new Task.Backgroundable(null, "Resolving github actions", false) { @Override public void run(@NotNull final ProgressIndicator indicator) { try { final AtomicInteger index = new AtomicInteger(0); - final double totalActions = queuedActions.size(); + final double totalActions = actions.size(); indicator.setIndeterminate(false); - queuedActions.forEach(action -> { + actions.forEach(action -> { final int i = index.incrementAndGet(); - 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); - } + action.resolve(); + indicator.setFraction(i / totalActions); + indicator.setText("Resolving " + (action.isAction() ? "action" : "workflow") + action.name()); }); triggerSyntaxHighlightingForActiveFiles(); } catch (final Exception e) { - queuedActions.forEach(action -> inFlightResolutions.remove(action.usesValue())); + // Proceed action even on issues within the progress bar + state.actions.values().forEach(GitHubActionCache::removeAction); triggerSyntaxHighlightingForActiveFiles(); throw e; } @@ -373,76 +136,24 @@ 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() { - final Application application = ApplicationManager.getApplication(); - if (application.isUnitTestMode()) { - return; - } - application.invokeLater(() -> - Stream.of(ProjectManager.getInstance().getOpenProjects()).forEach(GitHubActionCache::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); + } + }) + ) + ) ); } - 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) { - getActionCache().resolveInBackground(actions); + threadPoolExec(ProjectManager.getInstance().getDefaultProject(), () -> getActionCache().resolveAsync(actions)); } public static GitHubAction reloadActionAsync(final Project project, final String usesValue) { @@ -478,17 +189,8 @@ public static Optional isUseElement(final PsiElement psiElement) { } private GitHubAction saveNewAction(final Project project, final GitHubAction 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; + final boolean isLocal = !oldAction.usesValue().contains("@"); + return saveNewAction(oldAction.usesValue(), getAbsolutePath(isLocal, oldAction.usesValue(), project), isLocal, oldAction); } private GitHubAction saveNewAction(final String usesValue, final String path, final boolean isLocal, final GitHubAction oldAction) { @@ -515,22 +217,6 @@ 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) @@ -545,64 +231,4 @@ 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 deleted file mode 100644 index 034997e..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizations.java +++ /dev/null @@ -1,143 +0,0 @@ -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 deleted file mode 100644 index 0b0efcb..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowBundle.java +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index fe16e46..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/GitHubWorkflowSettingsConfigurable.java +++ /dev/null @@ -1,295 +0,0 @@ -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 1ab6319..7461eef 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotator.java @@ -1,17 +1,14 @@ 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.application.ApplicationManager; -import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; +import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.impl.source.tree.LeafPsiElement; @@ -19,11 +16,10 @@ 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; @@ -33,36 +29,24 @@ 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; @@ -74,18 +58,14 @@ 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()) { @@ -105,310 +85,16 @@ 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).stream().map(variable -> withIcon(variable, ICON_ENV)).toList(), - parseOutputVariables(element).stream().map(variable -> withIcon(variable, ICON_TEXT_VARIABLE)).toList() + parseEnvVariables(element), + parseOutputVariables(element) ).flatMap(Collection::stream).collect(Collectors.groupingBy(SimpleElement::startIndexOffset)).forEach((integer, elements) -> ofNullable(getFirstChild(elements)).ifPresent(lineElement -> holder .newSilentAnnotation(INFORMATION) .range(lineElement.range()) - .textAttributes(WorkflowTextAttributes.DECLARATION) - .gutterIconRenderer(new IconRenderer(null, element, lineElement.icon())) + .gutterIconRenderer(new IconRenderer(null, element, ICON_TEXT_VARIABLE)) .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(); @@ -419,13 +105,12 @@ private static void outputsHandler(final AnnotationHolder holder, final PsiEleme .orElseGet(Collections::emptyList); outputs.stream().filter(output -> { final String outputKey = output.getKeyText(); - 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); + 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 + "}"); }).forEach(output -> new SyntaxAnnotation( - GitHubWorkflowBundle.message("inspection.output.unused", output.getKeyText()), - SUPPRESS_ON, + "Unused [" + output.getKeyText() + "]", + null, HighlightSeverity.WEAK_WARNING, ProblemHighlightType.LIKE_UNUSED_SYMBOL, deleteElementAction(output.getTextRange()), @@ -435,40 +120,12 @@ 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)) @@ -476,115 +133,63 @@ public static Predicate isElementWithVariables(final YAMLKeyValue pa .isPresent(); } - @NotNull - public static List toSimpleElements(final PsiElement element) { - 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; - } + public static final Key VARIABLE_ELEMENTS = new Key<>("com.github.yunabraska.githubworkflow.VariableElements"); @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; + 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(); } @NotNull public static SimpleElement[] splitToElements(final SimpleElement simpleElement) { - 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); + 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); } public static List findDottedExpressions(final String text) { final List elements = new ArrayList<>(); - 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; + 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); } + } else if (elementStart != -1 && (i + 1 == text.length() || Character.isWhitespace(text.charAt(i)) || text.charAt(i + 1) == '}')) { + // END + elementStart = validateAndAddElement(currentElement, elements, elementStart, i); } - if (hasSeparator && start < index) { - elements.add(new SimpleElement(text.substring(start, index), new TextRange(start, index))); - } + previousChar = ch; } return elements; } @@ -603,11 +208,7 @@ 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); @@ -619,108 +220,13 @@ private static void variableElementHandler(final AnnotationHolder holder, final ); } - 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()); + 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 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 == '-'; + elementStart = -1; + currentElement.setLength(0); + return elementStart; } 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 f632092..0a05de2 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitter.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitter.java @@ -1,11 +1,12 @@ 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; @@ -28,7 +29,7 @@ final class PluginErrorReportSubmitter extends ErrorReportSubmitter { @NotNull @Override public String getReportActionText() { - return GitHubWorkflowBundle.message("error.report.action"); + return "Report Exception"; } @Override @@ -36,11 +37,6 @@ 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(); @@ -54,24 +50,24 @@ public boolean submit(final IdeaLoggingEvent @NotNull [] events, .ifPresent(sb::append); sb.append("&body="); - sb.append(URLEncoder.encode("\n\n### " + GitHubWorkflowBundle.message("error.report.description") + "\n", UTF_8)); + sb.append(URLEncoder.encode("\n\n### Description\n", UTF_8)); sb.append(URLEncoder.encode(StringUtil.defaultIfEmpty(additionalInfo, ""), 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### 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.message") + "\n", UTF_8)); + sb.append(URLEncoder.encode("\n\n### Message\n", UTF_8)); sb.append(URLEncoder.encode(StringUtil.defaultIfEmpty(event.getMessage(), ""), 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### 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.stacktrace") + "\n", UTF_8)); + sb.append(URLEncoder.encode("\n\n### 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 deleted file mode 100644 index d638dde..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/PluginSettings.java +++ /dev/null @@ -1,64 +0,0 @@ -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 3a59a74..87c4c09 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/ProjectStartup.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/ProjectStartup.java @@ -6,7 +6,8 @@ import com.github.yunabraska.githubworkflow.helper.PsiElementHelper; import com.github.yunabraska.githubworkflow.model.GitHubAction; import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.project.DumbService; @@ -15,7 +16,6 @@ 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.ScheduledFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; 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(listenerDisposable); + final MessageBusConnection connection = project.getMessageBus().connect(); 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 ScheduledFuture cleanupTask = AppExecutorUtil.getAppScheduledExecutorService() - .scheduleWithFixedDelay(() -> getActionCache().cleanUp(), 0, 30, TimeUnit.MINUTES); + final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.scheduleAtFixedRate(() -> getActionCache().cleanUp(), 0, 30, TimeUnit.MINUTES); // Ensure the executor is shut down when the project is disposed Disposer.register(ListenerService.getInstance(project), () -> { - cleanupTask.cancel(false); + executorService.shutdown(); + unregisterAction(project); }); 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 && virtualFile.isValid() && (GitHubWorkflowHelper.isWorkflowPath(toPath(virtualFile.getPath())))) { - ReadAction.nonBlocking(() -> unresolvedActions(project, virtualFile)) - .inSmartMode(project) - .submit(AppExecutorUtil.getAppExecutorService()) - .onSuccess(GitHubActionCache::resolveActionsAsync); + 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); } }; 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)) { - AppExecutorUtil.getAppExecutorService().execute(task); + ApplicationManager.getApplication().executeOnPooledThread(task); } else { - DumbService.getInstance(project).runWhenSmart(() -> AppExecutorUtil.getAppExecutorService().execute(task)); + DumbService.getInstance(project).runWhenSmart(() -> ApplicationManager.getApplication().executeOnPooledThread(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 55f91b5..12af833 100644 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java +++ b/src/main/java/com/github/yunabraska/githubworkflow/services/ReferenceContributor.java @@ -2,9 +2,7 @@ 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; @@ -17,7 +15,6 @@ 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; @@ -36,12 +33,13 @@ public void registerReferenceProviders(@NotNull final PsiReferenceRegistrar regi @NotNull final PsiElement psiElement, @NotNull final ProcessingContext context ) { - return getWorkflowFile(psiElement).isEmpty() ? PsiReference.EMPTY_ARRAY : textElement(psiElement) + return getWorkflowFile(psiElement).isEmpty() ? PsiReference.EMPTY_ARRAY : Optional.of(psiElement) + .filter(PsiElementHelper::isTextElement) .flatMap(element -> { - final String text = removeQuotes(element.getText().replace("IntellijIdeaRulezzz ", "").replace("IntellijIdeaRulezzz", "")); + final String text = element.getText().replace("IntellijIdeaRulezzz ", "").replace("IntellijIdeaRulezzz", ""); return referenceGithubAction(element) .or(() -> referenceNeeds(element, text)) - .or(() -> referenceVariables(element)); + ; } ) .orElse(PsiReference.EMPTY_ARRAY); @@ -49,29 +47,4 @@ 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 deleted file mode 100644 index 1e97e39..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/RefreshActionCacheAction.java +++ /dev/null @@ -1,43 +0,0 @@ -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 deleted file mode 100644 index a0851c9..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionProviders.java +++ /dev/null @@ -1,337 +0,0 @@ -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 deleted file mode 100644 index 548e89e..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteActionResolution.java +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 6252db9..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/RemoteServerSettings.java +++ /dev/null @@ -1,137 +0,0 @@ -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 deleted file mode 100644 index 8a89844..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/RestoreActionWarningsAction.java +++ /dev/null @@ -1,42 +0,0 @@ -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 deleted file mode 100644 index 44614d2..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupEnterHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -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 deleted file mode 100644 index 1ef4d6e..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowAutoPopupTypedHandler.java +++ /dev/null @@ -1,72 +0,0 @@ -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 deleted file mode 100644 index 29122ef..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionConfidence.java +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index 1dc30dc..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolver.java +++ /dev/null @@ -1,86 +0,0 @@ -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 deleted file mode 100644 index e6108f5..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputs.java +++ /dev/null @@ -1,226 +0,0 @@ -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 deleted file mode 100644 index c562639..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationProvider.java +++ /dev/null @@ -1,552 +0,0 @@ -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 deleted file mode 100644 index f21de2d..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index daa2bf0..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolver.java +++ /dev/null @@ -1,102 +0,0 @@ -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 deleted file mode 100644 index 076d004..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClient.java +++ /dev/null @@ -1,658 +0,0 @@ -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 deleted file mode 100644 index a853563..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfiguration.java +++ /dev/null @@ -1,178 +0,0 @@ -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 deleted file mode 100644 index da107b1..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationProducer.java +++ /dev/null @@ -1,93 +0,0 @@ -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 deleted file mode 100644 index d67e3cf..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConfigurationType.java +++ /dev/null @@ -1,70 +0,0 @@ -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 deleted file mode 100644 index 39494eb..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabs.java +++ /dev/null @@ -1,1021 +0,0 @@ -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 deleted file mode 100644 index 72e12c5..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunDownloads.java +++ /dev/null @@ -1,73 +0,0 @@ -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 deleted file mode 100644 index bf0d200..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunJobConsole.java +++ /dev/null @@ -1,51 +0,0 @@ -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 deleted file mode 100644 index 1086766..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjector.java +++ /dev/null @@ -1,221 +0,0 @@ -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 deleted file mode 100644 index 863fc00..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLineMarkerContributor.java +++ /dev/null @@ -1,87 +0,0 @@ -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 deleted file mode 100644 index 4ab0e77..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRenderer.java +++ /dev/null @@ -1,209 +0,0 @@ -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 deleted file mode 100644 index 1c8fef3..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandler.java +++ /dev/null @@ -1,677 +0,0 @@ -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 deleted file mode 100644 index 651f63c..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -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 deleted file mode 100644 index adff3ff..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditor.java +++ /dev/null @@ -1,143 +0,0 @@ -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 deleted file mode 100644 index 63086b9..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowRunTracker.java +++ /dev/null @@ -1,65 +0,0 @@ -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 deleted file mode 100644 index 08057f5..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowSyntaxSchema.java +++ /dev/null @@ -1,328 +0,0 @@ -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 deleted file mode 100644 index 8a79383..0000000 --- a/src/main/java/com/github/yunabraska/githubworkflow/services/WorkflowTextAttributes.java +++ /dev/null @@ -1,31 +0,0 @@ -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 df1483b..895c300 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -3,7 +3,6 @@ com.github.yunabraska.githubworkflowplugin Github Workflow Yuna Morgenstern - messages.GitHubWorkflowBundle com.intellij.modules.platform @@ -18,29 +17,12 @@ - - - - - - - - - @@ -48,39 +30,16 @@ - - - - - - - - - - - - - - - diff --git a/src/main/resources/github-docs/default-env.tsv b/src/main/resources/github-docs/default-env.tsv deleted file mode 100644 index d7ceb55..0000000 --- a/src/main/resources/github-docs/default-env.tsv +++ /dev/null @@ -1,45 +0,0 @@ -# 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 deleted file mode 100644 index 8fb0063..0000000 --- a/src/main/resources/github-docs/github-context.tsv +++ /dev/null @@ -1,40 +0,0 @@ -# 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 deleted file mode 100644 index 2771e50..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index a371bad..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_ar.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 4e571f2..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_cs.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index f4997a3..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_de.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 1a47e7b..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_es.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 8a66e19..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_fr.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 1c8debc..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_hi.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index e333d8d..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_id.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 7822206..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_it.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 9d5c7d9..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_ja.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index e7aa7fb..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_ko.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index cf84812..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_nl.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index d3d27a4..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_pl.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 1bcae49..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_pt_BR.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 4a4ca1d..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_ru.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 8949d30..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_sv.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 73c798e..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_th.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 2807735..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_tr.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 552f490..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_uk.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 6a4382a..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_vi.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 deleted file mode 100644 index 6273f98..0000000 --- a/src/main/resources/messages/GitHubWorkflowBundle_zh_CN.properties +++ /dev/null @@ -1,442 +0,0 @@ -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 new file mode 100644 index 0000000..06fac1e --- /dev/null +++ b/src/main/resources/messages/MyBundle.properties @@ -0,0 +1,4 @@ +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 deleted file mode 100644 index 7bb3f13..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/helper/FileDownloaderTest.java +++ /dev/null @@ -1,85 +0,0 @@ -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 deleted file mode 100644 index 302adc5..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowConfigTest.java +++ /dev/null @@ -1,158 +0,0 @@ -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 deleted file mode 100644 index 7b90002..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/helper/GitHubWorkflowHelperTest.java +++ /dev/null @@ -1,50 +0,0 @@ -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 deleted file mode 100644 index eeef4a8..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/model/GitHubActionTest.java +++ /dev/null @@ -1,104 +0,0 @@ -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 new file mode 100644 index 0000000..efe2aac --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/DownloadSchemasTest.java @@ -0,0 +1,41 @@ +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 deleted file mode 100644 index f1566fd..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/EditorFeatureTestCase.java +++ /dev/null @@ -1,240 +0,0 @@ -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 deleted file mode 100644 index 73ffd19..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/FakeRemoteServer.java +++ /dev/null @@ -1,162 +0,0 @@ -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 1ea9629..5a733be 100644 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubActionCacheTest.java +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubActionCacheTest.java @@ -1,219 +1,64 @@ 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 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 com.github.yunabraska.githubworkflow.services.GitHubActionCache.getActionCache; import static org.assertj.core.api.Assertions.assertThat; public class GitHubActionCacheTest extends BasePlatformTestCase { - public void testLocalActionSerializationAndDeserialization() throws IOException { + @Test + public void testSerializationAndDeserialization() throws InterruptedException { // GIVEN - final String actionPath = localActionPath(); - final GitHubActionCache originalCache = new GitHubActionCache(); - final GitHubAction javaAction = originalCache.get(null, actionPath).resolve(); + 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(); // THEN EXPECT - validateResolvedLocalAction(javaAction, actionPath); - assertThat(originalCache.get(null, actionPath).expiryTime()).isEqualTo(javaAction.expiryTime()); + validateResolvedJavaAction(javaAction); + assertThat(javaAction.expiryTime()).isEqualTo(getActionCache().get(project, "actions/setup-java@main").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(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(); + 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()); } - private static void validateResolvedLocalAction(final GitHubAction javaAction, final String actionPath) { + private static void validateResolvedJavaAction(final GitHubAction javaAction) { assertThat(javaAction.getInputs()).isNotEmpty(); assertThat(javaAction.getOutputs()).isNotEmpty(); - assertThat(javaAction.getInputs()).containsKey("deep"); - assertThat(javaAction.getOutputs()).containsKey("java_version"); - assertThat(javaAction.isLocal()).isTrue(); + assertThat(javaAction.isLocal()).isFalse(); assertThat(javaAction.isAction()).isTrue(); assertThat(javaAction.isResolved()).isTrue(); - assertThat(javaAction.githubUrl()).isEmpty(); - assertThat(javaAction.name()).isEqualTo(actionPath); - assertThat(javaAction.downloadUrl()).isEqualTo(actionPath); + 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.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 deleted file mode 100644 index c8a71f7..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/GitHubRequestAuthorizationsTest.java +++ /dev/null @@ -1,53 +0,0 @@ -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 new file mode 100644 index 0000000..973bf3d --- /dev/null +++ b/src/test/java/com/github/yunabraska/githubworkflow/services/HighlightAnnotatorTest.java @@ -0,0 +1,30 @@ +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 deleted file mode 100644 index 5ca16a9..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/LocalizationResourcesTest.java +++ /dev/null @@ -1,324 +0,0 @@ -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 deleted file mode 100644 index 37b357a..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/PluginErrorReportSubmitterTest.java +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index ee6c324..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/RemoteActionProvidersTest.java +++ /dev/null @@ -1,224 +0,0 @@ -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 deleted file mode 100644 index 9e11681..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/SchemaResourcesTest.java +++ /dev/null @@ -1,38 +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.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 deleted file mode 100644 index 590364a..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowActionRegistrationTest.java +++ /dev/null @@ -1,82 +0,0 @@ -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 deleted file mode 100644 index 7ee94f9..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCompletionTest.java +++ /dev/null @@ -1,1988 +0,0 @@ -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 deleted file mode 100644 index 58696d4..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowCurrentBranchResolverTest.java +++ /dev/null @@ -1,40 +0,0 @@ -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 deleted file mode 100644 index d9ca479..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDispatchInputsTest.java +++ /dev/null @@ -1,66 +0,0 @@ -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 deleted file mode 100644 index 18088cd..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowDocumentationTest.java +++ /dev/null @@ -1,288 +0,0 @@ -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 deleted file mode 100644 index 897f7cc..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowGutterActionTest.java +++ /dev/null @@ -1,260 +0,0 @@ -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 deleted file mode 100644 index 04d9c07..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowHighlightingTest.java +++ /dev/null @@ -1,2124 +0,0 @@ -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 deleted file mode 100644 index d4b866a..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowPerformanceTest.java +++ /dev/null @@ -1,49 +0,0 @@ -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 deleted file mode 100644 index c15ffc1..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowQuickFixTest.java +++ /dev/null @@ -1,433 +0,0 @@ -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 deleted file mode 100644 index 1b78219..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowReferenceTest.java +++ /dev/null @@ -1,737 +0,0 @@ -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 deleted file mode 100644 index 5e222d3..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRepositoryResolverTest.java +++ /dev/null @@ -1,58 +0,0 @@ -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 deleted file mode 100644 index c5b7b62..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunClientTest.java +++ /dev/null @@ -1,422 +0,0 @@ -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 deleted file mode 100644 index 4263a42..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunConsoleTabsTest.java +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 2f7415b..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLanguageInjectionTest.java +++ /dev/null @@ -1,53 +0,0 @@ -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 deleted file mode 100644 index 83f3791..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunLogRendererTest.java +++ /dev/null @@ -1,136 +0,0 @@ -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 deleted file mode 100644 index 123c14d..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunProcessHandlerTest.java +++ /dev/null @@ -1,819 +0,0 @@ -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 deleted file mode 100644 index 63ada58..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowRunSettingsEditorTest.java +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index 608782d..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowShowcaseTest.java +++ /dev/null @@ -1,172 +0,0 @@ -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 deleted file mode 100644 index 2999875..0000000 --- a/src/test/java/com/github/yunabraska/githubworkflow/services/WorkflowStylingTest.java +++ /dev/null @@ -1,404 +0,0 @@ -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 new file mode 100644 index 0000000..8612a25 --- /dev/null +++ b/src/test/resources/testdata/.github/action.yml @@ -0,0 +1,86 @@ +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 new file mode 100644 index 0000000..6458d2d --- /dev/null +++ b/src/test/resources/testdata/.github/issue_06.yml @@ -0,0 +1,105 @@ +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 new file mode 100644 index 0000000..ad26aff --- /dev/null +++ b/src/test/resources/testdata/.github/issue_10.yml @@ -0,0 +1,131 @@ +# 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 new file mode 100644 index 0000000..a2eb75e --- /dev/null +++ b/src/test/resources/testdata/.github/issue_24.yml @@ -0,0 +1,17 @@ +# 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 new file mode 100644 index 0000000..3122f3a --- /dev/null +++ b/src/test/resources/testdata/.github/issue_25.yml @@ -0,0 +1,16 @@ +# 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 new file mode 100644 index 0000000..f3b8bc8 --- /dev/null +++ b/src/test/resources/testdata/.github/issue_29.yml @@ -0,0 +1,34 @@ +# 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 new file mode 100644 index 0000000..b7f4cd6 --- /dev/null +++ b/src/test/resources/testdata/.github/local_references.yml @@ -0,0 +1,39 @@ +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 new file mode 100644 index 0000000..8612a25 --- /dev/null +++ b/src/test/resources/testdata/.github/my_action/action.yml @@ -0,0 +1,86 @@ +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 new file mode 100644 index 0000000..b44968e --- /dev/null +++ b/src/test/resources/testdata/.github/show_case.yml @@ -0,0 +1,123 @@ +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 }}]" + From 27526319bf4edfe734c9e9316b69698206498386 Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 19:27:29 +0200 Subject: [PATCH 16/17] Bump AssertJ --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2a8d98a..d643981 100644 --- a/build.gradle +++ b/build.gradle @@ -124,7 +124,7 @@ publishing { dependencies { testImplementation 'junit:junit:4.13.2' - testImplementation 'org.assertj:assertj-core:3.26.3' + testImplementation 'org.assertj:assertj-core:3.27.7' intellijPlatform { intellijIdea(requiredProperty('platformVersion')) From 2954052bd0052c4ba7dd6a0558facd425a32008a Mon Sep 17 00:00:00 2001 From: Yuna Morgenstern Date: Sat, 23 May 2026 20:07:45 +0200 Subject: [PATCH 17/17] Harden release workflow planning --- .github/workflows/build.yml | 114 +++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 48 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af660f1..b18dea3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,8 +51,8 @@ jobs: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN || github.token }} - - name: 🧭 Decide mode - id: mode + - name: 🧭 Plan run + id: plan shell: sh env: HEAD_MESSAGE: ${{ github.event.head_commit.message || '' }} @@ -61,6 +61,13 @@ jobs: run: | 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')" + + if [ -z "$plugin_id" ] || [ -z "$plugin_version" ]; then + echo "Missing pluginId or pluginVersion in gradle.properties." >&2 + exit 1 + fi if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then release="true" @@ -72,7 +79,7 @@ jobs: fi release_tag="" - release_version="" + target_version="$plugin_version" if [ "$release" = "true" ]; then if [ -n "$INPUT_TAG" ]; then release_tag="$INPUT_TAG" @@ -87,13 +94,24 @@ jobs: echo "Tag must look like v2026.5.20: $release_tag" >&2 exit 1 fi - release_version="${release_tag#v}" + target_version="${release_tag#v}" fi + 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=$release_version" >> "$GITHUB_OUTPUT" + echo "version=$target_version" >> "$GITHUB_OUTPUT" - name: 🗝️ Resolve cache key id: cache-key @@ -123,12 +141,12 @@ jobs: java-version: 25 - name: 🏷️ Prepare release metadata - if: steps.mode.outputs.release == 'true' + if: steps.plan.outputs.release == 'true' shell: sh env: - DRY_RUN: ${{ steps.mode.outputs.dry_run }} - RELEASE_TAG: ${{ steps.mode.outputs.tag }} - RELEASE_VERSION: ${{ steps.mode.outputs.version }} + 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 @@ -174,30 +192,42 @@ jobs: fi - name: 🔐 Validate release secrets - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + if: steps.plan.outputs.release == 'true' && steps.plan.outputs.dry_run != 'true' shell: sh env: PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} run: | - missing="" if [ -z "$PUBLISH_TOKEN" ]; then - missing="$missing PUBLISH_TOKEN" + echo "Missing release secret: PUBLISH_TOKEN" >&2 + exit 1 fi - if [ -n "$missing" ]; then - echo "Missing release secret(s):$missing" >&2 + 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 + + 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.mode.outputs.release }} - DRY_RUN: ${{ steps.mode.outputs.dry_run }} + RELEASE: ${{ steps.plan.outputs.release }} run: | - if [ "$RELEASE" = "true" ] && [ "$DRY_RUN" != "true" ]; then - ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all - elif [ "$RELEASE" = "true" ]; then + if [ "$RELEASE" = "true" ]; then ./gradlew --no-daemon check verifyPlugin buildPlugin --warning-mode all else ./gradlew --no-daemon check buildPlugin --warning-mode all @@ -205,10 +235,8 @@ jobs: - name: 📦 Locate release archive id: archive - if: steps.mode.outputs.release == 'true' + if: steps.plan.outputs.release == 'true' shell: sh - env: - DRY_RUN: ${{ steps.mode.outputs.dry_run }} run: | archive="$(find build/distributions -maxdepth 1 -type f -name '*.zip' | sort | head -n 1)" @@ -221,10 +249,10 @@ jobs: echo "name=$(basename "$archive")" >> "$GITHUB_OUTPUT" - name: 📝 Extract release notes - if: steps.mode.outputs.release == 'true' + if: steps.plan.outputs.release == 'true' shell: sh env: - RELEASE_TAG: ${{ steps.mode.outputs.tag }} + RELEASE_TAG: ${{ steps.plan.outputs.tag }} run: | version="${RELEASE_TAG#v}" awk -v version="$version" ' @@ -246,7 +274,7 @@ jobs: fi - name: 📚 Publish GitHub package - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + if: steps.plan.outputs.release == 'true' && steps.plan.outputs.dry_run != 'true' shell: sh env: GITHUB_ACTOR: ${{ github.actor }} @@ -255,39 +283,29 @@ jobs: ./gradlew --no-daemon publishPluginZipPublicationToGitHubPackagesRepository --warning-mode all - name: 🛒 Publish Marketplace - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + 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: | - plugin_id="$(sed -n 's/^[[:space:]]*pluginId[[:space:]]*=[[:space:]]*//p' gradle.properties | head -n 1 | tr -d '\r')" - if [ -z "$plugin_id" ]; then - echo "Missing pluginId in gradle.properties." >&2 - exit 1 - fi + 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 - curl --fail-with-body --retry 3 --retry-delay 10 \ - --header "Authorization: Bearer $PUBLISH_TOKEN" \ - -F "xmlId=$plugin_id" \ - -F "file=@$ARCHIVE_PATH" \ - -F "channel=$MARKETPLACE_CHANNEL" \ - https://plugins.jetbrains.com/api/updates/upload - else - curl --fail-with-body --retry 3 --retry-delay 10 \ - --header "Authorization: Bearer $PUBLISH_TOKEN" \ - -F "xmlId=$plugin_id" \ - -F "file=@$ARCHIVE_PATH" \ - https://plugins.jetbrains.com/api/updates/upload + set -- "$@" -F "channel=$MARKETPLACE_CHANNEL" fi + curl "$@" https://plugins.jetbrains.com/api/updates/upload - name: 📤 Push release commit and tag - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + if: steps.plan.outputs.release == 'true' && steps.plan.outputs.dry_run != 'true' shell: sh env: - RELEASE_TAG: ${{ steps.mode.outputs.tag }} + RELEASE_TAG: ${{ steps.plan.outputs.tag }} RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | git config user.name "Kira" @@ -303,12 +321,12 @@ jobs: git push origin "refs/tags/$RELEASE_TAG" - name: 🚀 Create GitHub release - if: steps.mode.outputs.release == 'true' && steps.mode.outputs.dry_run != 'true' + 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.mode.outputs.tag }} + 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 @@ -354,7 +372,7 @@ jobs: build/reports/jacoco - name: 🧾 Upload verifier reports - if: always() && steps.mode.outputs.release == 'true' + if: always() && steps.plan.outputs.release == 'true' uses: actions/upload-artifact@v7 with: name: plugin-verifier-reports