diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 8e7e446e..fbd124c8 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -28,7 +28,7 @@ jobs: dry-run: ${{ steps.check-dry-run.outputs.enabled }} steps: - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 2 @@ -66,10 +66,7 @@ jobs: contents: read steps: - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - - - name: Lint pinned actions - run: bash tools/linter_actions_pinned.sh + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Get list of changed C# files id: changed-files @@ -155,7 +152,7 @@ jobs: pull-requests: write steps: - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Setup .NET uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 @@ -163,7 +160,7 @@ jobs: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Download all coverage artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: coverage-* path: ./all-coverage @@ -181,7 +178,7 @@ jobs: -title:"Weaviate C# Client Coverage" - name: Upload HTML coverage report - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: coverage-report-html path: ./coveragereport @@ -196,7 +193,7 @@ jobs: path: ./coveragereport/SummaryGithub.md - name: Download all test results - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 continue-on-error: true with: pattern: test-results-* @@ -239,7 +236,7 @@ jobs: if [ -z "${{ secrets.NUGET_APIKEY }}" ]; then echo "Warning: NUGET_APIKEY is not set"; fi - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 fetch-tags: true diff --git a/.github/workflows/pr-security-lint.yaml b/.github/workflows/pr-security-lint.yaml index 3dc27e53..7c1cb63c 100644 --- a/.github/workflows/pr-security-lint.yaml +++ b/.github/workflows/pr-security-lint.yaml @@ -16,7 +16,7 @@ jobs: pull-requests: read steps: - name: Checkout base branch - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: ref: ${{ github.event.pull_request.base.sha }} diff --git a/.github/workflows/test-on-weaviate-version.yml b/.github/workflows/test-on-weaviate-version.yml index 72188385..bcffc1a7 100644 --- a/.github/workflows/test-on-weaviate-version.yml +++ b/.github/workflows/test-on-weaviate-version.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Setup .NET uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5 @@ -62,7 +62,7 @@ jobs: fi - name: Login to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 if: ${{ !github.event.pull_request.head.repo.fork && github.triggering_actor != 'dependabot[bot]' }} with: username: ${{ secrets.DOCKER_USERNAME }} @@ -123,7 +123,7 @@ jobs: run: /bin/bash ci/stop_weaviate.sh ${{ inputs.weaviate-version }} - name: Upload test results - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: failure() with: name: test-results-integration-${{ inputs.weaviate-version }} @@ -131,7 +131,7 @@ jobs: retention-days: 7 - name: Upload coverage data - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: always() with: name: coverage-integration-${{ inputs.weaviate-version }} diff --git a/src/Weaviate.Client.Tests/Integration/TestRbacAuthorization.cs b/src/Weaviate.Client.Tests/Integration/TestRbacAuthorization.cs index ed47fd4e..37c86f7d 100644 --- a/src/Weaviate.Client.Tests/Integration/TestRbacAuthorization.cs +++ b/src/Weaviate.Client.Tests/Integration/TestRbacAuthorization.cs @@ -7,23 +7,8 @@ namespace Weaviate.Client.Tests.Integration; /// /// RBAC Groups integration tests (Rest:8092 gRPC:50063). Authorization checks for various operations. /// -public class TestRbacAuthorization : IntegrationTests +public class TestRbacAuthorization : RbacIntegrationTests { - /// - /// Gets the value of the rest port - /// - public override ushort RestPort => 8092; - - /// - /// Gets the value of the grpc port - /// - public override ushort GrpcPort => 50063; - - /// - /// The admin api key - /// - private const string ADMIN_API_KEY = "admin-key"; - /// /// Initializes this instance /// @@ -35,11 +20,6 @@ public override async ValueTask InitializeAsync() RequireVersion("1.32.0"); } - /// - /// Gets the value of the credentials - /// - public override ICredentials? Credentials => Auth.ApiKey(ADMIN_API_KEY); - /// /// Tests that test authorization failure /// diff --git a/src/Weaviate.Client.Tests/Integration/TestRbacGroups.cs b/src/Weaviate.Client.Tests/Integration/TestRbacGroups.cs index 9154f4bd..389af18c 100644 --- a/src/Weaviate.Client.Tests/Integration/TestRbacGroups.cs +++ b/src/Weaviate.Client.Tests/Integration/TestRbacGroups.cs @@ -8,23 +8,8 @@ namespace Weaviate.Client.Tests.Integration; /// RBAC Groups integration tests (Rest:8092 gRPC:50063). Groups functionality is limited; these /// tests mainly exercise listing groups and fetching their role assignments. /// -public class TestRbacGroups : IntegrationTests +public class TestRbacGroups : RbacIntegrationTests { - /// - /// Gets the value of the rest port - /// - public override ushort RestPort => 8092; - - /// - /// Gets the value of the grpc port - /// - public override ushort GrpcPort => 50063; - - /// - /// The admin api key - /// - private const string ADMIN_API_KEY = "admin-key"; - /// /// Initializes this instance /// @@ -36,11 +21,6 @@ public override async ValueTask InitializeAsync() RequireVersion("1.32.0"); } - /// - /// Gets the value of the credentials - /// - public override ICredentials? Credentials => Auth.ApiKey(ADMIN_API_KEY); - /// /// Tests that list groups /// diff --git a/src/Weaviate.Client.Tests/Integration/TestRbacRoles.cs b/src/Weaviate.Client.Tests/Integration/TestRbacRoles.cs index 5beec4df..6a0d0d78 100644 --- a/src/Weaviate.Client.Tests/Integration/TestRbacRoles.cs +++ b/src/Weaviate.Client.Tests/Integration/TestRbacRoles.cs @@ -11,28 +11,8 @@ namespace Weaviate.Client.Tests.Integration; /// /// [Trait("Category", "RBAC")] -public class TestRbacRoles : IntegrationTests +public class TestRbacRoles : RbacIntegrationTests { - /// - /// Gets the value of the rest port - /// - public override ushort RestPort => 8092; - - /// - /// Gets the value of the grpc port - /// - public override ushort GrpcPort => 50063; - - /// - /// The admin api key - /// - private const string ADMIN_API_KEY = "admin-key"; - - /// - /// Gets the value of the credentials - /// - public override ICredentials? Credentials => Auth.ApiKey(ADMIN_API_KEY); - /// /// Makes the role name using the specified suffix /// diff --git a/src/Weaviate.Client.Tests/Integration/TestRbacUsers.cs b/src/Weaviate.Client.Tests/Integration/TestRbacUsers.cs index ffb474df..9314fcc5 100644 --- a/src/Weaviate.Client.Tests/Integration/TestRbacUsers.cs +++ b/src/Weaviate.Client.Tests/Integration/TestRbacUsers.cs @@ -10,28 +10,8 @@ namespace Weaviate.Client.Tests.Integration; /// Requires Weaviate with RBAC running on port defined in 8092/50063. /// [Trait("Category", "RBAC")] -public class TestRbacUsers : IntegrationTests +public class TestRbacUsers : RbacIntegrationTests { - /// - /// Gets the value of the rest port - /// - public override ushort RestPort => 8092; - - /// - /// Gets the value of the grpc port - /// - public override ushort GrpcPort => 50063; - - /// - /// The admin api key - /// - private const string ADMIN_API_KEY = "admin-key"; - - /// - /// Gets the value of the credentials - /// - public override ICredentials? Credentials => Auth.ApiKey(ADMIN_API_KEY); - /// /// Initializes this instance /// diff --git a/src/Weaviate.Client.Tests/Integration/_Integration.cs b/src/Weaviate.Client.Tests/Integration/_Integration.cs index 450909ac..b4c2a868 100644 --- a/src/Weaviate.Client.Tests/Integration/_Integration.cs +++ b/src/Weaviate.Client.Tests/Integration/_Integration.cs @@ -83,12 +83,12 @@ public IntegrationTests() /// /// Gets the value of the rest port /// - public virtual ushort RestPort => 8080; + public virtual ushort RestPort => _configuration.GetValue("WV_HTTP_PORT", 8080); /// /// Gets the value of the grpc port /// - public virtual ushort GrpcPort => 50051; // default local gRPC port + public virtual ushort GrpcPort => _configuration.GetValue("WV_GRPC_PORT", 50051); /// /// Disposes this instance diff --git a/src/Weaviate.Client.Tests/Integration/_RbacIntegration.cs b/src/Weaviate.Client.Tests/Integration/_RbacIntegration.cs new file mode 100644 index 00000000..b06e4fe1 --- /dev/null +++ b/src/Weaviate.Client.Tests/Integration/_RbacIntegration.cs @@ -0,0 +1,25 @@ +namespace Weaviate.Client.Tests.Integration; + +using Microsoft.Extensions.Configuration; +using Weaviate.Client; + +/// +/// Base class for RBAC integration tests. Connects to the RBAC-enabled Weaviate instance +/// whose ports are controlled by WV_RBAC_HTTP_PORT / WV_RBAC_GRPC_PORT environment variables +/// (defaulting to 8092 / 50063 to match the local docker-compose RBAC service). +/// Use WV_RBAC_HTTP_PORT / WV_RBAC_GRPC_PORT to point these tests at the proxy RBAC endpoint. +/// +public abstract class RbacIntegrationTests : IntegrationTests +{ + /// The API key for the built-in admin user on the RBAC server. + protected const string ADMIN_API_KEY = "admin-key"; + + /// + public override ushort RestPort => _configuration.GetValue("WV_RBAC_HTTP_PORT", 8092); + + /// + public override ushort GrpcPort => _configuration.GetValue("WV_RBAC_GRPC_PORT", 50063); + + /// + public override ICredentials? Credentials => Auth.ApiKey(ADMIN_API_KEY); +} diff --git a/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj b/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj index 2369194c..c6370270 100644 --- a/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj +++ b/src/Weaviate.Client.Tests/Weaviate.Client.Tests.csproj @@ -28,6 +28,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/src/Weaviate.Client.Tests/packages.lock.json b/src/Weaviate.Client.Tests/packages.lock.json index df85fd79..62194ad4 100644 --- a/src/Weaviate.Client.Tests/packages.lock.json +++ b/src/Weaviate.Client.Tests/packages.lock.json @@ -14,6 +14,12 @@ "resolved": "4.0.0", "contentHash": "SM8Qp6L/E3tsLKDFkV23M8WHyp5l1YqsXkylQDjQjV02Z2tUL3J6j1gV0gMs4L6YtoSB7jZPw2dnDdnpEXnpNQ==" }, + "JunitXml.TestLogger": { + "type": "Direct", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "wpmmxsaAmvOylBPMngyjwWdUiG3tu9eouBB9VN/5gbiWvaE6TInbKH8F0IGFIH0zTUaqbx4t4FV1avvC5htpdA==" + }, "Microsoft.Extensions.Configuration.EnvironmentVariables": { "type": "Direct", "requested": "[9.0.0, )", diff --git a/src/Weaviate.Client/Rest/Dto/Models.g.cs b/src/Weaviate.Client/Rest/Dto/Models.g.cs index a10bb20f..78ca2147 100644 --- a/src/Weaviate.Client/Rest/Dto/Models.g.cs +++ b/src/Weaviate.Client/Rest/Dto/Models.g.cs @@ -558,14 +558,6 @@ internal partial record ExportCreateRequest public System.Collections.Generic.IList? Exclude { get; set; } = default!; - /// - /// Backend-specific configuration - /// - - [System.Text.Json.Serialization.JsonPropertyName("config")] - - public Config? Config { get; set; } = default!; - } /// @@ -894,6 +886,14 @@ internal partial record InvertedIndexConfig public System.Collections.Generic.IList? TokenizerUserDict { get; set; } = default!; + /// + /// User-defined named stopword lists. Each key is a preset name that can be referenced by a property's textAnalyzer.stopwordPreset field. The value is an array of stopword strings. + /// + + [System.Text.Json.Serialization.JsonPropertyName("stopwordPresets")] + + public System.Collections.Generic.IDictionary>? StopwordPresets { get; set; } = default!; + } /// @@ -1047,12 +1047,12 @@ internal partial record TokenizeRequest public TextAnalyzerConfig? AnalyzerConfig { get; set; } = default!; /// - /// Optional stopword configuration. When provided, stopwords are removed from query tokens but preserved in indexed tokens. + /// Optional named stopword configurations. Each key is a preset name that can be referenced by analyzerConfig.stopwordPreset. Each value is a StopwordConfig (with optional preset, additions, and removals). /// - [System.Text.Json.Serialization.JsonPropertyName("stopwordConfig")] + [System.Text.Json.Serialization.JsonPropertyName("stopwordPresets")] - public StopwordConfig? StopwordConfig { get; set; } = default!; + public System.Collections.Generic.IDictionary? StopwordPresets { get; set; } = default!; } @@ -1953,6 +1953,14 @@ internal partial record TextAnalyzerConfig public System.Collections.Generic.IList? AsciiFoldIgnore { get; set; } = default!; + /// + /// Stopword preset name. Overrides the collection-level invertedIndexConfig.stopwords for this property. Only applies to properties using 'word' tokenization. Can be a built-in preset ('en', 'none') or a user-defined preset from invertedIndexConfig.stopwordPresets. + /// + + [System.Text.Json.Serialization.JsonPropertyName("stopwordPreset")] + + public string? StopwordPreset { get; set; } = default!; + } [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.1.0 (NJsonSchema v11.5.1.0 (Newtonsoft.Json v13.0.0.0))")] @@ -4721,11 +4729,11 @@ internal enum PermissionAction [System.Text.Json.Serialization.JsonStringEnumMemberName(@"read_groups")] Read_groups = 33, - [System.Text.Json.Serialization.JsonStringEnumMemberName(@"read_mcp")] - Read_mcp = 34, - [System.Text.Json.Serialization.JsonStringEnumMemberName(@"create_mcp")] - Create_mcp = 35, + Create_mcp = 34, + + [System.Text.Json.Serialization.JsonStringEnumMemberName(@"read_mcp")] + Read_mcp = 35, [System.Text.Json.Serialization.JsonStringEnumMemberName(@"update_mcp")] Update_mcp = 36, @@ -4791,19 +4799,6 @@ internal enum ExportCreateRequestFile_format } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.1.0 (NJsonSchema v11.5.1.0 (Newtonsoft.Json v13.0.0.0))")] - internal partial record Config - { - /// - /// Path prefix within the bucket or filesystem - /// - - [System.Text.Json.Serialization.JsonPropertyName("path")] - - public string? Path { get; set; } = default!; - - } - [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.1.0 (NJsonSchema v11.5.1.0 (Newtonsoft.Json v13.0.0.0))")] internal enum ExportCreateResponseStatus { diff --git a/src/Weaviate.Client/Rest/Schema/openapi.json b/src/Weaviate.Client/Rest/Schema/openapi.json index 0631d15d..53f585ba 100644 --- a/src/Weaviate.Client/Rest/Schema/openapi.json +++ b/src/Weaviate.Client/Rest/Schema/openapi.json @@ -345,8 +345,8 @@ "delete_aliases", "assign_and_revoke_groups", "read_groups", - "read_mcp", "create_mcp", + "read_mcp", "update_mcp" ] } @@ -607,16 +607,6 @@ "type": "string" }, "description": "List of collection names to exclude from the export. Cannot be used with 'include'." - }, - "config": { - "type": "object", - "description": "Backend-specific configuration", - "properties": { - "path": { - "type": "string", - "description": "Path prefix within the bucket or filesystem" - } - } } } }, @@ -857,6 +847,17 @@ "items": { "$ref": "#/definitions/TokenizerUserDictConfig" } + }, + "stopwordPresets": { + "description": "User-defined named stopword lists. Each key is a preset name that can be referenced by a property's textAnalyzer.stopwordPreset field. The value is an array of stopword strings.", + "type": "object", + "x-omitempty": true, + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } } }, "type": "object" @@ -989,9 +990,13 @@ "description": "Optional text analyzer configuration (e.g. ASCII folding).", "$ref": "#/definitions/TextAnalyzerConfig" }, - "stopwordConfig": { - "description": "Optional stopword configuration. When provided, stopwords are removed from query tokens but preserved in indexed tokens.", - "$ref": "#/definitions/StopwordConfig" + "stopwordPresets": { + "description": "Optional named stopword configurations. Each key is a preset name that can be referenced by analyzerConfig.stopwordPreset. Each value is a StopwordConfig (with optional preset, additions, and removals).", + "type": "object", + "x-omitempty": true, + "additionalProperties": { + "$ref": "#/definitions/StopwordConfig" + } } } }, @@ -1654,6 +1659,11 @@ "items": { "type": "string" } + }, + "stopwordPreset": { + "description": "Stopword preset name. Overrides the collection-level invertedIndexConfig.stopwords for this property. Only applies to properties using 'word' tokenization. Can be a built-in preset ('en', 'none') or a user-defined preset from invertedIndexConfig.stopwordPresets.", + "type": "string", + "x-omitempty": true } }, "x-omitempty": true, @@ -3470,7 +3480,7 @@ }, "description": "# Introduction
Weaviate is an open source, AI-native vector database that helps developers create intuitive and reliable AI-powered applications.
### Base Path
The base path for the Weaviate server is structured as `[YOUR-WEAVIATE-HOST]:[PORT]/v1`. As an example, if you wish to access the `schema` endpoint on a local instance, you would navigate to `http://localhost:8080/v1/schema`. Ensure you replace `[YOUR-WEAVIATE-HOST]` and `[PORT]` with your actual server host and port number respectively.
### Questions?
If you have any comments or questions, please feel free to reach out to us at the community forum [https://forum.weaviate.io/](https://forum.weaviate.io/).
### Issues?
If you find a bug or want to file a feature request, please open an issue on our GitHub repository for [Weaviate](https://github.com/weaviate/weaviate).
### Need more documentation?
For a quickstart, code examples, concepts and more, please visit our [documentation page](https://docs.weaviate.io/weaviate).", "title": "Weaviate REST API", - "version": "1.37.0-rc.0" + "version": "1.37.1" }, "parameters": { "CommonAfterParameterQuery": { @@ -8145,61 +8155,6 @@ } } }, - "/schema/{className}/vectors/{vectorIndexName}/index": { - "delete": { - "summary": "Delete a collection's vector index.", - "description": "Deletes a specific vector index within a collection (`className`). The vector index to delete is identified by `vectorIndexName`.", - "operationId": "schema.objects.vectors.delete", - "x-serviceIds": [ - "weaviate.local.manipulate.meta" - ], - "tags": [ - "schema" - ], - "parameters": [ - { - "name": "className", - "description": "The name of the collection (class) containing the property.", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "vectorIndexName", - "description": "The name of the vector index.", - "in": "path", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Vector index deleted successfully." - }, - "401": { - "description": "Unauthorized or invalid credentials." - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - }, - "422": { - "description": "Invalid vector index or collection provided.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - }, - "500": { - "description": "An error occurred while deleting the vector index. Check the ErrorResponse for details.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, "/schema/{className}/properties/{propertyName}/tokenize": { "post": { "summary": "Tokenize text using a property's configuration", @@ -9648,13 +9603,6 @@ "required": true, "type": "string", "description": "The unique identifier of the export." - }, - { - "name": "path", - "in": "query", - "required": false, - "type": "string", - "description": "Optional path prefix within the bucket. If not specified, uses the backend's default path." } ], "responses": { @@ -9717,13 +9665,6 @@ "required": true, "type": "string", "description": "The unique identifier of the export to cancel." - }, - { - "name": "path", - "in": "query", - "required": false, - "type": "string", - "description": "Optional path prefix within the bucket." } ], "responses": { diff --git a/tools/linter_actions_pinned.sh b/tools/linter_actions_pinned.sh deleted file mode 100755 index ebe39b37..00000000 --- a/tools/linter_actions_pinned.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -# Lint GitHub Actions workflow files to ensure all external actions are pinned to SHA hashes. -# Usage: bash tools/linter_actions_pinned.sh - -set -euo pipefail - -ERRORS=0 - -for workflow in .github/workflows/*.yaml .github/workflows/*.yml; do - [ -f "$workflow" ] || continue - - while IFS= read -r line; do - lineno=$(echo "$line" | cut -d: -f1) - content=$(echo "$line" | cut -d: -f2-) - - # Extract the action reference (everything after "uses:") - action_ref=$(echo "$content" | sed -n 's/.*uses:[[:space:]]*//p' | xargs) - - # Skip local actions (starting with ./) - if [[ "$action_ref" == ./* ]]; then - continue - fi - - # Extract the version part (after @, before space or # comment) - version=$(echo "$action_ref" | sed -n 's/.*@\([^ #]*\).*/\1/p') - - if [ -z "$version" ]; then - echo "::error file=${workflow},line=${lineno}::Action missing version pin: ${action_ref}" - ERRORS=$((ERRORS + 1)) - continue - fi - - # Check that the version is a 40-character hex SHA - if ! echo "$version" | grep -qE '^[0-9a-f]{40}$'; then - echo "::error file=${workflow},line=${lineno}::Action not pinned to SHA: ${action_ref} (version: ${version})" - ERRORS=$((ERRORS + 1)) - fi - done < <(grep -n 'uses:' "$workflow") -done - -if [ "$ERRORS" -gt 0 ]; then - echo "" - echo "ERROR: Found ${ERRORS} action(s) not pinned to a SHA hash." - echo "Replace tag references (e.g., @v5) with the full commit SHA (e.g., @93cb6ef...)" - echo "Preserve the tag as a comment: uses: actions/checkout@ # v5" - exit 1 -fi - -echo "All GitHub Actions are pinned to SHA hashes."