Skip to content

Conversation

@jdolle
Copy link
Collaborator

@jdolle jdolle commented Mar 5, 2025

Background

Closes #6423

Description

Ideally this validation should exist on the model but if we did that, we'd start rejecting services that already exist.
This is at least a start to restricting services to better names.

Modifying documentation to mention this new restriction will come as a followup PR.

Checklist

  • Input validation
  • Output encoding
  • Authentication management
  • Session management
  • Access control
  • Cryptographic practices
  • Error handling and logging
  • Data protection
  • Communication security
  • System configuration
  • Database security
  • File management
  • Memory management
  • Testing

@jdolle jdolle self-assigned this Mar 5, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Enhanced validation for service names during schema publishing and checking, ensuring names are no longer than 64 characters, start with a letter, and include only alphanumeric characters, dashes, underscores, or slashes. This offers clear error feedback for invalid names.
    • Introduced standardized naming restrictions for external integrations.
  • Tests

    • Added new test cases to verify that service name validations behave as expected.

Walkthrough

The update introduces a validation mechanism in the publish and internalCheck methods of the SchemaPublisher class, which checks if the provided service name exists among the latest schemas and validates it against specific criteria. If the service name is invalid, a SchemaPublishError or SchemaCheckError is returned. Additionally, the return type of the ensureCompositeSchemas function has been modified to indicate it will always return an array of CompositeSchema objects. A new file has also been created to specify a patch for service name restrictions.

Changes

File Change Summary
packages/.../schema-publisher.ts Added validation in the publish and internalCheck methods to ensure the service name is less than or equal to 64 characters, starts with a letter, and contains only alphanumeric characters, dashes, underscores, or forward slashes. Returns SchemaPublishError or SchemaCheckError for invalid service names. Added helper function isValidServiceName.
packages/.../schema-helper.ts Updated the return type of ensureCompositeSchemas from `CompositeSchema[]
.changeset/.../rotten-scissors-matter.md New file created specifying a patch for hive and @graphql-hive/cli packages, enforcing service name restrictions to a maximum of 64 characters and permitted characters.
integration-tests/.../schema.spec.ts Added two new test cases for validating service names during schema publishing and checking operations, ensuring proper handling of valid and invalid service names.

Possibly related PRs


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jdolle jdolle marked this pull request as ready for review March 5, 2025 23:43
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@graphql-hive/cli 0.49.1-alpha-20250310194937-2e46942a65c00cf49427a8c5efb97dc0c2b7306c npm ↗︎ unpkg ↗︎
hive 5.1.4-alpha-20250310194937-2e46942a65c00cf49427a8c5efb97dc0c2b7306c npm ↗︎ unpkg ↗︎

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/services/api/src/modules/schema/providers/schema-publisher.ts (1)

1043-1058: Good implementation of service name validation requirements.

The validation logic correctly checks that service names meet the specified criteria:

  1. Must be less than 64 characters
  2. Must start with a letter
  3. Can only contain alphanumeric characters, dash (-), or underscore (_)

Two minor observations about the regex:

  1. The global flag (g) is unnecessary when using test() method as you're checking if the pattern matches the entire string
  2. \w already includes underscore, so there's a redundancy in [\w_-] where underscore is included twice

Consider this slight optimization to the regex:

-if (input.service.length > 64 || !/^[a-zA-Z][\w_-]+$/g.test(input.service)) {
+if (input.service.length > 64 || !/^[a-zA-Z][\w-]+$/.test(input.service)) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a5010e and 5d3bbef.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/schema/providers/schema-publisher.ts
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: code-style / eslint-and-prettier

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2025

🐋 This PR was built and pushed to the following Docker images:

Targets: build

Platforms: linux/arm64

Image Tag: 2e46942a65c00cf49427a8c5efb97dc0c2b7306c

Docker Bake metadata
{
"app": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/api/health",
          "build-arg:IMAGE_DESCRIPTION": "The app of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/app",
          "build-arg:PORT": "3000",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/app",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/0ob660i3wvgkn1ugnsmt1p3mu",
  "containerimage.config.digest": "sha256:bf6a0800365d2901f8b5329fe7e3725683d23a9af8e19ed7a21a904dc5008d93",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:0c7db7c5f2c0d3a4d50982be3f908a95a3fb10437294c4d7abf6ea333f282594",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:0c7db7c5f2c0d3a4d50982be3f908a95a3fb10437294c4d7abf6ea333f282594",
  "image.name": "ghcr.io/graphql-hive/app:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/app:console_655-arm64"
},
"buildx.build.warnings": [
  {
    "vertex": "sha256:99e1536bf98a1cbabd377a5de48bd1482cb1f14779aac4403001d59a88ca1d44",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9ERVNDUklQVElPTicgKGxpbmUgMTQp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 14
        },
        "end": {
          "line": 14
        }
      }
    ]
  },
  {
    "vertex": "sha256:85947d8057569c5f81df03af34cd059d0cb6abcb9040ffa14deb92b763f639af",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDEwKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 10
        },
        "end": {
          "line": 10
        }
      }
    ]
  },
  {
    "vertex": "sha256:85947d8057569c5f81df03af34cd059d0cb6abcb9040ffa14deb92b763f639af",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAxMik=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:85947d8057569c5f81df03af34cd059d0cb6abcb9040ffa14deb92b763f639af",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDEyKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:85947d8057569c5f81df03af34cd059d0cb6abcb9040ffa14deb92b763f639af",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9USVRMRScgKGxpbmUgMTUp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 15
        },
        "end": {
          "line": 15
        }
      }
    ]
  },
  {
    "vertex": "sha256:99e1536bf98a1cbabd377a5de48bd1482cb1f14779aac4403001d59a88ca1d44",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9USVRMRScgKGxpbmUgMTIp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:99e1536bf98a1cbabd377a5de48bd1482cb1f14779aac4403001d59a88ca1d44",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIwKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 20
        },
        "end": {
          "line": 20
        }
      }
    ]
  },
  {
    "vertex": "sha256:99e1536bf98a1cbabd377a5de48bd1482cb1f14779aac4403001d59a88ca1d44",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAxMyk=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 13
        },
        "end": {
          "line": 13
        }
      }
    ]
  },
  {
    "vertex": "sha256:99e1536bf98a1cbabd377a5de48bd1482cb1f14779aac4403001d59a88ca1d44",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIxKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 21
        },
        "end": {
          "line": 21
        }
      }
    ]
  },
  {
    "vertex": "sha256:99e1536bf98a1cbabd377a5de48bd1482cb1f14779aac4403001d59a88ca1d44",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIyKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 22
        },
        "end": {
          "line": 22
        }
      }
    ]
  },
  {
    "vertex": "sha256:99e1536bf98a1cbabd377a5de48bd1482cb1f14779aac4403001d59a88ca1d44",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAyMSk=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 21
        },
        "end": {
          "line": 21
        }
      }
    ]
  },
  {
    "vertex": "sha256:99e1536bf98a1cbabd377a5de48bd1482cb1f14779aac4403001d59a88ca1d44",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRQT1JUJyAobGluZSAyMik=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 22
        },
        "end": {
          "line": 22
        }
      }
    ]
  },
  {
    "vertex": "sha256:85947d8057569c5f81df03af34cd059d0cb6abcb9040ffa14deb92b763f639af",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDExKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 11
        },
        "end": {
          "line": 11
        }
      }
    ]
  },
  {
    "vertex": "sha256:85947d8057569c5f81df03af34cd059d0cb6abcb9040ffa14deb92b763f639af",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9ERVNDUklQVElPTicgKGxpbmUgMTcp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 17
        },
        "end": {
          "line": 17
        }
      }
    ]
  }
],
"commerce": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The commerce service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/commerce",
          "build-arg:PORT": "3010",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/commerce",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/wmcxb0gfe27qwif2fkeyn4w73",
  "containerimage.config.digest": "sha256:91587dfe8ab1f08b17f1d941198aaf5aa87621dc71e6e2898fa385a83a0a94ae",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:69bdd6e8eee99246aa21d1a545f26db5bab19203867c27671b2067938e4501b7",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:69bdd6e8eee99246aa21d1a545f26db5bab19203867c27671b2067938e4501b7",
  "image.name": "ghcr.io/graphql-hive/commerce:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/commerce:console_655-arm64"
},
"composition-federation-2": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "Federation 2 Composition Service for GraphQL Hive.",
          "build-arg:IMAGE_TITLE": "graphql-hive/composition-federation-2",
          "build-arg:PORT": "3069",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/external-composition",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/jlznc1opxsm9a5e0it2zdgexd",
  "containerimage.config.digest": "sha256:1b64b992a96da5ece0ef601bb9ab16a6f65fabeb57cef28d8d3e2fdffc18a89a",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:d2334767ab109ace1db6c77e7bc921180e484d1e70ee03b1011349d8ac971d3a",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:d2334767ab109ace1db6c77e7bc921180e484d1e70ee03b1011349d8ac971d3a",
  "image.name": "ghcr.io/graphql-hive/composition-federation-2:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/composition-federation-2:console_655-arm64"
},
"emails": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The emails service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/emails",
          "build-arg:PORT": "3006",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/emails",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/47ma936rvdgzarhzzi0yz83d8",
  "containerimage.config.digest": "sha256:cd25a5b13b65d591797a58b0e0b1f452f88bf26b09eceac3c7e97e5bf8be4d53",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:246764c363609d084a604176fa6c0dd20ced0d1a2ed297bcd5a4241653845223",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:246764c363609d084a604176fa6c0dd20ced0d1a2ed297bcd5a4241653845223",
  "image.name": "ghcr.io/graphql-hive/emails:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/emails:console_655-arm64"
},
"policy": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The policy service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/policy",
          "build-arg:PORT": "3012",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/policy",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/rjlorsqgz5hnupy1pjqgwxfwq",
  "containerimage.config.digest": "sha256:c7f557e6f710f0ae443c12366150c62504b82cf774112b3fb87d831a9c4e6583",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:820771d066d4d1916d10e84816a31a7f557f9ce01b80b1fc2d041383211772fc",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:820771d066d4d1916d10e84816a31a7f557f9ce01b80b1fc2d041383211772fc",
  "image.name": "ghcr.io/graphql-hive/policy:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/policy:console_655-arm64"
},
"schema": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The schema service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/schema",
          "build-arg:PORT": "3002",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/schema",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/sa6an9cirtqs7rk6sycug81vw",
  "containerimage.config.digest": "sha256:322fd01c60e1e58511f0f3ff5421af65d79b55b8a1379d1ae04c4439959ae40e",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:96f544f13b573576279779b44ad3b993e15f514083d42349a21f7512089b5476",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:96f544f13b573576279779b44ad3b993e15f514083d42349a21f7512089b5476",
  "image.name": "ghcr.io/graphql-hive/schema:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/schema:console_655-arm64"
},
"server": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The server service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/server",
          "build-arg:PORT": "3001",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/server",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/btiayizoxtjq1gr924r4gdsaq",
  "containerimage.config.digest": "sha256:0d83ae4eeb0c5c7073280d844c6376b721b91d7a6819bdf3d5c4b3c039a7fb3c",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:7ddb6b49c936801c35796889bdd4dfc71eb7bcab29bb901899b65941412dfc04",
    "size": 2076,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:7ddb6b49c936801c35796889bdd4dfc71eb7bcab29bb901899b65941412dfc04",
  "image.name": "ghcr.io/graphql-hive/server:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/server:console_655-arm64"
},
"storage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "migrations.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:IMAGE_DESCRIPTION": "The migrations service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/storage",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/vhc2zt8vmbbbhoi8jp4z41i64",
  "containerimage.config.digest": "sha256:1471aa80d62c6735c4bc47b5407e425afcd9320217d6dac4bb311d3f70abcfdb",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:859cae0efabe1f6c39db4bb953a3bad632c592dda030cdf28edb97be87012035",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:859cae0efabe1f6c39db4bb953a3bad632c592dda030cdf28edb97be87012035",
  "image.name": "ghcr.io/graphql-hive/storage:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/storage:console_655-arm64"
},
"tokens": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The tokens service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/tokens",
          "build-arg:PORT": "3003",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/tokens",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/m77l0ismxpltry2d3ut86hdqe",
  "containerimage.config.digest": "sha256:85ff30abbf714e47e43a2065414f7161950310b8f05603ee5ce1f8447d6b478b",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:169694d56160eaaa57d3e92053c0b18f723ae4a9bd950fe3f5bb8001cdc67f17",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:169694d56160eaaa57d3e92053c0b18f723ae4a9bd950fe3f5bb8001cdc67f17",
  "image.name": "ghcr.io/graphql-hive/tokens:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/tokens:console_655-arm64"
},
"usage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage",
          "build-arg:PORT": "3006",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/jsdlyjw4ynbl4rvuzbrt7ufn3",
  "containerimage.config.digest": "sha256:848d3e6e7da07fb5c8ab2cc9d1475a74e4c9881bc38d750a7b26b57446447661",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:967a4f4edfc20a4249e46ca28ae9b96f6b907fab2015c76e0abcce00bc4fb48e",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:967a4f4edfc20a4249e46ca28ae9b96f6b907fab2015c76e0abcce00bc4fb48e",
  "image.name": "ghcr.io/graphql-hive/usage:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/usage:console_655-arm64"
},
"usage-ingestor": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage-ingestor",
          "build-arg:PORT": "3007",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage-ingestor",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/2sbk34t4d06m3zzn4t2mf82qx",
  "containerimage.config.digest": "sha256:900b2ec2f52696a112d25335dfed37256b29f04624628d3e59ae30505a3572b0",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:16d358af4dadee4d484d81a3a0acbc15861ec795331e16a6e0b30ccdef49d61d",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:16d358af4dadee4d484d81a3a0acbc15861ec795331e16a6e0b30ccdef49d61d",
  "image.name": "ghcr.io/graphql-hive/usage-ingestor:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/usage-ingestor:console_655-arm64"
},
"webhooks": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The webhooks ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/webhooks",
          "build-arg:PORT": "3005",
          "build-arg:RELEASE": "2e46942a65c00cf49427a8c5efb97dc0c2b7306c",
          "build-arg:SERVICE_DIR_NAME": "@hive/webhooks",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:dockerfile": "vwtvaockcxy2zg54vcsrvt112",
          "local-sessionid:shared": "vwtvaockcxy2zg54vcsrvt112"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-cdcca3ba-965a-48b6-98a4-8f3451edc651/builder-cdcca3ba-965a-48b6-98a4-8f3451edc6510/mhi0ma3q2jnesm8pfyq5faef7",
  "containerimage.config.digest": "sha256:3a86f3e62ce4327e68a1e218b8abebfe0a9c34c041a29e7c1277983bf11207e9",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:67f894ab918d3efbb013bcbe22678d81b8546782f70364dfbb791dfc477d1c84",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:67f894ab918d3efbb013bcbe22678d81b8546782f70364dfbb791dfc477d1c84",
  "image.name": "ghcr.io/graphql-hive/webhooks:2e46942a65c00cf49427a8c5efb97dc0c2b7306c-arm64,ghcr.io/graphql-hive/webhooks:console_655-arm64"
}
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/services/api/src/modules/schema/providers/schema-publisher.ts (3)

1043-1058: Add Zod validation for service names to align with project standards

The current implementation adds direct validation for service names which is good, but it could be improved by using Zod for consistency with other validations in the codebase. Also, there's a discrepancy between the regex and the error message - the regex allows forward slashes (/) but the error message doesn't mention this.

Consider refactoring this to use Zod validation for consistency, similar to the SchemaCheckContextIdModel at the end of the file:

-    if (!latestVersion && input.service) {
-      // this is a new service. Validate the service name.
-      if (input.service.length > 64 || !/^[a-zA-Z][\w\/_-]*$/g.test(input.service)) {
-        return {
-          __typename: 'SchemaPublishError',
-          valid: false,
-          changes: [],
-          errors: [
-            {
-              message:
-                'Invalid service name. Service name must be less than 64 characters, must start with a letter, and can only contain alphanumeric characters, dash (-), or underscore (_).',
-            },
-          ],
-        };
-      }
-    }
+    if (!latestVersion && input.service) {
+      // this is a new service. Validate the service name.
+      const ServiceNameModel = z
+        .string()
+        .max(64, {
+          message: 'Service name must be less than 64 characters.',
+        })
+        .regex(/^[a-zA-Z][\w_-]*$/, {
+          message: 'Service name must start with a letter and can only contain alphanumeric characters, dash (-), or underscore (_).',
+        });
+      
+      const result = ServiceNameModel.safeParse(input.service);
+      if (!result.success) {
+        return {
+          __typename: 'SchemaPublishError',
+          valid: false,
+          changes: [],
+          errors: [
+            {
+              message: `Invalid service name. ${result.error.errors[0].message}`,
+            },
+          ],
+        };
+      }
+    }

Note that I've removed the forward slash (/) from the regex and the global flag (g) as it's unnecessary for a pattern match test.


1045-1045: Remove the global flag from the regex pattern

The global flag (g) in the regex pattern is unnecessary when using the test() method, as this method only checks if the pattern matches the string.

-      if (input.service.length > 64 || !/^[a-zA-Z][\w\/_-]*$/g.test(input.service)) {
+      if (input.service.length > 64 || !/^[a-zA-Z][\w\/_-]*$/.test(input.service)) {

1046-1056: Extract service name validation limits to constants

The magic number "64" is hardcoded directly in the code. Consider extracting this and other validation rules to named constants for better maintainability and documentation.

Refactor the validation by adding constants at the class level or at the top of the file:

// Add at the top of the file or as class constants
+ const SERVICE_NAME_MAX_LENGTH = 64;
+ const SERVICE_NAME_PATTERN = /^[a-zA-Z][\w\/_-]*$/;

// Then in the validation code
-      if (input.service.length > 64 || !/^[a-zA-Z][\w\/_-]*$/g.test(input.service)) {
+      if (input.service.length > SERVICE_NAME_MAX_LENGTH || !SERVICE_NAME_PATTERN.test(input.service)) {
         return {
           __typename: 'SchemaPublishError',
           valid: false,
           changes: [],
           errors: [
             {
               message:
-                'Invalid service name. Service name must be less than 64 characters, must start with a letter, and can only contain alphanumeric characters, dash (-), or underscore (_).',
+                `Invalid service name. Service name must be less than ${SERVICE_NAME_MAX_LENGTH} characters, must start with a letter, and can only contain alphanumeric characters, dash (-), or underscore (_).`,
             },
           ],
         };
       }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d3bbef and 6e71690.

📒 Files selected for processing (1)
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/schema/providers/schema-publisher.ts
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (1)
packages/services/api/src/modules/schema/providers/schema-publisher.ts (1)

1043-1058:

❓ Verification inconclusive

Verify validation implementation with existing services and automated tests

This PR adds validation for new service names only, which is a good approach to prevent disruption of existing services. Ensure thorough testing with various service name examples to confirm the validation works as expected.


🏁 Script executed:

#!/bin/bash
# Find any existing tests that might need to be updated for the new validation

# Search for tests related to SchemaPublisher.publish
rg -A 10 "SchemaPublisher.*publish" --glob "*.test.ts" --glob "*.spec.ts"

# Search for any examples of service name usage that might need validation
rg -l "service_name" --glob "*.ts"

Length of output: 1140


Ensure Comprehensive Test Coverage for New Service Name Validation

The new service name validation logic in packages/services/api/src/modules/schema/providers/schema-publisher.ts (lines 1043-1058) is well-isolated to new services, which minimizes risk to existing functionality. However, please verify that the integration tests—particularly those in integration-tests/tests/api/schema/publish.spec.ts—cover all edge cases. Confirm that tests include scenarios for service names that are too long, don't start with a letter, or include invalid characters, and ascertain that any modules referencing service names (as found in several files) remain consistent with this new validation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/services/api/src/modules/schema/providers/schema-publisher.ts (3)

1043-1058: The validation logic looks good, but there's a minor formatting issue in the error message.

The service name validation logic is correctly implemented, checking both length and character requirements for new services. However, there's an extra comma in the error message that should be removed.

-                'Invalid service name. Service name must be less than 64 characters, must start with a letter, and can only contain alphanumeric characters, dash (-), underscore (_), , or forward slash (/).',
+                'Invalid service name. Service name must be less than 64 characters, must start with a letter, and can only contain alphanumeric characters, dash (-), underscore (_), or forward slash (/).',

1045-1045: Optimize the regex pattern by removing redundancy.

The current regex has redundant parts that can be simplified without changing its behavior.

- if (input.service.length > 64 || !/^[a-zA-Z][\w\/_-]*$/g.test(input.service)) {
+ if (input.service.length > 64 || !/^[a-zA-Z][\w\/-]*$/g.test(input.service)) {

The _ in the character class is redundant since \w already includes underscores (it matches [a-zA-Z0-9_]). Also, the global flag (g) is unnecessary for a single validation test.


1045-1057: Consider optimizing the validation logic for better performance and clearer error messages.

For better user experience and performance, consider separating the validation checks and providing more specific error messages for each case.

- if (input.service.length > 64 || !/^[a-zA-Z][\w\/_-]*$/g.test(input.service)) {
-   return {
-     __typename: 'SchemaPublishError',
-     valid: false,
-     changes: [],
-     errors: [
-       {
-         message:
-           'Invalid service name. Service name must be less than 64 characters, must start with a letter, and can only contain alphanumeric characters, dash (-), underscore (_), , or forward slash (/).',
-       },
-     ],
-   };
- }
+ // Check length first
+ if (input.service.length > 64) {
+   return {
+     __typename: 'SchemaPublishError',
+     valid: false,
+     changes: [],
+     errors: [
+       {
+         message: 'Service name is too long. Maximum length is 64 characters.',
+       },
+     ],
+   };
+ }
+ 
+ // Then check character constraints
+ if (!/^[a-zA-Z][\w\/-]*$/.test(input.service)) {
+   return {
+     __typename: 'SchemaPublishError',
+     valid: false,
+     changes: [],
+     errors: [
+       {
+         message: 'Invalid service name. Service name must start with a letter and can only contain alphanumeric characters, dash (-), underscore (_), or forward slash (/).',
+       },
+     ],
+   };
+ }

This approach has several benefits:

  1. Better performance - avoids running regex on long strings that will fail the length check anyway
  2. More specific error messages help users understand exactly what's wrong
  3. Simplified regex without redundant parts
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e71690 and b7c1ec1.

📒 Files selected for processing (1)
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/schema/providers/schema-publisher.ts
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (2)
packages/services/api/src/modules/schema/providers/schema-publisher.ts (2)

1045-1045: Fix inconsistency between regex and error message regarding allowed characters

The regex pattern allows forward slashes (/), but the error message doesn't mention this. This creates an inconsistency between what's allowed and what's communicated to the user.


1043-1058:

❓ Verification inconclusive

Verify behavior with existing service names in the system.

The validation is only applied for new services (!latestVersion && input.service), which aligns with the PR objective of not disrupting existing services. Consider creating a migration plan for legacy services that don't meet these criteria.


🏁 Script executed:

#!/bin/bash
# Description: Find any existing service names that wouldn't pass the new validation rules

# Search for service_name fields in the codebase
echo "Checking for service name patterns in the codebase..."

# First search for service names with invalid characters
rg "service_name['\"]?\s*[=:]\s*['\"]([^a-zA-Z]|[^a-zA-Z0-9_\/-])" --type ts

# Then search for potentially long service names (look for strings with 50+ chars that might be service names)
echo "Checking for potentially long service names..."
rg "service_name['\"]?\s*[=:]\s*['\"][a-zA-Z][a-zA-Z0-9_\/-]{49,}" --type ts

Length of output: 741


Action Required: Verify Legacy Service Name Behavior & Fix Regex in Verification Script

  • The new validation correctly checks new services (!latestVersion && input.service) without disrupting existing services, aligning with the intent.
  • However, the verification script encountered regex parse errors (unrecognized escape sequences). Please update the regex patterns to match your engine's syntax—for example, adjusting escape sequences or quoting—to accurately search for existing service names.
  • Additionally, consider manually verifying any legacy service names that might not comply with the new naming criteria and prepare a migration plan for them if required.

this.schemaManager.getMaybeLatestVersion(target),
]);

if (!latestVersion && input.service) {
Copy link
Contributor

@n1ru4l n1ru4l Mar 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we would rather have to check whether the latestVersion has a service associated named like this service? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There would also be an edge case, where someone deletes a service (hive schema:delete) and tries to republish it, but then it fails, because it would no longer be in the previous version 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Youre right about the latestVersion. I'll fix that.

For the edge case, I think we can ignore that. If they are deleting then we can treat like new.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added validation to schema check also. This will prevent us from having issues where the check passes but push fails because of the service name.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7c1ec1 and 2b17d4f.

📒 Files selected for processing (2)
  • packages/services/api/src/modules/schema/providers/schema-helper.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/schema/providers/schema-publisher.ts
  • packages/services/api/src/modules/schema/providers/schema-helper.ts
🔇 Additional comments (1)
packages/services/api/src/modules/schema/providers/schema-helper.ts (1)

40-42: Improved type signature for ensureCompositeSchemas

Removing the | never from the return type is a good change. The function now correctly indicates it returns an array of CompositeSchema objects, which is more accurate since the function doesn't actually throw any exceptions (it just filters the input array).

throw new Error('Expected a single schema');
}

export function ensureCompositeSchemas(schemas: readonly Schema[]): CompositeSchema[] | never {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed this never was because the function threw an array, but it only filters. So I'm not sure why this never is here... removed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/services/api/src/modules/schema/providers/schema-publisher.ts (3)

331-353: Consider unifying repeated validation logic for new services.
The validation for a new service name here is almost identical to the subsequent block in the publish method. Extracting this logic to a shared function (beyond isValidServiceName) or unifying checks may reduce duplication and improve maintainability.


1077-1099: Again, consider consolidating duplicate checks for new service name validation.
Similar to the earlier block for schema checks, you can refactor these lines to avoid duplication of the same checks and error messaging in multiple locations.


2497-2500: Remove unnecessary regex “g” flag.
Since you are doing a single pass test of the entire string, you don’t need the g flag here.

-function isValidServiceName(service: string): boolean {
-  return service.length <= 64 && /^[a-zA-Z][\w\/_-]*$/g.test(service);
+function isValidServiceName(service: string): boolean {
+  return service.length <= 64 && /^[a-zA-Z][\w\/_-]*$/.test(service);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16ab676 and fdafc38.

📒 Files selected for processing (1)
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/schema/providers/schema-publisher.ts
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (1)
packages/services/api/src/modules/schema/providers/schema-publisher.ts (1)

1064-1075: Conditional fetch optimization looks good.
Fetching the latest schemas only when input.service is provided helps avoid unnecessary database calls for single-service workflows.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
.changeset/rotten-scissors-matter.md (2)

6-6: Descriptive Text Clarity:
The description clearly states the new restriction for service names (limited to 64 characters and restricted to alphanumeric characters, underscores, hyphens, and slashes). However, there is a minor typographical error: "alphanumberic" should be "alphanumeric" to maintain clarity and professionalism.


7-7: Stray Content Identified:
Line 7 contains an isolated "7" which appears to be unintentional. If this is not a deliberate part of the changeset description, consider removing it to avoid confusion.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdafc38 and 8ef9e75.

📒 Files selected for processing (1)
  • .changeset/rotten-scissors-matter.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`.changeset/**`: A special folder with the files created/man...

.changeset/**: A special folder with the files created/managed by Changesets tool.
In PRs that already includes this file, please make sure that the content of the markdown file includes:

  • Full description of the change
  • A link to the relevant GitHub issues (if any)
  • Example with before/after
  • If the change is of type major, please ensure that the content have content about migration with before/after.
  • .changeset/rotten-scissors-matter.md
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (1)
.changeset/rotten-scissors-matter.md (1)

1-4: YAML Front Matter Integrity:
The YAML front matter is correctly structured and specifies patch releases for both 'hive' and '@graphql-hive/cli'. Ensure that these package changes align with your release process.

Copy link
Contributor

@n1ru4l n1ru4l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets add integration tests for this

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/services/api/src/modules/schema/providers/schema-publisher.ts (3)

2520-2522: Optimize the service name validation regex

The regex pattern could be improved for better readability and efficiency:

  1. \w already includes alphanumeric characters and underscore, so including _ separately is redundant
  2. The global flag g is unnecessary for a single test
  3. Consider adding JSDoc comments to document this function's purpose and parameters
+/**
+ * Validates a service name against the following criteria:
+ * - Must be 64 characters or less
+ * - Must start with a letter
+ * - Can only contain alphanumeric characters, dash (-), underscore (_), or forward slash (/)
+ * @param service The service name to validate
+ * @returns boolean indicating if the service name is valid
+ */
 function isValidServiceName(service: string): boolean {
-  return service.length <= 64 && /^[a-zA-Z][\w\/_-]*$/g.test(service);
+  return service.length <= 64 && /^[a-zA-Z][\w\/-]*$/.test(service);
 }

331-353: Consider extracting the error message to avoid duplication

The error message text for invalid service names is duplicated between the internalCheck and publish methods, which could lead to inconsistencies if one is updated but not the other.

Extract the error message to a constant at the file level:

+const INVALID_SERVICE_NAME_ERROR = 'Invalid service name. Service name must be 64 characters or less, must start with a letter, and can only contain alphanumeric characters, dash (-), underscore (_), or forward slash (/).';

// Then in this function
 if (!serviceExists && !isValidServiceName(input.service)) {
   return {
     __typename: 'SchemaCheckError',
     valid: false,
     changes: [],
     warnings: [],
     errors: [
       {
         message:
-          'Invalid service name. Service name must be 64 characters or less, must start with a letter, and can only contain alphanumeric characters, dash (-), underscore (_), or forward slash (/).',
+          INVALID_SERVICE_NAME_ERROR,
       },
     ],
   } as const;
 }

1085-1096: Improve readability of conditional Promise resolution

The conditional Promise.resolve() could be made clearer by using a ternary operator to make the intent more explicit.

 const [contracts, latestVersion, latestSchemas] = await Promise.all([
   this.contracts.getActiveContractsByTargetId({ targetId: selector.targetId }),
   this.schemaManager.getMaybeLatestVersion(target),
-  input.service
-    ? this.storage.getLatestSchemas({
-        organizationId: selector.organizationId,
-        projectId: selector.projectId,
-        targetId: selector.targetId,
-      })
-    : Promise.resolve(),
+  input.service ? this.storage.getLatestSchemas({
+    organizationId: selector.organizationId,
+    projectId: selector.projectId,
+    targetId: selector.targetId,
+  }) : Promise.resolve(undefined),
 ]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7604c80 and 25164bd.

📒 Files selected for processing (1)
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/schema/providers/schema-publisher.ts
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: test / unit
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
🔇 Additional comments (1)
packages/services/api/src/modules/schema/providers/schema-publisher.ts (1)

1098-1120: Use the extracted error message constant here as well

This is the second occurrence of the same error message text. Using the same constant here would ensure consistency.

 if (!serviceExists && !isValidServiceName(input.service)) {
   return {
     __typename: 'SchemaPublishError',
     valid: false,
     changes: [],
     errors: [
       {
         message:
-          'Invalid service name. Service name must be 64 characters or less, must start with a letter, and can only contain alphanumeric characters, dash (-), underscore (_), or forward slash (/).',
+          INVALID_SERVICE_NAME_ERROR,
       },
     ],
   };
 }

@jdolle jdolle requested a review from n1ru4l March 7, 2025 16:41
@n1ru4l
Copy link
Contributor

n1ru4l commented Mar 10, 2025

I am not sure whether we want to allow /.

My main argument for this is that our resource identifiers use the / character to separate the resource partials.

Examples:

target/${args.targetId}/appDeployment/${args.appDeploymentName}`
target/${args.targetId}/service/${args.serviceName}

I think it is very unlikely that service name or app deployment will have a "sub" resource e.g.

target/${args.targetId}/service/${args.serviceName}/foobars

But if we would introduce something like this, it would require additional adjustments if the / character is allowed in service names (e.g. url encode the service name).

Thoughts?

@jdolle
Copy link
Collaborator Author

jdolle commented Mar 10, 2025

That makes sense to me. It seems like best practices to keep the character list to a minimum also to avoid edge cases such as this. So I'll adjust that.

@jdolle jdolle enabled auto-merge (squash) March 10, 2025 18:49
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
integration-tests/tests/cli/schema.spec.ts (2)

98-151: Well-structured test for service name validation in publishing

This test comprehensively validates the service name restrictions when publishing a schema. The test appropriately:

  • Skips for single project types where service names aren't applicable
  • Tests rejection of numeric-only names
  • Tests rejection of names with special characters
  • Verifies acceptance of valid names with alphanumeric characters and hyphens

However, based on the PR objectives, there was discussion about not allowing the / character specifically. Consider adding an explicit test case for a service name containing the / character to ensure it's properly rejected.

        await expect(
          schemaPublish([
            '--registry.accessToken',
            secret,
            '--author',
            'Kamil',
            '--commit',
            'abc123',
            '--service',
            'asdf$#%^#@!#',
            ...serviceUrlArgs,
            'fixtures/init-schema.graphql',
          ]),
        ).rejects.toMatchSnapshot('specialCharacters');

+        // Test rejection of service name with slash character specifically
+        await expect(
+          schemaPublish([
+            '--registry.accessToken',
+            secret,
+            '--author',
+            'Kamil',
+            '--commit',
+            'abc123',
+            '--service',
+            'invalid/name',
+            ...serviceUrlArgs,
+            'fixtures/init-schema.graphql',
+          ]),
+        ).rejects.toMatchSnapshot('nameWithSlash');
+

153-203: Consistent validation test for schema checking operation

This test properly mirrors the publishing test to ensure consistent validation behavior when checking schemas. The test cases comprehensively cover numeric-only names, names with special characters, and valid names.

Similar to the previous test, consider adding a specific test case for a service name containing the / character to address the explicit concern raised in the PR objectives discussion.

        await expect(
          schemaCheck([
            '--registry.accessToken',
            secret,
            '--author',
            'Kamil',
            '--commit',
            'abc123',
            '--service',
            'asdf$#%^#@!#',
            'fixtures/init-schema.graphql',
          ]),
        ).rejects.toMatchSnapshot('specialCharacters');

+        // Test rejection of service name with slash character specifically
+        await expect(
+          schemaCheck([
+            '--registry.accessToken',
+            secret,
+            '--author',
+            'Kamil',
+            '--commit',
+            'abc123',
+            '--service',
+            'invalid/name',
+            'fixtures/init-schema.graphql',
+          ]),
+        ).rejects.toMatchSnapshot('nameWithSlash');
+
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b17f30 and 2e46942.

📒 Files selected for processing (1)
  • integration-tests/tests/cli/schema.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`integration-tests/**`: integration tests for ensuring featu...

integration-tests/**: integration tests for ensuring features and important flows are running. Usually, changes to the packages/ directory will affect this. In ideal flow, each PR should include integration tests.

  • integration-tests/tests/cli/schema.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: typescript / typecheck
  • GitHub Check: test / unit
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: code-style / eslint-and-prettier

Copy link
Contributor

@n1ru4l n1ru4l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 🥳

@jdolle jdolle merged commit c0d9ca3 into main Mar 11, 2025
28 checks passed
@jdolle jdolle deleted the console-655 branch March 11, 2025 06:37
@n1ru4l n1ru4l mentioned this pull request Mar 11, 2025
14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

sanitize/validate service name input

2 participants