Repository: liam-hq/liam
Pull request #941: ✨ Link schemas in the repository and continuously update the ERD
Author: login: NoritakaIkeda
Labels: name: Review effort 3/5
Created: 2025-03-19T10:32:29Z | Merged: 2025-03-21T11:51:17Z

## Issue

- resolve:

## Why is this change needed?
<!-- Please explain briefly why this change is necessary -->

https://github.com/user-attachments/assets/90bd9565-ba91-4452-8269-c79493e64d6c



## What would you like reviewers to focus on?
<!-- What specific aspects are you requesting review for? -->

## Testing Verification
<!-- Please describe how you verified these changes in your local environment using text/images/video -->

## What was done
<!-- This section will be filled by PR-Agent when the Pull Request is opened -->

### 🤖 Generated by PR Agent at 4f12f891d27d496dc91c714f572aefc0e0bff54e

- Introduced ERD (Entity-Relationship Diagram) rendering for GitHub repositories.
  - Added `ERDViewer` component for rendering database structures.
  - Integrated GitHub API to fetch repository and file content.
- Refactored GitHub API utilities for better caching and error handling.
  - Replaced `getFileContent` with a cached version.
  - Added `getRepository` utility for repository metadata retrieval.
- Enhanced migration detail page with ERD links.
  - Dynamically generated links to ERD diagrams for matched schema files.
  - Updated styles and layout for better user experience.
- Fixed metadata generation errors by avoiding `notFound` calls.


## Detailed Changes
<!-- This section will be filled by PR-Agent when the Pull Request is opened -->

<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
      <summary><strong>api.server.ts</strong><dd><code>Refactor GitHub API utilities with caching</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

frontend/apps/app/libs/github/api.server.ts

<li>Added <code>getRepository</code> and cached <code>getFileContent</code> functions.<br> <li> Refactored <code>getFileContent</code> for better error handling and caching.<br> <li> Removed the old <code>getFileContent</code> implementation.


</details>


  </td>
  <td><a href="https://github.com/liam-hq/liam/pull/941/files#diff-43b0383ada9a3fb1a74039da72aaef0423ffaa06576e487c40a95c3405da193a">+53/-32</a>&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
      <summary><strong>processSavePullRequest.ts</strong><dd><code>Update `processSavePullRequest` to use cached API</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

frontend/apps/app/src/functions/processSavePullRequest.ts

<li>Updated <code>getFileContent</code> usage to use the new cached version.<br> <li> Adjusted parameters for <code>getFileContent</code> calls.


</details>


  </td>
  <td><a href="https://github.com/liam-hq/liam/pull/941/files#diff-cff950b9d59239ee4634fa2d3f3bcbd37161c360ac56bc4fb431fca1659e2e5c">+3/-5</a>&nbsp; &nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
      <summary><strong>MigrationDetailPage.module.css</strong><dd><code>Add styles for ERD links</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.module.css

<li>Added styles for ERD link elements.<br> <li> Enhanced hover effects for ERD links.


</details>


  </td>
  <td><a href="https://github.com/liam-hq/liam/pull/941/files#diff-32c8687f2ee5ce5153d7d20623df891fa65d5ed14e4b185088a72280707601be">+12/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
      <summary><strong>erdViewer.tsx</strong><dd><code>Add `ERDViewer` component for database visualization</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/erdViewer.tsx

<li>Added <code>ERDViewer</code> component for rendering database structures.<br> <li> Integrated <code>ERDRenderer</code> and cookie consent functionality.


</details>


  </td>
  <td><a href="https://github.com/liam-hq/liam/pull/941/files#diff-23cef07a550a8b8ac59f47862f91d209cca12f20fed7bd14ffa566f850051d64">+61/-0</a>&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
      <summary><strong>page.tsx</strong><dd><code>Implement ERD page with GitHub integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/page.tsx

<li>Implemented ERD page for rendering database structures.<br> <li> Integrated GitHub API for fetching repository and file content.<br> <li> Added error handling for missing files and unsupported formats.


</details>


  </td>
  <td><a href="https://github.com/liam-hq/liam/pull/941/files#diff-ff0af273206f1815552501f125df50ae38ffc5ce993e227a39e740694fea8b99">+216/-0</a>&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
      <summary><strong>MigrationDetailPage.tsx</strong><dd><code>Add ERD links to migration detail page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.tsx

<li>Added ERD links to migration detail page.<br> <li> Integrated GitHub API for pull request details and file matching.<br> <li> Enhanced migration content fetching with schema file patterns.


</details>


  </td>
  <td><a href="https://github.com/liam-hq/liam/pull/941/files#diff-011aa266ad74dd462ae2261a203611dc29967eab240dc4131cc80042c6eb5f70">+58/-1</a>&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

## Additional Notes
<!-- Any additional information for reviewers -->

___

> <details> <summary>  Need help?</summary><li>Type <code>/help how to ...</code> in the comments thread for any questions about Qodo Merge usage.</li><li>Check out the <a href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a> for more information.</li></details>


Diff:
diff --git a/frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/erdViewer.tsx b/frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/erdViewer.tsx
new file mode 100644
index 0000000000..8229481311
--- /dev/null
+++ b/frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/erdViewer.tsx
@@ -0,0 +1,61 @@
+'use client'
+
+import { CookieConsent } from '@/components/CookieConsent'
+import type { DBStructure, ProcessError } from '@liam-hq/db-structure'
+import {
+  ERDRenderer,
+  VersionProvider,
+  initDBStructureStore,
+  versionSchema,
+} from '@liam-hq/erd-core'
+import { useEffect, useState } from 'react'
+import * as v from 'valibot'
+
+type ErrorObject = {
+  name: string
+  message: string
+  instruction?: string
+}
+
+type ERDViewerProps = {
+  dbStructure: DBStructure
+  errorObjects: ErrorObject[]
+  defaultSidebarOpen: boolean
+  defaultPanelSizes?: number[]
+}
+
+export default function ERDViewer({
+  dbStructure,
+  errorObjects,
+  defaultSidebarOpen,
+  defaultPanelSizes = [20, 80],
+}: ERDViewerProps) {
+  const [isShowCookieConsent, setShowCookieConsent] = useState(false)
+
+  useEffect(() => {
+    initDBStructureStore(dbStructure)
+    setShowCookieConsent(window === window.parent)
+  }, [dbStructure])
+
+  const versionData = {
+    version: '0.1.0', // NOTE: no maintained version for ERD Web
+    gitHash: process.env.NEXT_PUBLIC_GIT_HASH,
+    envName: process.env.NEXT_PUBLIC_ENV_NAME,
+    date: process.env.NEXT_PUBLIC_RELEASE_DATE,
+    displayedOn: 'web',
+  }
+  const version = v.parse(versionSchema, versionData)
+
+  return (
+    <div style={{ height: '100dvh' }}>
+      <VersionProvider version={version}>
+        <ERDRenderer
+          defaultSidebarOpen={defaultSidebarOpen}
+          defaultPanelSizes={defaultPanelSizes}
+          errorObjects={errorObjects}
+        />
+      </VersionProvider>
+      {isShowCookieConsent && <CookieConsent />}
+    </div>
+  )
+}
diff --git a/frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/page.tsx b/frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/page.tsx
new file mode 100644
index 0000000000..05ee916ced
--- /dev/null
+++ b/frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/page.tsx
@@ -0,0 +1,161 @@
+import path from 'node:path'
+import type { PageProps } from '@/app/types'
+import { getFileContent, getRepository } from '@/libs/github/api.server'
+import { prisma } from '@liam-hq/db'
+import {
+  type SupportedFormat,
+  detectFormat,
+  parse,
+  setPrismWasmUrl,
+  supportedFormatSchema,
+} from '@liam-hq/db-structure/parser'
+import * as Sentry from '@sentry/nextjs'
+import type { Metadata } from 'next'
+import { cookies } from 'next/headers'
+import { notFound } from 'next/navigation'
+import * as v from 'valibot'
+import ERDViewer from './erdViewer'
+
+const paramsSchema = v.object({
+  projectId: v.string(),
+  branchOrCommit: v.string(),
+  slug: v.array(v.string()),
+})
+
+const searchParamsSchema = v.object({
+  format: v.optional(supportedFormatSchema),
+})
+
+export default async function Page({
+  params,
+  searchParams: _searchParams,
+}: PageProps) {
+  const parsedParams = v.safeParse(paramsSchema, await params)
+  if (!parsedParams.success) throw notFound()
+
+  const { projectId, branchOrCommit, slug } = parsedParams.output
+  const filePath = slug.join('/')
+
+  const blankDbStructure = { tables: {}, relationships: {} }
+
+  try {
+    const project = await prisma.project.findUnique({
+      where: { id: Number(projectId) },
+      include: {
+        repositoryMappings: {
+          include: {
+            repository: {
+              select: { owner: true, name: true, installationId: true },
+            },
+          },
+        },
+      },
+    })
+
+    const repository = project?.repositoryMappings[0]?.repository
+    if (!repository?.installationId || !repository.owner || !repository.name) {
+      throw new Error('Repository information not found')
+    }
+
+    const repositoryFullName = `${repository.owner}/${repository.name}`
+
+    const content = await getFileContent(
+      repositoryFullName,
+      filePath,
+      branchOrCommit,
+      Number(repository.installationId),
+    )
+
+    if (!content) {
+      return (
+        <ERDViewer
+          dbStructure={blankDbStructure}
+          defaultSidebarOpen={false}
+          errorObjects={[
+            {
+              name: 'FileNotFound',
+              message:
+                'The specified file could not be found in the repository.',
+              instruction:
+                'Please check the file path and branch/commit reference.',
+            },
+          ]}
+        />
+      )
+    }
+
+    setPrismWasmUrl(path.resolve(process.cwd(), 'prism.wasm'))
+
+    let format: SupportedFormat | undefined
+    const searchParams = await _searchParams
+
+    if (v.is(searchParamsSchema, searchParams)) {
+      format = searchParams.format
+    }
+    if (!format) {
+      format = detectFormat(filePath)
+    }
+
+    if (!format) {
+      return (
+        <ERDViewer
+          dbStructure={blankDbStructure}
+          defaultSidebarOpen={false}
+          errorObjects={[
+            {
+              name: 'FormatError',
+              message: 'Could not detect the file format.',
+              instruction:
+                'Please specify the format in the URL query parameter `format`',
+            },
+          ]}
+        />
+      )
+    }
+
+    const { value: dbStructure, errors } = await parse(content, format)
+    for (const error of errors) {
+      Sentry.captureException(error)
+    }
+
+    const cookieStore = await cookies()
+    const defaultSidebarOpen =
+      cookieStore.get('sidebar:state')?.value === 'true'
+    const layoutCookie = cookieStore.get('panels:layout')
+    const defaultPanelSizes = (() => {
+      if (!layoutCookie) return [20, 80]
+      try {
+        const sizes = JSON.parse(layoutCookie.value)
+        if (Array.isArray(sizes) && sizes.length >= 2) return sizes
+      } catch {}
+      return [20, 80]
+    })()
+
+    return (
+      <ERDViewer
+        dbStructure={dbStructure}
+        defaultSidebarOpen={defaultSidebarOpen}
+        defaultPanelSizes={defaultPanelSizes}
+        errorObjects={errors.map((error) => ({
+          name: error.name,
+          message: error.message,
+        }))}
+      />
+    )
+  } catch (_error) {
+    return (
+      <ERDViewer
+        dbStructure={blankDbStructure}
+        defaultSidebarOpen={false}
+        errorObjects={[
+          {
+            name: 'GitHubError',
+            message: 'Failed to fetch file content from GitHub',
+            instruction:
+              'Please check your repository permissions and try again.',
+          },
+        ]}
+      />
+    )
+  }
+}
diff --git a/frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.module.css b/frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.module.css
index 63b9b56efa..c261ea46ae 100644
--- a/frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.module.css
+++ b/frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.module.css
@@ -62,6 +62,18 @@
   letter-spacing: 0.0375rem;
 }
 
+.erdLink {
+  display: inline-block;
+  margin-top: 1rem;
+  color: var(--color-primary);
+  text-decoration: none;
+  font-weight: 500;
+}
+
+.erdLink:hover {
+  text-decoration: underline;
+}
+
 .reviewContent {
   margin-top: 1rem;
   background-color: var(--bg-secondary, #fff);
diff --git a/frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.tsx b/frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.tsx
index 65a5d443a9..ba1fb8d3b3 100644
--- a/frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.tsx
+++ b/frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.tsx
@@ -1,4 +1,9 @@
+import {
+  getPullRequestDetails,
+  getPullRequestFiles,
+} from '@/libs/github/api.server'
 import { prisma } from '@liam-hq/db'
+import { minimatch } from 'minimatch'
 import Link from 'next/link'
 import { notFound } from 'next/navigation'
 import type { FC } from 'react'
@@ -21,6 +26,13 @@ async function getMigrationContents(migrationId: string) {
         select: {
           id: true,
           pullNumber: true,
+          repository: {
+            select: {
+              installationId: true,
+              name: true,
+              owner: true,
+            },
+          },
         },
       },
     },
@@ -31,6 +43,7 @@ async function getMigrationContents(migrationId: string) {
   }
 
   const pullRequest = migration.pullRequest
+  const { repository } = pullRequest
 
   const overallReview = await prisma.overallReview.findFirst({
     where: {
@@ -42,14 +55,46 @@ async function getMigrationContents(migrationId: string) {
     return notFound()
   }
 
+  const prDetails = await getPullRequestDetails(
+    Number(repository.installationId),
+    repository.owner,
+    repository.name,
+    Number(pullRequest.pullNumber),
+  )
+
+  const files = await getPullRequestFiles(
+    Number(repository.installationId),
+    repository.owner,
+    repository.name,
+    Number(pullRequest.pullNumber),
+  )
+
+  const patterns = await prisma.watchSchemaFilePattern.findMany({
+    where: { projectId: Number(overallReview.projectId) },
+    select: { pattern: true },
+  })
+
+  const matchedFiles = files
+    .map((file) => file.filename)
+    .filter((filename) =>
+      patterns.some((pattern) => minimatch(filename, pattern.pattern)),
+    )
+
+  const erdLinks = matchedFiles.map((filename) => ({
+    path: `/app/projects/${overallReview.projectId}/erd/${prDetails.head.ref}/${filename}`,
+    filename,
+  }))
+
   return {
     migration,
     overallReview,
+    erdLinks,
   }
 }
 
 export const MigrationDetailPage: FC<Props> = async ({ migrationId }) => {
-  const { migration, overallReview } = await getMigrationContents(migrationId)
+  const { migration, overallReview, erdLinks } =
+    await getMigrationContents(migrationId)
 
   const projectId = overallReview.projectId
 
@@ -59,11 +104,7 @@ export const MigrationDetailPage: FC<Props> = async ({ migrationId }) => {
 
   return (
     <main className={styles.wrapper}>
-      <Link
-        href={`/app/projects/${projectId}`}
-        className={styles.backLink}
-        aria-label="Back to project detail"
-      >
+      <Link href={`/app/projects/${projectId}`} className={styles.backLink}>
         ← Back to Project Detail
       </Link>
 
@@ -74,6 +115,13 @@ export const MigrationDetailPage: FC<Props> = async ({ migrationId }) => {
       <div className={styles.twoColumns}>
         <div className={styles.box}>
           <h2 className={styles.h2}>Migration Health</h2>
+          <div className={styles.erdLinks}>
+            {erdLinks.map(({ path, filename }) => (
+              <Link key={path} href={path} className={styles.erdLink}>
+                View ERD Diagram: {filename} →
+              </Link>
+            ))}
+          </div>
         </div>
         <div className={styles.box}>
           <h2 className={styles.h2}>Summary</h2>
diff --git a/frontend/apps/app/libs/github/api.server.ts b/frontend/apps/app/libs/github/api.server.ts
index 457f772d49..cd90b46ff8 100644
--- a/frontend/apps/app/libs/github/api.server.ts
+++ b/frontend/apps/app/libs/github/api.server.ts
@@ -117,47 +117,66 @@ export const updatePullRequestComment = async (
   return response.data
 }
 
+export const verifyWebhookSignature = (
+  payload: string,
+  signature: string,
+): boolean => {
+  const hmac = crypto.createHmac(
+    'sha256',
+    process.env.GITHUB_WEBHOOK_SECRET || '',
+  )
+  const digest = `sha256=${hmac.update(payload).digest('hex')}`
+
+  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature))
+}
+
+export const getRepository = async (
+  projectId: string,
+  installationId: number,
+) => {
+  const [owner, repo] = projectId.split('/')
+  if (!owner || !repo) throw new Error('Invalid project ID format')
+
+  const octokit = await createOctokit(installationId)
+  const { data } = await octokit.repos.get({
+    owner,
+    repo,
+  })
+
+  return data
+}
+
 export const getFileContent = async (
+  repositoryFullName: string,
+  filePath: string,
+  ref: string,
   installationId: number,
-  owner: string,
-  repo: string,
-  path: string,
-  ref?: string,
-): Promise<string> => {
+): Promise<string | null> => {
+  const [owner, repo] = repositoryFullName.split('/')
+
+  if (!owner || !repo) {
+    console.error('Invalid repository format:', repositoryFullName)
+    return null
+  }
+
   const octokit = await createOctokit(installationId)
 
   try {
-    const response = await octokit.repos.getContent({
+    const { data } = await octokit.repos.getContent({
       owner,
       repo,
-      path,
+      path: filePath,
       ref,
     })
 
-    // The GitHub API returns the file content encoded in Base64
-    if ('content' in response.data && !Array.isArray(response.data)) {
-      const content = Buffer.from(response.data.content, 'base64').toString(
-        'utf-8',
-      )
-      return content
+    if ('type' in data && data.type === 'file' && 'content' in data) {
+      return Buffer.from(data.content, 'base64').toString('utf-8')
     }
 
-    throw new Error('Not a file or file content not available')
+    console.warn('Not a file:', filePath)
+    return null
   } catch (error) {
-    console.error(`Error fetching file content for ${path}:`, error)
-    throw error
+    console.error(`Error fetching file content for ${filePath}:`, error)
+    return null
   }
 }
-
-export const verifyWebhookSignature = (
-  payload: string,
-  signature: string,
-): boolean => {
-  const hmac = crypto.createHmac(
-    'sha256',
-    process.env.GITHUB_WEBHOOK_SECRET || '',
-  )
-  const digest = `sha256=${hmac.update(payload).digest('hex')}`
-
-  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature))
-}
diff --git a/frontend/apps/app/src/functions/processSavePullRequest.ts b/frontend/apps/app/src/functions/processSavePullRequest.ts
index d714d2ba31..0c2fb54bb5 100644
--- a/frontend/apps/app/src/functions/processSavePullRequest.ts
+++ b/frontend/apps/app/src/functions/processSavePullRequest.ts
@@ -96,16 +96,14 @@ export async function processSavePullRequest(
     matchedFiles.map(async (file) => {
       try {
         const content = await getFileContent(
-          Number(repository.installationId),
-          repository.owner,
-          repository.name,
+          `${repository.owner}/${repository.name}`,
           file.filename,
           prDetails.head.ref,
+          Number(repository.installationId),
         )
-
         return {
           filename: file.filename,
-          content,
+          content: content ?? '',
         }
       } catch (error) {
         console.error(`Error fetching content for ${file.filename}:`, error)


Reviews:
user: login: NoritakaIkeda
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T09:48:32Z
user: login: MH4GF
body: LGTM 👍🏻 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T11:33:50Z
user: login: NoritakaIkeda
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-21T11:40:32Z

Review comments:
diff_hunk: @@ -117,38 +118,6 @@ export const updatePullRequestComment = async (
   return response.data
 }
 
-export const getFileContent = async (
-  installationId: number,
-  owner: string,
-  repo: string,
-  path: string,
-  ref?: string,
-): Promise<string> => {
-  const octokit = await createOctokit(installationId)
-
-  try {
-    const response = await octokit.repos.getContent({
-      owner,
-      repo,
-      path,
-      ref,
-    })
-
-    // The GitHub API returns the file content encoded in Base64
-    if ('content' in response.data && !Array.isArray(response.data)) {
-      const content = Buffer.from(response.data.content, 'base64').toString(
-        'utf-8',
-      )
-      return content
-    }
-
-    throw new Error('Not a file or file content not available')
-  } catch (error) {
-    console.error(`Error fetching file content for ${path}:`, error)
-    throw error
-  }
-}
-
path: frontend/apps/app/libs/github/api.server.ts
user: login: NoritakaIkeda
body: Merged the parallel implementations of getRepository that were developed concurrently.
created_at: 2025-03-21T09:44:11Z
author_association: MEMBER
diff_hunk: @@ -96,16 +96,14 @@ export async function processSavePullRequest(
     matchedFiles.map(async (file) => {
       try {
         const content = await getFileContent(
-          Number(repository.installationId),
-          repository.owner,
-          repository.name,
+          `${repository.owner}/${repository.name}`,
           file.filename,
           prDetails.head.ref,
+          Number(repository.installationId),
         )
-
         return {
           filename: file.filename,
-          content,
+          content: content ?? '',
path: frontend/apps/app/src/functions/processSavePullRequest.ts
user: login: NoritakaIkeda
body: As a result of merging the parallel implementations of getRepository that were developed concurrently, I also updated the usage accordingly.
created_at: 2025-03-21T09:44:50Z
author_association: MEMBER
path: frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/page.tsx
user: login: NoritakaIkeda
body: Based on the implementation in [this file](https://github.com/liam-hq/liam/blob/a40d83fa12d75c7cd2ccac2fe2a519f3ebc09aa2/frontend/apps/app/app/erd/p/%5B...slug%5D/page.tsx), I updated the code to fetch file data from the GitHub API instead.
created_at: 2025-03-21T09:46:15Z
author_association: MEMBER
path: frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/erdViewer.tsx
user: login: NoritakaIkeda
body: The implementation is almost the same as in [erdViewer.tsx](https://github.com/liam-hq/liam/blob/a40d83fa12d75c7cd2ccac2fe2a519f3ebc09aa2/frontend/apps/app/app/erd/p/%5B...slug%5D/erdViewer.tsx).
created_at: 2025-03-21T09:47:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,61 @@
+'use client'
+
+import { CookieConsent } from '@/components/CookieConsent'
+import type { DBStructure, ProcessError } from '@liam-hq/db-structure'
+import {
+  ERDRenderer,
+  VersionProvider,
+  initDBStructureStore,
+  versionSchema,
+} from '@liam-hq/erd-core'
+import { useEffect, useState } from 'react'
+import * as v from 'valibot'
+
+type ErrorObject = {
+  name: string
+  message: string
+  instruction?: string
+}
+
+type ERDViewerProps = {
+  dbStructure: DBStructure
+  errorObjects: ErrorObject[]
+  defaultSidebarOpen: boolean
+  defaultPanelSizes?: number[]
+}
+
+export default function ERDViewer({
+  dbStructure,
+  errorObjects,
+  defaultSidebarOpen,
+  defaultPanelSizes = [20, 80],
+}: ERDViewerProps) {
+  const [isShowCookieConsent, setShowCookieConsent] = useState(false)
+
+  useEffect(() => {
+    initDBStructureStore(dbStructure)
+    setShowCookieConsent(window === window.parent)
+  }, [dbStructure])
+
+  const versionData = {
+    version: '0.1.0', // NOTE: no maintained version for ERD Web
+    gitHash: process.env.NEXT_PUBLIC_GIT_HASH,
+    envName: process.env.NEXT_PUBLIC_ENV_NAME,
+    date: process.env.NEXT_PUBLIC_RELEASE_DATE,
+    displayedOn: 'web',
+  }
+  const version = v.parse(versionSchema, versionData)
+
+  return (
+    <div style={{ height: '100dvh' }}>
+      <VersionProvider version={version}>
+        <ERDRenderer
+          defaultSidebarOpen={defaultSidebarOpen}
+          defaultPanelSizes={defaultPanelSizes}
+          errorObjects={errorObjects}
+        />
+      </VersionProvider>
+      {isShowCookieConsent && <CookieConsent />}
path: frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/erdViewer.tsx
user: login: MH4GF
body: This `CookieConsent` should be enabled on all pages, so it should be moved to layout.tsx.
created_at: 2025-03-21T11:05:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+'use client'
+
+import { CookieConsent } from '@/components/CookieConsent'
+import type { DBStructure, ProcessError } from '@liam-hq/db-structure'
+import {
+  ERDRenderer,
+  VersionProvider,
+  initDBStructureStore,
+  versionSchema,
+} from '@liam-hq/erd-core'
+import { useEffect, useState } from 'react'
+import * as v from 'valibot'
+
+type ErrorObject = {
+  name: string
+  message: string
+  instruction?: string
+}
+
+type ERDViewerProps = {
+  dbStructure: DBStructure
+  errorObjects: ErrorObject[]
+  defaultSidebarOpen: boolean
+  defaultPanelSizes?: number[]
+}
+
+export default function ERDViewer({
+  dbStructure,
+  errorObjects,
+  defaultSidebarOpen,
+  defaultPanelSizes = [20, 80],
+}: ERDViewerProps) {
+  const [isShowCookieConsent, setShowCookieConsent] = useState(false)
+
+  useEffect(() => {
+    initDBStructureStore(dbStructure)
+    setShowCookieConsent(window === window.parent)
+  }, [dbStructure])
+
+  const versionData = {
+    version: '0.1.0', // NOTE: no maintained version for ERD Web
+    gitHash: process.env.NEXT_PUBLIC_GIT_HASH,
+    envName: process.env.NEXT_PUBLIC_ENV_NAME,
+    date: process.env.NEXT_PUBLIC_RELEASE_DATE,
+    displayedOn: 'web',
+  }
+  const version = v.parse(versionSchema, versionData)
path: frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/erdViewer.tsx
user: login: MH4GF
body: I would like to commonize with Liam ERD side.
created_at: 2025-03-21T11:06:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,6 +119,18 @@ export const MigrationDetailPage: FC<Props> = async ({ migrationId }) => {
       <div className={styles.twoColumns}>
         <div className={styles.box}>
           <h2 className={styles.h2}>Migration Health</h2>
+          <div className={styles.erdLinks}>
+            {erdLinks.map(({ path, filename }) => (
+              <Link
+                key={path}
+                href={path}
+                className={styles.erdLink}
+                aria-label={`View ERD diagram for ${filename}`}
path: frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.tsx
user: login: MH4GF
body: Is this necessary?
created_at: 2025-03-21T11:20:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,6 +119,18 @@ export const MigrationDetailPage: FC<Props> = async ({ migrationId }) => {
       <div className={styles.twoColumns}>
         <div className={styles.box}>
           <h2 className={styles.h2}>Migration Health</h2>
+          <div className={styles.erdLinks}>
+            {erdLinks.map(({ path, filename }) => (
+              <Link
+                key={path}
+                href={path}
+                className={styles.erdLink}
+                aria-label={`View ERD diagram for ${filename}`}
path: frontend/apps/app/features/migrations/pages/MigrationDetailPage/MigrationDetailPage.tsx
user: login: NoritakaIkeda
body: Not needed here, so I removed it 🙆 
created_at: 2025-03-21T11:40:32Z
author_association: MEMBER

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-03-19T10:32:33Z
body: ###  ⚠️  No Changeset found

Latest commit: b875be49bfa96d72e3901f30e6079801db53599b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/liam-hq/liam/new/migration-erd?filename=.changeset/few-chairs-glow.md&value=---%0A%22%40liam-hq%2Fapp%22%3A%20patch%0A---%0A%0A%E2%9C%A8%20Link%20schemas%20in%20the%20repository%20and%20continuously%20update%20the%20ERD%0A)


author_association: NONE
user: login: vercel[bot]
created_at: 2025-03-19T10:32:34Z
body: [vc]: #AEZqmUdyLupETkzGqJFkXbfzPG79lGIvROh9mMD+0X0=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ0ZXN0LWxpYW0tZG9jcyIsInJvb3REaXJlY3RvcnkiOiJmcm9udGVuZC9hcHBzL2RvY3MiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9yb3V0ZS0wNi1jb3JlL3Rlc3QtbGlhbS1kb2NzLzRyemI4MU1HU2NHY0J5QTVZSnViRjVEb1Bpb3oiLCJuZXh0Q29tbWl0U3RhdHVzIjoiSUdOT1JFRCJ9LHsibmFtZSI6InRlc3QtbGlhbS1hcHAiLCJyb290RGlyZWN0b3J5IjoiZnJvbnRlbmQvYXBwcy9hcHAiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9yb3V0ZS0wNi1jb3JlL3Rlc3QtbGlhbS1hcHAvek1VNkpCUnFuYUVqblA2Vno3enlacmU1TTRncyIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIn0seyJuYW1lIjoidGVzdC1saWFtLWVyZC1zYW1wbGUiLCJyb290RGlyZWN0b3J5IjoiZnJvbnRlbmQvYXBwcy9lcmQtc2FtcGxlIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vcm91dGUtMDYtY29yZS90ZXN0LWxpYW0tZXJkLXNhbXBsZS83SkZ4cVdqaWY3V1dGdE5RMlRqcHg3dFlpb29tIiwibmV4dENvbW1pdFN0YXR1cyI6IklHTk9SRUQifSx7Im5hbWUiOiJsaWFtLWRvY3MiLCJyb290RGlyZWN0b3J5IjoiZnJvbnRlbmQvYXBwcy9kb2NzIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3JvdXRlLTA2LWNvcmUvbGlhbS1kb2NzLzRCSFRjb1hrYkJqYkwyY2Y5R1IyNmpURjFkbXIiLCJwcmV2aWV3VXJsIjoibGlhbS1kb2NzLWdpdC1taWdyYXRpb24tZXJkLXJvdXRlLTA2LWNvcmUudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9fSx7Im5hbWUiOiJsaWFtLWVyZC1zYW1wbGUiLCJyb290RGlyZWN0b3J5IjoiZnJvbnRlbmQvYXBwcy9lcmQtc2FtcGxlIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3JvdXRlLTA2LWNvcmUvbGlhbS1lcmQtc2FtcGxlLzlTUmtFZWlCUDl2bnd1QW93a2ZzcXBqNjROV0MiLCJwcmV2aWV3VXJsIjoibGlhbS1lcmQtc2FtcGxlLWdpdC1taWdyYXRpb24tZXJkLXJvdXRlLTA2LWNvcmUudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJsaWFtLWVyZC1zYW1wbGUtZ2l0LW1pZ3JhdGlvbi1lcmQtcm91dGUtMDYtY29yZS52ZXJjZWwuYXBwIn19LHsibmFtZSI6ImxpYW0tYXBwIiwicm9vdERpcmVjdG9yeSI6ImZyb250ZW5kL2FwcHMvYXBwIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3JvdXRlLTA2LWNvcmUvbGlhbS1hcHAvREY1NzlCNWZ0NTV5VWNUR3VUcWVDUzk5QWNibSIsInByZXZpZXdVcmwiOiJsaWFtLWFwcC1naXQtbWlncmF0aW9uLWVyZC1yb3V0ZS0wNi1jb3JlLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoibGlhbS1hcHAtZ2l0LW1pZ3JhdGlvbi1lcmQtcm91dGUtMDYtY29yZS52ZXJjZWwuYXBwIn19XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **liam-app** | ✅ Ready ([Inspect](https://vercel.com/route-06-core/liam-app/DF579B5ft55yUcTGuTqeCS99Acbm)) | [Visit Preview](https://liam-app-git-migration-erd-route-06-core.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/liam-app-git-migration-erd-route-06-core.vercel.app?via=pr-comment-feedback-link) | Mar 21, 2025 11:43am |
| **liam-erd-sample** | ✅ Ready ([Inspect](https://vercel.com/route-06-core/liam-erd-sample/9SRkEeiBP9vnwuAowkfsqpj64NWC)) | [Visit Preview](https://liam-erd-sample-git-migration-erd-route-06-core.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/liam-erd-sample-git-migration-erd-route-06-core.vercel.app?via=pr-comment-feedback-link) | Mar 21, 2025 11:43am |

<details><summary>4 Skipped Deployments</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **liam-docs** | ⬜️ Ignored ([Inspect](https://vercel.com/route-06-core/liam-docs/4BHTcoXkbBjbL2cf9GR26jTF1dmr)) | [Visit Preview](https://liam-docs-git-migration-erd-route-06-core.vercel.app) |  | Mar 21, 2025 11:43am |
| **test-liam-app** | ⬜️ Ignored ([Inspect](https://vercel.com/route-06-core/test-liam-app/zMU6JBRqnaEjnP6Vz7zyZre5M4gs)) |  |  | Mar 21, 2025 11:43am |
| **test-liam-docs** | ⬜️ Ignored ([Inspect](https://vercel.com/route-06-core/test-liam-docs/4rzb81MGScGcByA5YJubF5DoPioz)) |  |  | Mar 21, 2025 11:43am |
| **test-liam-erd-sample** | ⬜️ Ignored ([Inspect](https://vercel.com/route-06-core/test-liam-erd-sample/7JFxqWjif7WWFtNQ2Tjpx7tYioom)) |  |  | Mar 21, 2025 11:43am |
</details>







author_association: NONE
user: login: supabase[bot]
created_at: 2025-03-19T10:32:56Z
body: [supa]:jvdhjtjljbwgydrrkwbp
**Updates to [Preview Branch (migration-erd) ↗︎](https://supabase.com/dashboard/project/jvdhjtjljbwgydrrkwbp)**

| Deployments | Status                      | Updated                   |
| ----------- | --------------------------- | ------------------------- |
| Database    | ✅ | Fri, 21 Mar 2025 11:40:05 UTC |
| Services    | ✅  | Fri, 21 Mar 2025 11:40:05 UTC  |
| APIs        | ✅      | Fri, 21 Mar 2025 11:40:05 UTC      |

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

| Tasks          | Status                       | Updated                    |
| -------------- | ---------------------------- | -------------------------- |
| Configurations | ✅ | Fri, 21 Mar 2025 11:40:06 UTC |
| Migrations     | ✅ | Fri, 21 Mar 2025 11:40:06 UTC |
| Seeding        | ✅   | Fri, 21 Mar 2025 11:40:06 UTC   |
| Edge Functions | ✅  | Fri, 21 Mar 2025 11:40:06 UTC  |

<hr/>




View logs for this [Workflow Run ↗︎](https://supabase.com/dashboard/project/jvdhjtjljbwgydrrkwbp/branches).
Learn more about [Supabase for Git ↗︎](https://supabase.com/docs/guides/platform/branching).

author_association: NONE
user: login: qodo-free-for-open-source-projects[bot]
created_at: 2025-03-21T09:49:54Z
body: ## PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

<table>
<tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 3 🔵🔵🔵⚪⚪</td></tr>
<tr><td>🧪&nbsp;<strong>No relevant tests</strong></td></tr>
<tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr>
<tr><td>⚡&nbsp;<strong>Recommended focus areas for review</strong><br><br>

<details><summary><a href='https://github.com/liam-hq/liam/pull/941/files#diff-43b0383ada9a3fb1a74039da72aaef0423ffaa06576e487c40a95c3405da193aR149-R183'><strong>Error Handling</strong></a>

The new getFileContent function returns null on error instead of throwing exceptions. This changes error handling behavior which might affect dependent code that expects exceptions.
</summary>

```typescript
export const getFileContent = cache(
  async (
    repositoryFullName: string,
    filePath: string,
    ref: string,
    installationId: number,
  ): Promise<string | null> => {
    const [owner, repo] = repositoryFullName.split('/')

    if (!owner || !repo) {
      console.error('Invalid repository format:', repositoryFullName)
      return null
    }

    const octokit = await createOctokit(installationId)

    try {
      const { data } = await octokit.repos.getContent({
        owner,
        repo,
        path: filePath,
        ref,
      })

      if ('type' in data && data.type === 'file' && 'content' in data) {
        return Buffer.from(data.content, 'base64').toString('utf-8')
      }

      console.warn('Not a file:', filePath)
      return null
    } catch (error) {
      console.error(`Error fetching file content for ${filePath}:`, error)
      return null
    }
  },
```

</details>

<details><summary><a href='https://github.com/liam-hq/liam/pull/941/files#diff-ff0af273206f1815552501f125df50ae38ffc5ce993e227a39e740694fea8b99R142-R142'><strong>Potential Error</strong></a>

The code sets prism.wasm path using path.resolve with process.cwd(), which might not work correctly in production environments where the working directory could be different.
</summary>

```typescriptreact
setPrismWasmUrl(path.resolve(process.cwd(), 'prism.wasm'))
```

</details>

<details><summary><a href='https://github.com/liam-hq/liam/pull/941/files#diff-ff0af273206f1815552501f125df50ae38ffc5ce993e227a39e740694fea8b99R200-R215'><strong>Error Suppression</strong></a>

The catch block at the end of the Page component catches all errors with a generic _error variable but doesn't log the actual error, making debugging difficult.
</summary>

```typescriptreact
} catch (_error) {
  return (
    <ERDViewer
      dbStructure={blankDbStructure}
      defaultSidebarOpen={false}
      errorObjects={[
        {
          name: 'GitHubError',
          message: 'Failed to fetch file content from GitHub',
          instruction:
            'Please check your repository permissions and try again.',
        },
      ]}
    />
  )
}
```

</details>

</td></tr>
</table>

author_association: CONTRIBUTOR
user: login: qodo-free-for-open-source-projects[bot]
created_at: 2025-03-21T09:50:25Z
body: ## PR Code Suggestions ✨

<!-- 4f12f89 -->

Explore these optional code suggestions:

<table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=1>Possible issue</td>
<td>



<details><summary>Fix path resolution issue</summary>

___


**The <code>path.resolve(process.cwd(), 'prism.wasm')</code> will likely fail in production <br>environments where the current working directory might differ from what's <br>expected. Consider using a path relative to the application's root or a public <br>URL.**

[frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/page.tsx [142]](https://github.com/liam-hq/liam/pull/941/files#diff-ff0af273206f1815552501f125df50ae38ffc5ce993e227a39e740694fea8b99R142-R142)

```diff
-setPrismWasmUrl(path.resolve(process.cwd(), 'prism.wasm'))
+setPrismWasmUrl('/prism.wasm') // Use a path relative to the public directory
```

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=0 -->

<details><summary>Suggestion importance[1-10]: 9</summary>

__

Why: The suggestion addresses a critical path resolution issue that would likely cause the application to fail in production environments. Using an absolute path with process.cwd() is unreliable as the working directory can vary between environments.


</details></details></td><td align=center>High

</td></tr><tr><td rowspan=1>General</td>
<td>



<details><summary>Improve error reporting<!-- not_implemented --></summary>

___


**Sending every parsing error to Sentry could potentially flood your error <br>tracking system. Consider adding context to the errors or implementing rate <br>limiting for these errors.**

[frontend/apps/app/app/(app)/app/projects/[projectId]/erd/[branchOrCommit]/[...slug]/page.tsx [171-174]](https://github.com/liam-hq/liam/pull/941/files#diff-ff0af273206f1815552501f125df50ae38ffc5ce993e227a39e740694fea8b99R171-R174)

```diff
 const { value: dbStructure, errors } = await parse(content, format)
-for (const error of errors) {
-  Sentry.captureException(error)
+if (errors.length > 0) {
+  Sentry.withScope((scope) => {
+    scope.setContext('parsing', { 
+      filePath, 
+      format, 
+      errorCount: errors.length 
+    });
+    Sentry.captureException(new Error(`Schema parsing errors in ${filePath}`));
+  });
 }
```

- [ ] **Apply this suggestion** <!-- /improve --apply_suggestion=1 -->

<details><summary>Suggestion importance[1-10]: 7</summary>

__

Why: The suggestion significantly improves error reporting by adding context to Sentry errors and preventing potential flooding of the error tracking system. This enhances debugging capabilities while maintaining system performance.


</details></details></td><td align=center>Medium

</td></tr>
<tr><td align="center" colspan="2">

- [ ] Update <!-- /improve --more_suggestions=true -->

</td><td></td></tr></tbody></table>
author_association: CONTRIBUTOR