Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(laboratory): scope environment variables to target #6500

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

jasonkuhrt
Copy link
Member

@jasonkuhrt jasonkuhrt commented Feb 11, 2025

Background

This change makes Laboratory environment variables scoped to target.

Description

The application layer changes are minimal by design. The approach taken is to factor out an "engine" for reading versionable entries with automatic migration. Migration is trivial right now given its simply key read migrations with no concern about value contents. Code is written to facilitate addition of schema migration logic later, namely a few comments at key locations.

Checklist

  • E2E Testing
  • Unit Testing (would like to add a few for the versioning lib)
  • changeset

Copy link
Contributor

coderabbitai bot commented Feb 11, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Laboratory Environment Variables are now scoped per target. This ensures that changes in one target’s laboratory do not affect others, with a seamless migration from the previous global setup.
    • Improved local storage handling introduces versioning support for more robust data management.
  • Tests

    • Expanded end-to-end tests verify target-specific behavior and the correct display of environment configurations.
  • Chores

    • Updated configuration for testing environments, including conditional retries and enhanced seed data management.

Walkthrough

This pull request introduces a new scoping mechanism for Laboratory Environment Variables, ensuring they are specific to each Target rather than being shared globally. The implementation transitions from using a static key in local storage to a structure that supports versioned entries through a new VersionedEntrySpec. Local storage hooks have been updated, a key-value store abstraction is introduced, and migration strategies are outlined to facilitate this change. Cypress configurations and tests have been adjusted to validate the new behavior, enhancing the management of environment variables across different targets.

Changes

File(s) Change Summary
.changeset/rich-terms-knock.md Introduces a new scoping mechanism for Laboratory Environment Variables with a migration strategy from global to target-scoped keys.
packages/web/app/src/lib/hooks/use-local-storage-json.ts, packages/web/app/src/lib/hooks/use-local-storage.ts Updates local storage hooks to support versioned entries via VersionedEntrySpec; modifies key handling, retrieval via readVersionedEntryLocalStorage, and dependency tracking.
packages/web/app/src/lib/preflight/graphiql-plugin.tsx Adds a new constant for environment variable storage keys with a dynamic, scoped structure, replacing the legacy single string key.
packages/web/app/src/lib/versioned-entry.ts, packages/web/app/src/lib/versioned-entry.spec.ts Introduces a key-value store abstraction, VersionedEntrySpec, and policies for handling previous entries; adds tests to validate migration and versioning behavior.
cypress.config.ts, cypress/e2e/laboratory-environment-variables.cy.ts Modifies Cypress configuration for conditional retries and enhances the seed task to include detailed target information; adds E2E tests validating scoped and global environment variable behavior.
integration-tests/testkit/seed.ts Enhances target creation logic by adding a computed URL-like path property to each target for improved context.
cypress/e2e/laboratory-preflight.cy.ts Updates context object from data to ctx for consistency across test cases.
cypress/support/e2e.ts Adds import for cypress-localstorage-commands to enhance testing capabilities.
cypress/support/testkit.ts Introduces persistAuthenticationCookies function to manage authentication cookies across tests.
package.json Adds cypress-localstorage-commands dependency to devDependencies.

Possibly related PRs

  • fix(laboratory): Preflight Script environment variables no parse storage read #6450 – The changes in the main PR regarding the introduction of a new scoping mechanism for Laboratory Environment Variables are related to the modifications in the retrieved PR, which also addresses local storage management and the handling of environment variables in the context of preflight scripts. Both PRs involve updates to how local storage is utilized, particularly in relation to environment variables.
  • refactor(libraries): type safe local storage json #6455 – The changes in the main PR regarding the introduction of a new scoping mechanism for Laboratory Environment Variables are related to the modifications in the retrieved PR that enhance the useLocalStorageJson hook to incorporate Zod schema validation, as both involve updates to how local storage is managed and accessed.

📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 895d220 and 8a1c77f.

📒 Files selected for processing (1)
  • integration-tests/testkit/seed.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • integration-tests/testkit/seed.ts
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: test / integration (3)
  • GitHub Check: test / integration (2)
  • GitHub Check: test / integration (1)
  • GitHub Check: test / e2e

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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.

Copy link
Contributor

github-actions bot commented Feb 11, 2025

📚 Storybook Deployment

The latest changes are available as preview in: https://f6cf5a80.hive-storybook.pages.dev

Copy link
Contributor

github-actions bot commented Feb 11, 2025

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

Targets: build

Platforms: linux/arm64

Image Tag: 8a1c77f9b1566ad7b9c083096f037f8d7016ccab

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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/u03pvsacfj78c7ta2vikymg5h",
  "containerimage.config.digest": "sha256:bf56e10fa64afae845e6eb49dd3da78de7a6d8983f74778b252fb4f6aa77568f",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:567b09287f4a980c67cdf49ab54a19932e685cc91da8b21d8f22a4b3feb1a2af",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:567b09287f4a980c67cdf49ab54a19932e685cc91da8b21d8f22a4b3feb1a2af",
  "image.name": "ghcr.io/graphql-hive/app:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/app:console_994-arm64"
},
"buildx.build.warnings": [
  {
    "vertex": "sha256:59a8da04ea58a18a470e2c684d98bcf4c0dda6f1910759e2051c3d7ab1a2f119",
    "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:59a8da04ea58a18a470e2c684d98bcf4c0dda6f1910759e2051c3d7ab1a2f119",
    "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
        }
      }
    ]
  },
  {
    "vertex": "sha256:d0db291ada70c43c405b5a7efff7caa1e79023b12f45120693673b05a92a3446",
    "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:d0db291ada70c43c405b5a7efff7caa1e79023b12f45120693673b05a92a3446",
    "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:d0db291ada70c43c405b5a7efff7caa1e79023b12f45120693673b05a92a3446",
    "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:d0db291ada70c43c405b5a7efff7caa1e79023b12f45120693673b05a92a3446",
    "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:d0db291ada70c43c405b5a7efff7caa1e79023b12f45120693673b05a92a3446",
    "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:59a8da04ea58a18a470e2c684d98bcf4c0dda6f1910759e2051c3d7ab1a2f119",
    "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:d0db291ada70c43c405b5a7efff7caa1e79023b12f45120693673b05a92a3446",
    "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:59a8da04ea58a18a470e2c684d98bcf4c0dda6f1910759e2051c3d7ab1a2f119",
    "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:59a8da04ea58a18a470e2c684d98bcf4c0dda6f1910759e2051c3d7ab1a2f119",
    "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:d0db291ada70c43c405b5a7efff7caa1e79023b12f45120693673b05a92a3446",
    "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:d0db291ada70c43c405b5a7efff7caa1e79023b12f45120693673b05a92a3446",
    "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:59a8da04ea58a18a470e2c684d98bcf4c0dda6f1910759e2051c3d7ab1a2f119",
    "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
        }
      }
    ]
  }
],
"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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/6ikd82ghrz98nsrj7e6u0p4bc",
  "containerimage.config.digest": "sha256:f7bbb4ad4cae5468c997ae6533c30d729863381884fe3cbf63f0f71d86173fb6",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:82df5c1e33f9d594b0a591350b8fdf17f0c0079429300da46a906787684772d5",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:82df5c1e33f9d594b0a591350b8fdf17f0c0079429300da46a906787684772d5",
  "image.name": "ghcr.io/graphql-hive/composition-federation-2:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/composition-federation-2:console_994-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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/r86bu5c5n617ka3c4t0s7k5di",
  "containerimage.config.digest": "sha256:17ee283b92e1e9a29e0772135fea4e3349d0b0e16207aa0da85718478bff6952",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:e74466e6f42535138e85759bf7513b37eec70590fc1f1a04d83a366c8073ccb8",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:e74466e6f42535138e85759bf7513b37eec70590fc1f1a04d83a366c8073ccb8",
  "image.name": "ghcr.io/graphql-hive/emails:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/emails:console_994-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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/m20s74xf3ri8kic0pd2d65oe8",
  "containerimage.config.digest": "sha256:9e2386a323842267b26d61b4ae1a1eacb84593a3218651702fc93b28672b1552",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:420eda98eae5b3ab63aafd51a0fc0db48c98c9f144fc673cbe717637e5cf0544",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:420eda98eae5b3ab63aafd51a0fc0db48c98c9f144fc673cbe717637e5cf0544",
  "image.name": "ghcr.io/graphql-hive/policy:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/policy:console_994-arm64"
},
"rate-limit": {
  "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 rate limit service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/rate-limit",
          "build-arg:PORT": "3009",
          "build-arg:RELEASE": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "build-arg:SERVICE_DIR_NAME": "@hive/rate-limit",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/7yhxz5ir9olk2htsv28itllh2",
  "containerimage.config.digest": "sha256:7192d0984e6aa43af5233b751516efb376ce68b70c553ad5402fce2dd6c7414f",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:5cdc0ae55b092101f8615851da7e6dda2576d2d07768b8eb48bf21b908863d21",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:5cdc0ae55b092101f8615851da7e6dda2576d2d07768b8eb48bf21b908863d21",
  "image.name": "ghcr.io/graphql-hive/rate-limit:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/rate-limit:console_994-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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/l3xwvl2hkfzlgq6mkij45gpss",
  "containerimage.config.digest": "sha256:c8c93b87d3f5b0f7e4006eb1e8f7aa408fd0527559c064b7d0716042a471c54a",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:6d3d8584a9e1e5693be1686a9d8267ee9e1d48568ed4a0bee428e3ac7b6739b5",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:6d3d8584a9e1e5693be1686a9d8267ee9e1d48568ed4a0bee428e3ac7b6739b5",
  "image.name": "ghcr.io/graphql-hive/schema:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/schema:console_994-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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/yrc5mxa0umfbver31s99pw4vn",
  "containerimage.config.digest": "sha256:982dc5f6870494c9e9e2933a6c31e9bfe5bf44c38b078cc1631c94cdc5ca17b8",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:888f477cd86e247e096997b7141ba4e4dc96067ea142521b793dc07268275490",
    "size": 2076,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:888f477cd86e247e096997b7141ba4e4dc96067ea142521b793dc07268275490",
  "image.name": "ghcr.io/graphql-hive/server:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/server:console_994-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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/u9tib12z1szfy3yt1m0q8t2rn",
  "containerimage.config.digest": "sha256:14afd993f5555b7fec46b3c86ba11016a9f8352cc72e2e3c3b56ee76eff9e51a",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:52c67e5c0c16f31cd943ab21865d01600418f928a6357785ea323df0dd1896fa",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:52c67e5c0c16f31cd943ab21865d01600418f928a6357785ea323df0dd1896fa",
  "image.name": "ghcr.io/graphql-hive/storage:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/storage:console_994-arm64"
},
"stripe-billing": {
  "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 stripe billing service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/stripe-billing",
          "build-arg:PORT": "3010",
          "build-arg:RELEASE": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "build-arg:SERVICE_DIR_NAME": "@hive/stripe-billing",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/t3q9oifapf7wsukhc0pq3k5kd",
  "containerimage.config.digest": "sha256:e7000c9149854d646c9d804a5f3e6935b06b0f759cd9175fa40eaf75d961bb4b",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:61da472461916ebf6012074d6dc2ac11548501b33126564edce869d05d5969d7",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:61da472461916ebf6012074d6dc2ac11548501b33126564edce869d05d5969d7",
  "image.name": "ghcr.io/graphql-hive/stripe-billing:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/stripe-billing:console_994-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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/cl2t796dlu4i4omm6adef6raq",
  "containerimage.config.digest": "sha256:f8671d55db6ad7270a0e90572f46ece82563198a2d4d53640bf7f27b8f8de730",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:a14a8d6d489094d077812d45236644ea4d9da81cbba8bda626cdeffb256c7982",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:a14a8d6d489094d077812d45236644ea4d9da81cbba8bda626cdeffb256c7982",
  "image.name": "ghcr.io/graphql-hive/tokens:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/tokens:console_994-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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/tt7g10roheynmnbdi5spk20dq",
  "containerimage.config.digest": "sha256:30aa66781cce978287971b2e74db318b8eabe82b95eb7d3634ec1cc5ee63f8f0",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:48eff8bcf0d0fb095d27f5879297dba21644c6bb80fd5b89b04d689467ae0df2",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:48eff8bcf0d0fb095d27f5879297dba21644c6bb80fd5b89b04d689467ae0df2",
  "image.name": "ghcr.io/graphql-hive/usage:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/usage:console_994-arm64"
},
"usage-estimator": {
  "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 estimator service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage-estimator",
          "build-arg:PORT": "3008",
          "build-arg:RELEASE": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage-estimator",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/jr39o6110vfmd0mpruztqdzoh",
  "containerimage.config.digest": "sha256:e5dc1dd8dfe645ee1cf3249f14e85939cae30a6416aab8f19bc629f376f8d66b",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:889ab2802807bbfaef1c1b5f0a4172c714702ce51931dd4e6af1663decd6756e",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:889ab2802807bbfaef1c1b5f0a4172c714702ce51931dd4e6af1663decd6756e",
  "image.name": "ghcr.io/graphql-hive/usage-estimator:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/usage-estimator:console_994-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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/x4m5rtg5loioo4580zkztpa7o",
  "containerimage.config.digest": "sha256:eb9e161d1127b5c3888578059cc26f0aa30c0c81f7a845e23d176090cef259dd",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:37052072df60cb124efb9c1beb5dadcba010312a695e82a18476cfe300b3ecb3",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:37052072df60cb124efb9c1beb5dadcba010312a695e82a18476cfe300b3ecb3",
  "image.name": "ghcr.io/graphql-hive/usage-ingestor:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/usage-ingestor:console_994-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": "8a1c77f9b1566ad7b9c083096f037f8d7016ccab",
          "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": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:dockerfile": "v4dxzzj3i8m6vdnjmtxpxh1ox",
          "local-sessionid:shared": "v4dxzzj3i8m6vdnjmtxpxh1ox"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-bf5a90c8-9a8c-4791-8413-f17b284c4c49/builder-bf5a90c8-9a8c-4791-8413-f17b284c4c490/ybr52jgi8w35jjvpu3d3usgvz",
  "containerimage.config.digest": "sha256:3ebab1fa92bfdf6f33e74b8f2a0de0fa876e524829e763a1a4fb6ae3ea199c09",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:de8f2c4cef2b1fbd6ef4fc31694c700e749a0032117a9b89b619feb0d640742a",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:de8f2c4cef2b1fbd6ef4fc31694c700e749a0032117a9b89b619feb0d640742a",
  "image.name": "ghcr.io/graphql-hive/webhooks:8a1c77f9b1566ad7b9c083096f037f8d7016ccab-arm64,ghcr.io/graphql-hive/webhooks:console_994-arm64"
}
}

Copy link
Contributor

github-actions bot commented Feb 12, 2025

🚀 Snapshot Release (alpha)

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

Package Version Info
hive 5.2.0-alpha-20250218142533-8a1c77f9b1566ad7b9c083096f037f8d7016ccab npm ↗︎ unpkg ↗︎

@jasonkuhrt

This comment was marked as resolved.

@jasonkuhrt jasonkuhrt marked this pull request as ready for review February 13, 2025 01:18
@jasonkuhrt
Copy link
Member Author

Still need to add an E2E test but rest is generally ready for review.

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 (12)
packages/web/app/src/lib/hooks/use-local-storage.ts (2)

4-5: Good approach to support both string and versioned specs.
Allowing the key parameter to be either a string or a VersionedEntrySpec provides flexibility for future scaling while preserving backward compatibility. Consider documenting this change prominently to avoid confusion about passing arrays vs. single keys.


14-14: Clarify usage of the first versioned entry key.
Storing to versionedEntry[0].key effectively treats the earliest spec as canonical, but if additional spec entries exist, you may want a clearer rationale for always writing only to the first entry. If you plan to keep multiple versions, consider how you might handle writes across them.

packages/web/app/src/lib/versioned-entry.ts (2)

41-92: Check the removal logic for prior entries.
By default, previousEntriesPolicy is ignore, but if set to remove, previous entries are immediately removed (lines 75–77). In scenarios where the user wants to copy forward without deleting, a dedicated policy or approach may be beneficial. Currently, “ignore” might already solve copying-only use cases, but adding an explicit “copyOnly” policy could block unintentional removals.


105-115: Local and memory-based implementations are straightforward.
Both keyValueStoreLocalStorage and createKeyValueStoreMemory are well-structured. Keep in mind potential synchronization or concurrency issues (e.g., if localStorage is modified across tabs). For the memory store, mention lifespan or disposal strategy in docs if used for ephemeral testing.

Also applies to: 117-117, 119-129

packages/web/app/src/lib/hooks/use-local-storage-json.ts (3)

8-8: Similar pattern for versioned key logic.
Wrapping a string in an array for VersionedEntrySpec is a neat trick to unify the usage. Continually ensure the approach remains intuitive for future maintainers.


55-55: Storing JSON with the first spec key.
As with useLocalStorage, writing to versionedEntry[0].key is consistent, but double-check if multiple version keys must be updated or if the rest should remain.


75-75: Explicit union for key input is valuable.
type KeyInput = string | VersionedEntrySpec; clarifies permissible usage. Document typical usage patterns, so others know how to pass either a simple key string or a fully versioned spec.

Also applies to: 80-80

packages/web/app/src/lib/versioned-entry.spec.ts (2)

22-52: Enhance test descriptions for better clarity.

While the test cases are comprehensive, the descriptions could be more descriptive to better communicate the test scenarios.

Consider updating the test descriptions to be more explicit about what's being tested. For example:

-	// Returns null if spec key is missing in db
+	// Should return null when the specified key is not present in the database
 	{ spec: [{ key:a }],                   databaseBefore: {},      databaseAfter: {},      value: null },
 	{ spec: [{ key:a }],                   databaseBefore: {b},     databaseAfter: {b},     value: null },
-	// Returns value if spec key is present in db
+	// Should return the value when the specified key exists in the database
 	{ spec: [{ key:a }],                   databaseBefore: {a},     databaseAfter: {a},     value: a },

54-60: Add assertions for database state validation.

The test could benefit from more specific assertions about the database state.

Consider adding assertions to validate specific aspects of the database state:

 ({ databaseBefore, databaseAfter, spec, value, previousEntriesPolicy }) => {
   const readVersionedEntryMemory = readVersionedEntry(createKeyValueStoreMemory(databaseBefore));
   const valueActual = readVersionedEntryMemory({spec, previousEntriesPolicy})
-  expect(databaseBefore).toEqual(databaseAfter)
+  // Assert specific aspects of the database state
+  expect(Object.keys(databaseBefore).length).toEqual(Object.keys(databaseAfter).length)
+  expect(Object.keys(databaseAfter)).toContain(spec[0].key)
   expect(valueActual).toEqual(value)
 },
cypress/e2e/laboratory-preflight.cy.ts (1)

61-61: Address the TODO comment for target ID inclusion.

The TODO comment indicates that this test needs to be updated to include a target ID, which aligns with the new target-scoped environment variables feature.

Would you like me to help implement the test update to include the target ID? This would ensure the test properly validates the new scoping mechanism.

packages/web/app/src/lib/preflight/graphiql-plugin.tsx (1)

154-159: Remove redundant TODO comment.

The TODO comment appears to be obsolete as the implementation already includes the updated key.

-    // todo
-    // 'hive:laboratory:isPreflightEnabled',
     'hive:laboratory:isPreflightScriptEnabled',
.changeset/rich-terms-knock.md (1)

13-13: Add missing comma for better readability.

A comma is needed after "For an indefinite period of time" to improve readability.

-For an indefinite period of time we will support the following migration when you load Laboratory on any target.
+For an indefinite period of time, we will support the following migration when you load Laboratory on any target.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~13-~13: Possible missing comma found.
Context: ...Anything!) For an indefinite period of time we will support the following migration...

(AI_HYDRA_LEO_MISSING_COMMA)

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 73abccf and c166d97.

📒 Files selected for processing (7)
  • .changeset/rich-terms-knock.md (1 hunks)
  • cypress/e2e/laboratory-preflight.cy.ts (1 hunks)
  • packages/web/app/src/lib/hooks/use-local-storage-json.ts (3 hunks)
  • packages/web/app/src/lib/hooks/use-local-storage.ts (1 hunks)
  • packages/web/app/src/lib/preflight/graphiql-plugin.tsx (1 hunks)
  • packages/web/app/src/lib/versioned-entry.spec.ts (1 hunks)
  • packages/web/app/src/lib/versioned-entry.ts (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/rich-terms-knock.md
🪛 LanguageTool
.changeset/rich-terms-knock.md

[uncategorized] ~13-~13: Possible missing comma found.
Context: ...Anything!) For an indefinite period of time we will support the following migration...

(AI_HYDRA_LEO_MISSING_COMMA)

⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: typescript / typecheck
  • GitHub Check: storybook-preview / deployment
  • 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
🔇 Additional comments (12)
packages/web/app/src/lib/hooks/use-local-storage.ts (2)

2-2: No issues with the new import.
The import of readVersionedEntryLocalStorage and VersionedEntrySpec is properly aligned with the feature for versioned local storage.


8-8: Ensure consistent fallback if null is returned by the reader.
readVersionedEntryLocalStorage can return null, so verifying the fallback to defaultValue upon initialization is crucial. This appears to be handled properly in the useState initializer, but consider logging or tracing null returns for diagnostic clarity.

packages/web/app/src/lib/versioned-entry.ts (3)

5-9: Interface definition looks solid.
The KeyValueStore contract is simple and extensible. Consider adding async variants in the future if remote or asynchronous storage is required.


17-24: Versioned spec design is flexible for future migrations.
The tuple-based VersionedEntrySpec and the EntrySpec layout leave room for schema-based upgrades. Document the expected usage pattern to guide future contributors on adding advanced migrations.


94-100: Policies are concise and clear.
PreviousEntriesPolicy and the associated as const usage is a good pattern, making the policy keys less error-prone. If you anticipate more than two modes, consider enumerating them more extensively.

packages/web/app/src/lib/hooks/use-local-storage-json.ts (3)

4-4: Import aligns with versioned entry usage.
Importing readVersionedEntryLocalStorage and VersionedEntrySpec here follows the same pattern as in use-local-storage.ts. Consistency is good across hooks.


30-30: Consistent usage of versioned reading.
Using readVersionedEntryLocalStorage ensures the JSON-based hook also benefits from the same migration logic. This maintains parity between plain and JSON-storing hooks.


66-68: Good type enforcement strategy.
Separating the parameter signature by whether the schema has a default is a sophisticated approach. This ensures users supply a default when schema defaults are missing, preventing accidental undefined usage.

packages/web/app/src/lib/versioned-entry.spec.ts (1)

9-15: LGTM! Well-structured test case interface.

The TestCase interface is well-designed, clearly defining the expected state before and after operations, along with the necessary inputs and outputs.

packages/web/app/src/lib/preflight/graphiql-plugin.tsx (1)

160-166: LGTM! Well-implemented target-scoped environment variables.

The implementation correctly handles both target-specific and legacy environment variable storage, supporting the migration strategy outlined in the changeset.

.changeset/rich-terms-knock.md (2)

1-3: LGTM! Correct changeset metadata.

The changeset correctly specifies a minor version bump for the 'hive' package.


5-20: LGTM! Clear and comprehensive migration documentation.

The changeset provides excellent documentation of the changes and migration strategy:

  • Clear explanation of the previous behavior
  • Detailed description of the new target-scoped approach
  • Well-documented migration strategy with clear steps
🧰 Tools
🪛 LanguageTool

[uncategorized] ~13-~13: Possible missing comma found.
Context: ...Anything!) For an indefinite period of time we will support the following migration...

(AI_HYDRA_LEO_MISSING_COMMA)

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: 4

🧹 Nitpick comments (1)
packages/web/app/src/lib/hooks/use-local-storage.ts (1)

8-17: Consider memoizing the initial value computation.

The getInitialValue callback is recreated whenever versionedEntrySerialized or defaultValue changes, which triggers the effect and re-sets the value. While this is correct, we could optimize it further.

-  const getInitialValue = useCallback(() => {
+  const getInitialValue = useMemo(() => {
     const value = readVersionedEntryLocalStorage({ spec: versionedEntry });
     return value ?? defaultValue;
-  }, [versionedEntrySerialized, defaultValue]);
+  }, [versionedEntrySerialized, defaultValue]);

   const [value, setValue] = useState(getInitialValue());

   useEffect(() => {
-    setValue(getInitialValue());
+    setValue(getInitialValue);
   }, [getInitialValue]);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c166d97 and c7b974c.

📒 Files selected for processing (5)
  • cypress.config.ts (2 hunks)
  • cypress/e2e/laboratory-environment-variables.cy.ts (1 hunks)
  • integration-tests/testkit/seed.ts (1 hunks)
  • packages/web/app/src/lib/hooks/use-local-storage.ts (1 hunks)
  • packages/web/app/src/lib/preflight/graphiql-plugin.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/app/src/lib/preflight/graphiql-plugin.tsx
🧰 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/testkit/seed.ts
🪛 Biome (1.9.4)
cypress/e2e/laboratory-environment-variables.cy.ts

[error] 75-75: Don't focus the test.

The 'only' method is often used for debugging or during implementation. It should be removed before deploying to production.
Consider removing 'only' to ensure all tests are executed.
Unsafe fix: Remove focus from test.

(lint/suspicious/noFocusedTests)


[error] 19-26: Do not export from a test file.

(lint/suspicious/noExportsInTest)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: storybook-preview / deployment
  • 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 (4)
packages/web/app/src/lib/hooks/use-local-storage.ts (1)

4-6: LGTM! The key handling logic is well-implemented.

The implementation correctly handles both string and versioned keys, with proper type safety and key serialization.

cypress.config.ts (2)

26-26: LGTM! Smart retry configuration.

The retry configuration intelligently adapts based on the CI environment, which helps reduce test execution time in local development while maintaining reliability in CI.


36-40: LGTM! Well-structured target mapping.

The target mapping correctly computes paths for each environment, which is essential for the laboratory environment variables scoping feature.

integration-tests/testkit/seed.ts (1)

214-217: LGTM! Comprehensive target path computation.

The implementation correctly computes the path for each target by combining organization, project, and target slugs. This is essential for the laboratory environment variables scoping feature.

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

🔭 Outside diff range comments (1)
packages/web/app/src/lib/preflight/graphiql-plugin.tsx (1)

187-202: 🛠️ Refactor suggestion

Improve error handling for invalid environment variables JSON.

Currently, invalid JSON in environment variables is silently replaced with an empty object, which could lead to data loss.

Consider these improvements:

  1. Preserve the invalid JSON string.
  2. Show an error message to the user.
  3. Allow the user to fix the JSON manually.
-        () => ({}),
+        (error) => {
+          toast({
+            title: 'Invalid Environment Variables',
+            description: 'Please fix the JSON syntax in your environment variables.',
+            variant: 'destructive',
+          });
+          return resultEnvironmentVariablesDecoded;
+        }
🧹 Nitpick comments (3)
cypress/e2e/laboratory-environment-variables.cy.ts (1)

74-79: Add more test coverage for environment variables.

The current test suite covers basic scenarios. Consider adding tests for:

  1. Multiple targets with different environment variables
  2. Overriding global variables with target-specific ones
  3. Error handling for invalid JSON
  4. Migration scenarios

Would you like me to generate the additional test cases?

Also applies to: 81-87, 89-97

cypress/support/testkit.ts (1)

1-40: Enhance error handling in cookie persistence.

While the cookie persistence implementation is good, consider adding error handling for cases where cookies don't exist or have unexpected values.

 export function persistAuthenticationCookies() {
   const ctx = {
     cookies: [] as Cypress.Cookie[],
   };
 
   before(() => {
+    // Add timeout to prevent infinite wait
+    const timeout = { timeout: 10000 };
-    cy.getCookie('sRefreshToken').should('exist');
+    cy.getCookie('sRefreshToken', timeout)
+      .should('exist')
+      .then(cookie => {
+        if (!cookie?.value) {
+          throw new Error('Refresh token is empty');
+        }
+      });
     cy.visit('/');
     cy.wait(2000);
 
-    cy.getCookie('sAccessToken').should('exist');
-    cy.getCookie('sFrontToken').should('exist');
-    cy.getCookie('st-last-access-token-update').should('exist');
+    cy.getCookie('sAccessToken', timeout).should('exist');
+    cy.getCookie('sFrontToken', timeout).should('exist');
+    cy.getCookie('st-last-access-token-update', timeout).should('exist');
package.json (1)

80-80: Consider pinning the cypress-localstorage-commands version.

Using ^2.2.7 allows minor version updates which could introduce breaking changes in tests.

-    "cypress-localstorage-commands": "^2.2.7",
+    "cypress-localstorage-commands": "2.2.7",
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c7b974c and f4bdfe6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • cypress.config.ts (2 hunks)
  • cypress/e2e/laboratory-environment-variables.cy.ts (1 hunks)
  • cypress/e2e/laboratory-preflight.cy.ts (2 hunks)
  • cypress/support/e2e.ts (1 hunks)
  • cypress/support/testkit.ts (1 hunks)
  • package.json (1 hunks)
  • packages/web/app/src/lib/preflight/graphiql-plugin.tsx (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • cypress/support/e2e.ts
🧰 Additional context used
🪛 Biome (1.9.4)
cypress/e2e/laboratory-environment-variables.cy.ts

[error] 25-32: Do not export from a test file.

(lint/suspicious/noExportsInTest)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: static-analysis / analyze (typescript)
  • 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 (9)
cypress.config.ts (4)

4-4: LGTM! Good addition of local storage commands plugin.

The cypress-localstorage-commands plugin will help manage environment variables in local storage during tests.


27-27: Good conditional retry configuration.

Setting retries based on CI environment is a good practice as it helps balance between test reliability in CI and fast feedback in local development.


29-30: LGTM! Proper integration of local storage commands.

The plugin is correctly integrated into the Cypress configuration.


39-43: LGTM! Well-structured target data.

The targets object provides a clear and organized way to access different environment targets in tests.

cypress/e2e/laboratory-environment-variables.cy.ts (2)

25-32: Move storage key constant to a utility file.

The environmentVariablesStorageKey object should be moved to a utility file since it's being exported from a test file.

🧰 Tools
🪛 Biome (1.9.4)

[error] 25-32: Do not export from a test file.

(lint/suspicious/noExportsInTest)


34-37: LGTM! Well-structured test data.

The test data is clearly defined and includes both object and JSON string formats.

cypress/e2e/laboratory-preflight.cy.ts (2)

20-22: LGTM! Improved context object naming.

The rename from data to ctx better represents the object's purpose as a test context.


61-63: LGTM! Consistent usage of target slug.

The target slug is correctly accessed through the context object.

packages/web/app/src/lib/preflight/graphiql-plugin.tsx (1)

166-172: LGTM! Well-structured storage key implementation.

The array of storage keys is properly ordered with scoped key before global key, ensuring correct fallback behavior.

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: 2

♻️ Duplicate comments (1)
cypress/e2e/laboratory-environment-variables.cy.ts (1)

47-72: 🛠️ Refactor suggestion

Enhance test coverage.

The current test suite only covers basic scenarios. Consider adding tests for:

  1. Multiple targets with different environment variables
  2. Overriding global variables with target-specific ones
  3. Error handling for invalid JSON
  4. Migration scenarios

Would you like me to generate the additional test cases?

🧹 Nitpick comments (2)
cypress/e2e/laboratory-environment-variables.cy.ts (1)

8-11: Consider using typed test data.

The data object could benefit from TypeScript interfaces to ensure type safety and improve maintainability.

+interface TestData {
+  envars: Record<string, string>;
+  envarsJson: string;
+}
+
-const data = {
+const data: TestData = {
   envars: { foo: '123' },
   envarsJson: '{"foo":"123"}',
};
cypress/support/testkit.ts (1)

31-70: Optimize cookie persistence logic.

The cookie persistence logic has several areas for improvement:

  1. The 2-second wait is arbitrary and could be flaky
  2. Cookie operations could be batched
  3. Error handling could be improved
 export function persistAuthenticationCookies() {
   const ctx = {
     cookies: [] as Cypress.Cookie[],
   };
 
   before(() => {
     cy.getCookie('sRefreshToken').should('exist');
     cy.visit('/');
-    cy.wait(2000);
+    // Wait for the application to be ready
+    cy.get('[data-cy="app-ready"]').should('exist');
 
-    cy.getCookie('sAccessToken').should('exist');
-    cy.getCookie('sFrontToken').should('exist');
-    cy.getCookie('st-last-access-token-update').should('exist');
+    const requiredCookies = [
+      'sAccessToken',
+      'sFrontToken',
+      'sRefreshToken',
+      'st-last-access-token-update'
+    ];
 
-    cy.getCookie('sAccessToken').then(sAccessToken => {
-      ctx.cookies.push(sAccessToken);
-    });
-    cy.getCookie('sFrontToken').then(sFrontToken => {
-      ctx.cookies.push(sFrontToken);
-    });
-    cy.getCookie('sRefreshToken').then(sRefreshToken => {
-      ctx.cookies.push(sRefreshToken);
-    });
+    // Verify all required cookies exist
+    requiredCookies.forEach(cookieName => {
+      cy.getCookie(cookieName).should('exist');
+    });
 
-    cy.getCookie('st-last-access-token-update').then(stLastAccessTokenUpdate => {
-      ctx.cookies.push(stLastAccessTokenUpdate);
+    // Batch collect all cookies
+    cy.wrap(requiredCookies).each(cookieName => {
+      cy.getCookie(cookieName).then(cookie => {
+        if (cookie) {
+          ctx.cookies.push(cookie);
+        } else {
+          throw new Error(`Required cookie ${cookieName} not found`);
+        }
+      });
     });
 
-    cy.clearCookie('st-last-access-token-update');
-    cy.clearCookie('sRefreshToken');
-    cy.clearCookie('sAccessToken');
-    cy.clearCookie('sFrontToken');
+    // Batch clear all cookies
+    requiredCookies.forEach(cookieName => {
+      cy.clearCookie(cookieName);
+    });
   });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f4bdfe6 and b7729cc.

📒 Files selected for processing (2)
  • cypress/e2e/laboratory-environment-variables.cy.ts (1 hunks)
  • cypress/support/testkit.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: storybook-preview / deployment
  • 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)
cypress/e2e/laboratory-environment-variables.cy.ts (1)

30-33: Uncomment and utilize production target test.

The commented-out production target test function suggests incomplete test coverage. Consider implementing tests for both development and production targets to ensure proper isolation.

Would you like me to generate additional test cases that cover both development and production targets?

@jasonkuhrt
Copy link
Member Author

Struggled today with Cypress but ultimately got tests running how I wanted:

  • assert local storage state
  • share same session and resources for whole test suite

Will think through the rest of test cases next week. E.g. on edit in modal only scoped should be written to.

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

♻️ Duplicate comments (1)
cypress/support/testkit.ts (1)

5-12: ⚠️ Potential issue

Address TODOs in storage key implementation.

The storage key implementation has two unaddressed TODOs:

  1. Importing from core utility lib
  2. Handling the silent global case with optional target ID

Apply this diff to address the TODOs:

-// todo: instead of copying this, import it from core utility lib.
-export const environmentVariablesStorageKey = {
-  // todo: optional target effectively gives this the possibility of being silently global
-  // which feels subtle and thus likely to introduce hard to trace defects. Should we abort instead?
-  scoped: (targetId?: string) =>
-    `hive/targetId:${targetId ?? '__null__'}/laboratory/environment-variables`,
-  global: 'hive:laboratory:environment',
-};
+import { environmentVariablesStorageKey as coreStorageKey } from '../../core/utility';
+
+export const environmentVariablesStorageKey = {
+  scoped: (targetId: string) => {
+    if (!targetId) {
+      throw new Error('Target ID is required for scoped environment variables');
+    }
+    return coreStorageKey.scoped(targetId);
+  },
+  global: coreStorageKey.global,
+};
🧹 Nitpick comments (7)
cypress/e2e/laboratory-environment-variables.cy.ts (3)

8-11: Enhance test data with more comprehensive cases.

The test data is currently minimal. Consider adding more test cases to cover different scenarios:

  • Complex environment variables with nested objects
  • Multiple variables
  • Special characters
  • Empty objects
 const data = {
-  envars: { foo: '123' },
-  envarsJson: '{"foo":"123"}',
+  envars: {
+    simple: { foo: '123' },
+    complex: { nested: { key: 'value' }, array: [1, 2, 3] },
+    special: { 'key-with-dash': 'value' },
+    empty: {}
+  },
+  envarsJson: {
+    simple: '{"foo":"123"}',
+    complex: '{"nested":{"key":"value"},"array":[1,2,3]}',
+    special: '{"key-with-dash":"value"}',
+    empty: '{}'
+  }
 };

32-33: Remove or implement commented code.

The commented production target navigation function suggests a potential need for testing with production targets. Either implement these tests or remove the commented code.

 const visitTargetDevelopment = () => cy.visit(`${ctx.targetDevelopment.path}/laboratory`);
-// const visitTargetProduction = () => cy.visit(`${ctx.targetProduction.path}/laboratory`);

37-41: Improve code formatting to avoid prettier-ignore.

The code uses prettier-ignore comments to handle long lines. Consider breaking down the lines for better readability without disabling formatting.

-const storageGlobalGet = () => cy.getLocalStorage(environmentVariablesStorageKey.global);
-const storageGlobalSet = (value: string) => cy.setLocalStorage(environmentVariablesStorageKey.global, value); // prettier-ignore
-
-const storageTargetDevelopmentGet = () => cy.getLocalStorage(environmentVariablesStorageKey.scoped(ctx.targetDevelopment.id)); // prettier-ignore
-const storageTargetDevelopmentSet = (value: string) => cy.setLocalStorage(environmentVariablesStorageKey.scoped(ctx.targetDevelopment.id), value); // prettier-ignore
+const storageGlobalGet = () => 
+  cy.getLocalStorage(environmentVariablesStorageKey.global);
+
+const storageGlobalSet = (value: string) => 
+  cy.setLocalStorage(environmentVariablesStorageKey.global, value);
+
+const storageTargetDevelopmentGet = () => 
+  cy.getLocalStorage(
+    environmentVariablesStorageKey.scoped(ctx.targetDevelopment.id)
+  );
+
+const storageTargetDevelopmentSet = (value: string) => 
+  cy.setLocalStorage(
+    environmentVariablesStorageKey.scoped(ctx.targetDevelopment.id),
+    value
+  );
cypress/support/testkit.ts (2)

14-31: Address TODO about scoping selectors.

The selectors object has a TODO about scoping to laboratory. This should be addressed to improve maintainability.

-// todo: Once other PRs are merged these selectors will be scoped to a place for laboratory.
-export const selectors = {
+export const laboratorySelectors = {
   editorEnvironmentVariables: '[data-cy="preflight-editor-mini"]',
   buttonGraphiQLPreflight: '[aria-label*="Preflight Script"]',
   buttonModalCy: 'preflight-modal-button',
   // ... rest of the selectors
 };

Then update the imports in test files:

-import { selectors } from '../support/testkit';
+import { laboratorySelectors as selectors } from '../support/testkit';

33-72: Improve error handling in cookie persistence.

The cookie persistence implementation could be more robust with better error handling and logging.

 export function persistAuthenticationCookies() {
   const ctx = {
     cookies: [] as Cypress.Cookie[],
+    errors: [] as Error[],
   };
 
   before(() => {
-    cy.getCookie('sRefreshToken').should('exist');
+    cy.getCookie('sRefreshToken')
+      .should('exist')
+      .catch(error => {
+        ctx.errors.push(new Error(`Refresh token not found: ${error.message}`));
+      });
     cy.visit('/');
     cy.wait(2000);
 
-    cy.getCookie('sAccessToken').should('exist');
-    cy.getCookie('sFrontToken').should('exist');
-    cy.getCookie('st-last-access-token-update').should('exist');
+    const requiredCookies = [
+      'sAccessToken',
+      'sFrontToken',
+      'st-last-access-token-update'
+    ];
+
+    requiredCookies.forEach(cookieName => {
+      cy.getCookie(cookieName)
+        .should('exist')
+        .catch(error => {
+          ctx.errors.push(new Error(`${cookieName} not found: ${error.message}`));
+        });
+    });
+
+    if (ctx.errors.length > 0) {
+      throw new Error(
+        `Authentication setup failed:\n${ctx.errors.map(e => e.message).join('\n')}`
+      );
+    }
     // ... rest of the implementation
   });
 }
integration-tests/testkit/seed.ts (2)

65-69: Add JSDoc documentation to Target interface.

The Target interface would benefit from documentation explaining its purpose and properties.

+/**
+ * Represents a target in the system.
+ */
 export interface Target {
+  /** Unique identifier for the target */
   id: string
+  /** Full path to access the target in the UI */
   path: string
+  /** URL-friendly identifier for the target */
   slug: string
 }

220-224: Make path construction more maintainable.

The path construction is hardcoded in the mapping function. Consider extracting it to a utility function for better maintainability.

+const constructTargetPath = (
+  organizationSlug: string,
+  projectSlug: string,
+  targetSlug: string
+) => `/${organizationSlug}/${projectSlug}/${targetSlug}`;
+
 const targets = projectResult.createProject.ok!.createdTargets.map(_ => ({
   ..._,
-  path: `/${organization.slug}/${project.slug}/${_.slug}`,
+  path: constructTargetPath(organization.slug, project.slug, _.slug),
 }));
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b7729cc and b92f941.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • cypress/e2e/laboratory-environment-variables.cy.ts (1 hunks)
  • cypress/support/testkit.ts (1 hunks)
  • integration-tests/testkit/seed.ts (2 hunks)
  • package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 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/testkit/seed.ts
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • 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: storybook-preview / deployment
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: test / db-migrations
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts

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

♻️ Duplicate comments (2)
cypress/e2e/laboratory-environment-variables.cy.ts (1)

105-123: 🛠️ Refactor suggestion

Move the Monaco editor utility to a shared file.

The TODO comment and static analysis hint both suggest moving this utility function out of the test file.

Create a new file cypress/support/monaco-editor.ts and move the implementation there:

// cypress/support/monaco-editor.ts
export function setMonacoEditorContents(editorCyName: string, text: string) {
  // Implementation...
}

Then update the import in the test file:

+import { setMonacoEditorContents } from '../support/monaco-editor';
-// todo: in another PR this utility is factored out into a shared file
-export function setMonacoEditorContents(editorCyName: string, text: string) {
-  // Implementation...
-}
🧰 Tools
🪛 Biome (1.9.4)

[error] 106-123: Do not export from a test file.

(lint/suspicious/noExportsInTest)

cypress/support/testkit.ts (1)

5-12: ⚠️ Potential issue

Address TODOs and improve error handling in storage key utility.

The storage key utility has two TODOs that should be addressed:

  1. Import from core utility lib instead of copying
  2. Handle the silent global case

Apply this diff to improve error handling:

-// todo: instead of copying this, import it from core utility lib.
 export const environmentVariablesStorageKey = {
-  // todo: optional target effectively gives this the possibility of being silently global
-  // which feels subtle and thus likely to introduce hard to trace defects. Should we abort instead?
-  scoped: (targetId?: string) =>
-    `hive/targetId:${targetId ?? '__null__'}/laboratory/environment-variables`,
+  scoped: (targetId: string) => {
+    if (!targetId) {
+      throw new Error('Target ID is required for scoped environment variables');
+    }
+    return `hive/targetId:${targetId}/laboratory/environment-variables`;
+  },
   global: 'hive:laboratory:environment',
 };
🧹 Nitpick comments (2)
cypress/e2e/laboratory-environment-variables.cy.ts (1)

8-13: Consider using TypeScript's as const assertion for test data.

The test data object could benefit from being more strictly typed to catch potential typos and ensure data consistency.

-const data = {
+const data = {
   globalEnvars: { foo: '123' },
   globalEnvarsJson: '{"foo":"123"}',
   scopedEnvars: { bar: '456' },
   targetEnvarsJson: '{"bar":"456"}',
-};
+} as const;
cypress/support/testkit.ts (1)

33-72: Improve cookie persistence implementation.

The cookie persistence implementation has a few potential issues:

  1. Hard-coded wait time
  2. Redundant cookie existence checks
  3. Manual cookie management

Consider these improvements:

 export function persistAuthenticationCookies() {
   const ctx = {
     cookies: [] as Cypress.Cookie[],
   };
 
   before(() => {
-    cy.getCookie('sRefreshToken').should('exist');
     cy.visit('/');
-    cy.wait(2000);
+    // Wait for specific condition instead of arbitrary time
+    cy.get('[data-cy="app-loaded"]').should('exist');
 
-    cy.getCookie('sAccessToken').should('exist');
-    cy.getCookie('sFrontToken').should('exist');
-    cy.getCookie('st-last-access-token-update').should('exist');
+    const requiredCookies = [
+      'sAccessToken',
+      'sFrontToken',
+      'sRefreshToken',
+      'st-last-access-token-update'
+    ];
 
-    cy.getCookie('sAccessToken').then(sAccessToken => {
-      ctx.cookies.push(sAccessToken);
-    });
-    cy.getCookie('sFrontToken').then(sFrontToken => {
-      ctx.cookies.push(sFrontToken);
-    });
-    cy.getCookie('sRefreshToken').then(sRefreshToken => {
-      ctx.cookies.push(sRefreshToken);
-    });
-    cy.getCookie('st-last-access-token-update').then(stLastAccessTokenUpdate => {
-      ctx.cookies.push(stLastAccessTokenUpdate);
+    // Verify all required cookies exist
+    requiredCookies.forEach(cookieName => {
+      cy.getCookie(cookieName).should('exist');
     });
 
-    cy.clearCookie('st-last-access-token-update');
-    cy.clearCookie('sRefreshToken');
-    cy.clearCookie('sAccessToken');
-    cy.clearCookie('sFrontToken');
+    // Store all cookies at once
+    cy.getAllCookies().then(cookies => {
+      ctx.cookies = cookies.filter(cookie => 
+        requiredCookies.includes(cookie.name)
+      );
+    });
+
+    // Clear all cookies at once
+    cy.clearCookies();
   });
 
   beforeEach(() => {
     ctx.cookies.forEach(cookie => {
       cy.setCookie(cookie.name, cookie.value, cookie);
     });
   });
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b92f941 and 84b7d52.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • cypress/e2e/laboratory-environment-variables.cy.ts (1 hunks)
  • cypress/e2e/laboratory-preflight.cy.ts (2 hunks)
  • cypress/support/testkit.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cypress/e2e/laboratory-preflight.cy.ts
🧰 Additional context used
🪛 Biome (1.9.4)
cypress/e2e/laboratory-environment-variables.cy.ts

[error] 106-123: Do not export from a test file.

(lint/suspicious/noExportsInTest)

⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: Mend Security Check
🔇 Additional comments (1)
cypress/e2e/laboratory-environment-variables.cy.ts (1)

47-49: Uncomment and utilize the production target storage functions.

The commented-out functions for production target storage suggest incomplete test coverage for production target scenarios.

Consider adding test cases that verify environment variable behavior across different targets:

it('maintains separate environment variables for different targets', () => {
  const devVars = { dev: 'value' };
  const prodVars = { prod: 'value' };
  storageTargetDevelopmentSet(JSON.stringify(devVars));
  storageTargetProductionSet(JSON.stringify(prodVars));
  
  visitTargetDevelopment();
  openPreflightTab();
  cy.contains(JSON.stringify(devVars));
  
  visitTargetProduction();
  openPreflightTab();
  cy.contains(JSON.stringify(prodVars));
});

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/web/app/src/lib/hooks/use-local-storage.ts (1)

16-25: Consider memoizing versionedEntry to optimize performance.

The getInitialValue callback depends on versionedEntrySerialized, which is recalculated on every render. Consider memoizing versionedEntry and its serialized form to prevent unnecessary callback recreations.

+  const versionedEntry = useMemo(
+    () => (typeof key === 'string' ? [{ key }] : key) as VersionedEntrySpec,
+    [key],
+  );
+  const versionedEntrySerialized = useMemo(
+    () => serializeVersionedEntrySpec(versionedEntry),
+    [versionedEntry],
+  );
-  const versionedEntry: VersionedEntrySpec = typeof key === 'string' ? [{ key }] : key;
-  const versionedEntrySerialized = serializeVersionedEntrySpec(versionedEntry);
packages/web/app/src/lib/versioned-entry.ts (2)

22-28: Consider adding JSDoc comments for future schema migration.

The EntrySpec interface has TODO comments about future schema migration. Consider adding JSDoc comments to better document the planned features.

 interface EntrySpec {
   key: string;
-  // todo once we have use-case
-  // schema:
-  // fromPrevious:
+  /** @todo Add schema validation for entry values */
+  // schema?: Schema;
+  /** @todo Add migration function for previous versions */
+  // fromPrevious?: (value: string) => string;
 }

124-134: Consider adding error handling for memory store operations.

The memory store implementation might benefit from error handling for edge cases (e.g., invalid keys or values).

 export const createKeyValueStoreMemory = (database: KeyValueStoreDatabase): KeyValueStore => ({
   get(key) {
+    if (typeof key !== 'string') {
+      throw new Error('Invalid key type');
+    }
     return database[key] ?? null;
   },
   set(key, value) {
+    if (typeof key !== 'string' || typeof value !== 'string') {
+      throw new Error('Invalid key or value type');
+    }
     database[key] = value;
   },
   remove(key) {
+    if (typeof key !== 'string') {
+      throw new Error('Invalid key type');
+    }
     delete database[key];
   },
 });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 84b7d52 and 895d220.

📒 Files selected for processing (2)
  • packages/web/app/src/lib/hooks/use-local-storage.ts (1 hunks)
  • packages/web/app/src/lib/versioned-entry.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
packages/web/app/src/lib/hooks/use-local-storage.ts (1)
Learnt from: jasonkuhrt
PR: graphql-hive/console#6500
File: packages/web/app/src/lib/hooks/use-local-storage.ts:19-25
Timestamp: 2025-02-14T19:38:19.025Z
Learning: In React's useCallback, only include dependencies that are directly used within the callback's implementation. Depending on another callback (that has its own dependencies) when its implementation isn't used can lead to unnecessary callback recreations.
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: storybook-preview / deployment
  • 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
🔇 Additional comments (3)
packages/web/app/src/lib/hooks/use-local-storage.ts (2)

9-15: LGTM! Clean implementation of versioned entry handling.

The conversion from string key to VersionedEntrySpec and the serialization logic is well-implemented.


27-33: LGTM! Correct dependency in the setter callback.

The set callback now correctly depends on versionedEntryLatestSerialized instead of getInitialValue, aligning with the learning from previous reviews about including only directly used dependencies.

packages/web/app/src/lib/versioned-entry.ts (1)

5-11: LGTM! Well-defined interface for key-value storage.

The KeyValueStore interface provides a clean abstraction for storage operations.

Comment on lines +76 to +83
for (const { entry, index } of spec.map((entry, index) => ({ entry, index }))) {
const value = keyValueStore.get(entry.key);
searchResults.push({ entry, value, index });
// Note: Once we have schemas, we should not remove here, wait until _after_ successful migration
if (index > 0 && previousEntriesPolicy === PreviousEntriesPolicy.remove) {
keyValueStore.remove(entry.key);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider deferring removal until after successful migration.

The code removes previous entries before potential schema migration, as noted in the comment. This could lead to data loss if migration fails.


🏁 Script executed:

#!/bin/bash
# Search for any schema migration implementations or tests
rg -g '!node_modules' -A 5 'schema.*migration|migration.*schema'

Length of output: 14789


Action: Defer Removal Until Post-Migration Success

The current implementation in packages/web/app/src/lib/versioned-entry.ts (lines 76–83) immediately calls keyValueStore.remove(entry.key) when previousEntriesPolicy is remove. Although there is active migration logic (e.g., in packages/migrations/src/actions/2024.07.23T09.36.00.schema-cleanup-tracker.ts), prematurely removing entries may lead to data loss if the accompanying schema migration fails.

Recommendations:

  • Remove the immediate key removal: Defer calling keyValueStore.remove(entry.key) until after a successful migration.
  • Integrate into the migration pipeline: Consider moving this removal step into a post-migration hook to ensure the operation only occurs after migration success.

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.

2 participants