diff --git a/.env.example b/.env.example
index 239f191a..af803bee 100644
--- a/.env.example
+++ b/.env.example
@@ -2,7 +2,7 @@ FRAMNA_DOCS_BASE_URL=http://localhost:3000
FRAMNA_DOCS_TITLE=Framna Docs
FRAMNA_DOCS_DESCRIPTION=Documentation for Framna's APIs
FRAMNA_DOCS_HELP_URL=https://github.com/shapehq/framna-docs/wiki
-FRAMNA_DOCS_PROJECT_CONFIGURATION_FILENAME=.shape-docs.yml
+FRAMNA_DOCS_PROJECT_CONFIGURATION_FILENAME=.framna-docs.yml
NEXTAUTH_URL=https://docs.example.com
NEXTAUTH_SECRET=use [openssl rand -base64 32] to generate a 32 bytes value
REDIS_URL=localhost
@@ -24,3 +24,4 @@ GITHUB_APP_ID=123456
GITHUB_PRIVATE_KEY_BASE_64=base 64 encoded version of the private key - see README.md for more info
ENCRYPTION_PUBLIC_KEY_BASE_64=base 64 encoded version of the public key
ENCRYPTION_PRIVATE_KEY_BASE_64=base 64 encoded version of the private key
+NEXT_PUBLIC_ENABLE_DIFF_SIDEBAR=true
diff --git a/.github/workflows/check-changes-to-env.yml b/.github/workflows/check-changes-to-env.yml
index 61d982b9..bc67d682 100644
--- a/.github/workflows/check-changes-to-env.yml
+++ b/.github/workflows/check-changes-to-env.yml
@@ -1,7 +1,7 @@
name: Check Changes to Env
permissions:
contents: read
- pull-requests: read
+ pull-requests: write
issues: write
on:
pull_request:
diff --git a/Dockerfile b/Dockerfile
index b24c6764..671b45aa 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,11 @@
FROM node:24-alpine AS base
+FROM base AS oasdiff
+ARG OASDIFF_VERSION=2.10.0
+RUN apk add --no-cache curl tar ca-certificates
+RUN curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh \
+ | sh -s -- -b /usr/local/bin "v${OASDIFF_VERSION}"
+
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
@@ -46,6 +52,7 @@ RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
+COPY --from=oasdiff /usr/local/bin/oasdiff /usr/local/bin/oasdiff
# Set the correct permission for prerender cache
RUN mkdir .next
diff --git a/README.md b/README.md
index d5695df4..dda1ecea 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,17 @@ Please refer to the following articles in [the wiki](https://github.com/shapehq/
- [Updating Documentation](https://github.com/shapehq/framna-docs/wiki/Updating-Documentation)
- [Deploying Framna Docs](https://github.com/shapehq/framna-docs/wiki/Deploying-Framna-Docs)
+### Install the OpenAPI diff tool locally
+
+Framna Docs relies on the [`oasdiff`](https://github.com/oasdiff/oasdiff) CLI when comparing specifications.
+
+On MacOS you can install with homebrew:
+
+```bash
+brew tap oasdiff/homebrew-oasdiff
+brew install oasdiff
+```
+
## 👨🔧 How does it work?
Framna Docs uses [OpenAPI specifications](https://swagger.io) from GitHub repositories. Users log in with their GitHub account to access documentation for projects they have access to. A repository only needs an OpenAPI spec to be recognized by Framna Docs, but customization is possible with a .framna-docs.yml file. Here's an example:
diff --git a/__test__/diff/OasDiffCalculator.test.ts b/__test__/diff/OasDiffCalculator.test.ts
new file mode 100644
index 00000000..cbe4932f
--- /dev/null
+++ b/__test__/diff/OasDiffCalculator.test.ts
@@ -0,0 +1,142 @@
+import { OasDiffCalculator } from "../../src/features/diff/data/OasDiffCalculator"
+import IGitHubClient from "../../src/common/github/IGitHubClient"
+
+const createMockGitHubClient = (
+ baseUrl: string,
+ headUrl: string,
+ mergeBaseSha = "abc123"
+): IGitHubClient => ({
+ async compareCommitsWithBasehead() {
+ return { mergeBaseSha }
+ },
+ async getRepositoryContent(request) {
+ if (request.ref === mergeBaseSha) {
+ return { downloadURL: baseUrl }
+ }
+ return { downloadURL: headUrl }
+ },
+ async graphql() {
+ return {}
+ },
+ async getPullRequestFiles() {
+ return []
+ },
+ async getPullRequestComments() {
+ return []
+ },
+ async addCommentToPullRequest() {},
+ async updatePullRequestComment() {}
+})
+
+test("It rejects non-GitHub URLs for base spec", async () => {
+ const mockGitHubClient = createMockGitHubClient(
+ "https://malicious-site.com/file.yaml",
+ "https://raw.githubusercontent.com/owner/repo/main/file.yaml"
+ )
+ const calculator = new OasDiffCalculator(mockGitHubClient)
+
+ await expect(
+ calculator.calculateDiff("owner", "repo", "path.yaml", "base", "head")
+ ).rejects.toThrow("Invalid URL for base spec")
+})
+
+test("It rejects invalid URLs", async () => {
+ const mockGitHubClient = createMockGitHubClient(
+ "not-a-valid-url",
+ "https://raw.githubusercontent.com/owner/repo/main/file.yaml"
+ )
+ const calculator = new OasDiffCalculator(mockGitHubClient)
+
+ await expect(
+ calculator.calculateDiff("owner", "repo", "path.yaml", "base", "head")
+ ).rejects.toThrow("Invalid URL for base spec")
+})
+
+test("It accepts raw.githubusercontent.com URLs", async () => {
+ const mockGitHubClient = createMockGitHubClient(
+ "https://raw.githubusercontent.com/owner/repo/main/file1.yaml",
+ "https://raw.githubusercontent.com/owner/repo/main/file2.yaml"
+ )
+ const calculator = new OasDiffCalculator(mockGitHubClient)
+
+ // This will fail when trying to execute oasdiff, but that's expected
+ // We're only testing that URL validation passes
+ await expect(
+ calculator.calculateDiff("owner", "repo", "path.yaml", "base", "head")
+ ).rejects.toThrow("Failed to execute OpenAPI diff tool")
+})
+
+test("It accepts github.com URLs", async () => {
+ const mockGitHubClient = createMockGitHubClient(
+ "https://github.com/owner/repo/raw/main/file1.yaml",
+ "https://github.com/owner/repo/raw/main/file2.yaml"
+ )
+ const calculator = new OasDiffCalculator(mockGitHubClient)
+
+ // This will fail when trying to execute oasdiff, but that's expected
+ // We're only testing that URL validation passes
+ await expect(
+ calculator.calculateDiff("owner", "repo", "path.yaml", "base", "head")
+ ).rejects.toThrow("Failed to execute OpenAPI diff tool")
+})
+
+test("It accepts api.github.com URLs", async () => {
+ const mockGitHubClient = createMockGitHubClient(
+ "https://api.github.com/repos/owner/repo/contents/file1.yaml",
+ "https://api.github.com/repos/owner/repo/contents/file2.yaml"
+ )
+ const calculator = new OasDiffCalculator(mockGitHubClient)
+
+ // This will fail when trying to execute oasdiff, but that's expected
+ // We're only testing that URL validation passes
+ await expect(
+ calculator.calculateDiff("owner", "repo", "path.yaml", "base", "head")
+ ).rejects.toThrow("Failed to execute OpenAPI diff tool")
+})
+
+test("It rejects URLs with GitHub-like subdomains but different domains", async () => {
+ const mockGitHubClient = createMockGitHubClient(
+ "https://raw.githubusercontent.com.evil.com/file.yaml",
+ "https://raw.githubusercontent.com/owner/repo/main/file.yaml"
+ )
+ const calculator = new OasDiffCalculator(mockGitHubClient)
+
+ await expect(
+ calculator.calculateDiff("owner", "repo", "path.yaml", "base", "head")
+ ).rejects.toThrow("Invalid URL for base spec")
+})
+
+test("It validates both base and head URLs", async () => {
+ const mockGitHubClient = createMockGitHubClient(
+ "https://raw.githubusercontent.com/owner/repo/main/file1.yaml",
+ "https://malicious-site.com/file.yaml"
+ )
+ const calculator = new OasDiffCalculator(mockGitHubClient)
+
+ await expect(
+ calculator.calculateDiff("owner", "repo", "path.yaml", "base", "head")
+ ).rejects.toThrow("Invalid URL for head spec")
+})
+
+test("It returns empty changes when comparing same refs", async () => {
+ const mockGitHubClient = createMockGitHubClient(
+ "https://raw.githubusercontent.com/owner/repo/main/file1.yaml",
+ "https://raw.githubusercontent.com/owner/repo/main/file2.yaml",
+ "abc123"
+ )
+ const calculator = new OasDiffCalculator(mockGitHubClient)
+
+ const result = await calculator.calculateDiff(
+ "owner",
+ "repo",
+ "path.yaml",
+ "abc123",
+ "abc123"
+ )
+
+ expect(result).toEqual({
+ from: "abc123",
+ to: "abc123",
+ changes: []
+ })
+})
diff --git a/__test__/projects/GitHubProjectDataSource.test.ts b/__test__/projects/GitHubProjectDataSource.test.ts
index 12438c76..586375bc 100644
--- a/__test__/projects/GitHubProjectDataSource.test.ts
+++ b/__test__/projects/GitHubProjectDataSource.test.ts
@@ -845,11 +845,13 @@ test("It adds remote versions from the project configuration", async () => {
id: "huey",
name: "Huey",
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/huey.yml" })}`,
+ urlHash: "89ba381286214eec",
isDefault: false
}, {
id: "dewey",
name: "Dewey",
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/dewey.yml" })}`,
+ urlHash: "8f810fff152505f6",
isDefault: false
}]
}, {
@@ -860,6 +862,7 @@ test("It adds remote versions from the project configuration", async () => {
id: "louie",
name: "Louie",
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/louie.yml" })}`,
+ urlHash: "b83ebf43ceede6bc",
isDefault: false
}]
}])
@@ -925,6 +928,7 @@ test("It modifies ID of remote version if the ID already exists", async () => {
id: "baz",
name: "Baz",
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`,
+ urlHash: "25cb42ff63570cb5",
isDefault: false
}]
}, {
@@ -935,6 +939,7 @@ test("It modifies ID of remote version if the ID already exists", async () => {
id: "hello",
name: "Hello",
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/hello.yml" })}`,
+ urlHash: "d078bd689699d1f0",
isDefault: false
}]
}])
@@ -979,6 +984,7 @@ test("It lets users specify the ID of a remote version", async () => {
id: "baz",
name: "Baz",
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`,
+ urlHash: "25cb42ff63570cb5",
isDefault: false
}]
}])
@@ -1023,6 +1029,7 @@ test("It lets users specify the ID of a remote specification", async () => {
id: "some-spec",
name: "Baz",
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`,
+ urlHash: "25cb42ff63570cb5",
isDefault: false
}]
}])
diff --git a/package-lock.json b/package-lock.json
index e5b16364..5db706a0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,52 +16,52 @@
"@fortawesome/free-regular-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0",
"@fortawesome/react-fontawesome": "^3.1.1",
- "@mui/icons-material": "^7.3.5",
+ "@mui/icons-material": "^7.3.6",
"@mui/material": "^7.0.1",
"@octokit/auth-app": "^8.1.2",
"@octokit/core": "^7.0.6",
- "@octokit/webhooks": "~14.1.3",
+ "@octokit/webhooks": "~14.2.0",
"core-js": "^3.47.0",
"encoding": "^0.1.13",
"figma-squircle": "^1.1.0",
"install": "^0.13.0",
"ioredis": "^5.8.2",
"mobx": "^6.15.0",
- "next": "16.0.7",
+ "next": "16.1.1",
"next-auth": "^5.0.0-beta.30",
- "npm": "^11.6.4",
+ "npm": "^11.7.0",
"nprogress": "^0.2.0",
"octokit": "^5.0.5",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
"redis-semaphore": "^5.6.2",
"redoc": "^2.5.2",
"sharp": "^0.34.2",
"styled-components": "^6.1.19",
- "swr": "^2.3.7",
+ "swr": "^2.3.8",
"usehooks-ts": "^3.1.1",
"yaml": "^2.8.2",
- "zod": "^4.1.13"
+ "zod": "^4.3.4"
},
"devDependencies": {
"@auth/pg-adapter": "^1.11.1",
- "@tailwindcss/postcss": "^4.1.17",
+ "@tailwindcss/postcss": "^4.1.18",
"@types/jest": "^30.0.0",
- "@types/node": "^24.10.1",
+ "@types/node": "^25.0.3",
"@types/nprogress": "^0.2.3",
- "@types/pg": "^8.15.6",
+ "@types/pg": "^8.16.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
- "@typescript-eslint/eslint-plugin": "^8.48.0",
- "@typescript-eslint/parser": "^8.48.0",
- "eslint": "^9.39.1",
- "eslint-config-next": "^16.0.6",
+ "@typescript-eslint/eslint-plugin": "^8.51.0",
+ "@typescript-eslint/parser": "^8.51.0",
+ "eslint": "^9.39.2",
+ "eslint-config-next": "^16.1.1",
"pg": "^8.16.3",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.4",
- "ts-jest": "^29.4.5",
+ "ts-jest": "^29.4.6",
"typescript": "^5.9.3",
- "typescript-eslint": "^8.48.0"
+ "typescript-eslint": "^8.51.0"
},
"engines": {
"node": ">=24.0.0 <25.0.0",
@@ -981,9 +981,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
- "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2157,9 +2157,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.5.tgz",
- "integrity": "sha512-kOLwlcDPnVz2QMhiBv0OQ8le8hTCqKM9cRXlfVPL91l3RGeOsxrIhNRsUt3Xb8wb+pTVUolW+JXKym93vRKxCw==",
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.6.tgz",
+ "integrity": "sha512-QaYtTHlr8kDFN5mE1wbvVARRKH7Fdw1ZuOjBJcFdVpfNfRYKF3QLT4rt+WaB6CKJvpqxRsmEo0kpYinhH5GeHg==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -2167,9 +2167,9 @@
}
},
"node_modules/@mui/icons-material": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.5.tgz",
- "integrity": "sha512-LciL1GLMZ+VlzyHAALSVAR22t8IST4LCXmljcUSx2NOutgO2XnxdIp8ilFbeNf9wpo0iUFbAuoQcB7h+HHIf3A==",
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.6.tgz",
+ "integrity": "sha512-0FfkXEj22ysIq5pa41A2NbcAhJSvmcZQ/vcTIbjDsd6hlslG82k5BEBqqS0ZJprxwIL3B45qpJ+bPHwJPlF7uQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4"
@@ -2182,7 +2182,7 @@
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
- "@mui/material": "^7.3.5",
+ "@mui/material": "^7.3.6",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
@@ -2193,17 +2193,17 @@
}
},
"node_modules/@mui/material": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
- "integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.6.tgz",
+ "integrity": "sha512-R4DaYF3dgCQCUAkr4wW1w26GHXcf5rCmBRHVBuuvJvaGLmZdD8EjatP80Nz5JCw0KxORAzwftnHzXVnjR8HnFw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.28.4",
- "@mui/core-downloads-tracker": "^7.3.5",
- "@mui/system": "^7.3.5",
- "@mui/types": "^7.4.8",
- "@mui/utils": "^7.3.5",
+ "@mui/core-downloads-tracker": "^7.3.6",
+ "@mui/system": "^7.3.6",
+ "@mui/types": "^7.4.9",
+ "@mui/utils": "^7.3.6",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
@@ -2222,7 +2222,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
- "@mui/material-pigment-css": "^7.3.5",
+ "@mui/material-pigment-css": "^7.3.6",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -2243,13 +2243,13 @@
}
},
"node_modules/@mui/private-theming": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.5.tgz",
- "integrity": "sha512-cTx584W2qrLonwhZLbEN7P5pAUu0nZblg8cLBlTrZQ4sIiw8Fbvg7GvuphQaSHxPxrCpa7FDwJKtXdbl2TSmrA==",
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.6.tgz",
+ "integrity": "sha512-Ws9wZpqM+FlnbZXaY/7yvyvWQo1+02Tbx50mVdNmzWEi51C51y56KAbaDCYyulOOBL6BJxuaqG8rNNuj7ivVyw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
- "@mui/utils": "^7.3.5",
+ "@mui/utils": "^7.3.6",
"prop-types": "^15.8.1"
},
"engines": {
@@ -2270,9 +2270,9 @@
}
},
"node_modules/@mui/styled-engine": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.5.tgz",
- "integrity": "sha512-zbsZ0uYYPndFCCPp2+V3RLcAN6+fv4C8pdwRx6OS3BwDkRCN8WBehqks7hWyF3vj1kdQLIWrpdv/5Y0jHRxYXQ==",
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.6.tgz",
+ "integrity": "sha512-+wiYbtvj+zyUkmDB+ysH6zRjuQIJ+CM56w0fEXV+VDNdvOuSywG+/8kpjddvvlfMLsaWdQe5oTuYGBcodmqGzQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
@@ -2304,16 +2304,16 @@
}
},
"node_modules/@mui/system": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.5.tgz",
- "integrity": "sha512-yPaf5+gY3v80HNkJcPi6WT+r9ebeM4eJzrREXPxMt7pNTV/1eahyODO4fbH3Qvd8irNxDFYn5RQ3idHW55rA6g==",
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.6.tgz",
+ "integrity": "sha512-8fehAazkHNP1imMrdD2m2hbA9sl7Ur6jfuNweh5o4l9YPty4iaZzRXqYvBCWQNwFaSHmMEj2KPbyXGp7Bt73Rg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
- "@mui/private-theming": "^7.3.5",
- "@mui/styled-engine": "^7.3.5",
- "@mui/types": "^7.4.8",
- "@mui/utils": "^7.3.5",
+ "@mui/private-theming": "^7.3.6",
+ "@mui/styled-engine": "^7.3.6",
+ "@mui/types": "^7.4.9",
+ "@mui/utils": "^7.3.6",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
@@ -2344,9 +2344,9 @@
}
},
"node_modules/@mui/types": {
- "version": "7.4.8",
- "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.8.tgz",
- "integrity": "sha512-ZNXLBjkPV6ftLCmmRCafak3XmSn8YV0tKE/ZOhzKys7TZXUiE0mZxlH8zKDo6j6TTUaDnuij68gIG+0Ucm7Xhw==",
+ "version": "7.4.9",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.9.tgz",
+ "integrity": "sha512-dNO8Z9T2cujkSIaCnWwprfeKmTWh97cnjkgmpFJ2sbfXLx8SMZijCYHOtP/y5nnUb/Rm2omxbDMmtUoSaUtKaw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4"
@@ -2361,13 +2361,13 @@
}
},
"node_modules/@mui/utils": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.5.tgz",
- "integrity": "sha512-jisvFsEC3sgjUjcPnR4mYfhzjCDIudttSGSbe1o/IXFNu0kZuR+7vqQI0jg8qtcVZBHWrwTfvAZj9MNMumcq1g==",
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.6.tgz",
+ "integrity": "sha512-jn+Ba02O6PiFs7nKva8R2aJJ9kJC+3kQ2R0BbKNY3KQQ36Qng98GnPRFTlbwYTdMD6hLEBKaMLUktyg/rTfd2w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
- "@mui/types": "^7.4.8",
+ "@mui/types": "^7.4.9",
"@types/prop-types": "^15.7.15",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
@@ -2404,15 +2404,15 @@
}
},
"node_modules/@next/env": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.7.tgz",
- "integrity": "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.1.tgz",
+ "integrity": "sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
- "version": "16.0.6",
- "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.6.tgz",
- "integrity": "sha512-9INsBF3/4XL0/tON8AGsh0svnTtDMLwv3iREGWnWkewGdOnd790tguzq9rX8xwrVthPyvaBHhw1ww0GZz0jO5Q==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.1.tgz",
+ "integrity": "sha512-Ovb/6TuLKbE1UiPcg0p39Ke3puyTCIKN9hGbNItmpQsp+WX3qrjO3WaMVSi6JHr9X1NrmthqIguVHodMJbh/dw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2420,9 +2420,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.7.tgz",
- "integrity": "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.1.tgz",
+ "integrity": "sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==",
"cpu": [
"arm64"
],
@@ -2436,9 +2436,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.7.tgz",
- "integrity": "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.1.tgz",
+ "integrity": "sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==",
"cpu": [
"x64"
],
@@ -2452,9 +2452,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.7.tgz",
- "integrity": "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.1.tgz",
+ "integrity": "sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==",
"cpu": [
"arm64"
],
@@ -2468,9 +2468,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.7.tgz",
- "integrity": "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.1.tgz",
+ "integrity": "sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==",
"cpu": [
"arm64"
],
@@ -2484,9 +2484,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.7.tgz",
- "integrity": "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.1.tgz",
+ "integrity": "sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==",
"cpu": [
"x64"
],
@@ -2500,9 +2500,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.7.tgz",
- "integrity": "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.1.tgz",
+ "integrity": "sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==",
"cpu": [
"x64"
],
@@ -2516,9 +2516,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.7.tgz",
- "integrity": "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.1.tgz",
+ "integrity": "sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==",
"cpu": [
"arm64"
],
@@ -2532,9 +2532,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.7.tgz",
- "integrity": "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.1.tgz",
+ "integrity": "sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==",
"cpu": [
"x64"
],
@@ -2797,9 +2797,9 @@
"license": "MIT"
},
"node_modules/@octokit/openapi-webhooks-types": {
- "version": "12.0.3",
- "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.0.3.tgz",
- "integrity": "sha512-90MF5LVHjBedwoHyJsgmaFhEN1uzXyBDRLEBe7jlTYx/fEhPAk3P3DAJsfZwC54m8hAIryosJOL+UuZHB3K3yA==",
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.1.0.tgz",
+ "integrity": "sha512-WiuzhOsiOvb7W3Pvmhf8d2C6qaLHXrWiLBP4nJ/4kydu+wpagV5Fkz9RfQwV2afYzv3PB+3xYgp4mAdNGjDprA==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-graphql": {
@@ -2915,12 +2915,12 @@
}
},
"node_modules/@octokit/webhooks": {
- "version": "14.1.3",
- "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-14.1.3.tgz",
- "integrity": "sha512-gcK4FNaROM9NjA0mvyfXl0KPusk7a1BeA8ITlYEZVQCXF5gcETTd4yhAU0Kjzd8mXwYHppzJBWgdBVpIR9wUcQ==",
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-14.2.0.tgz",
+ "integrity": "sha512-da6KbdNCV5sr1/txD896V+6W0iamFWrvVl8cHkBSPT+YlvmT3DwXa4jxZnQc+gnuTEqSWbBeoSZYTayXH9wXcw==",
"license": "MIT",
"dependencies": {
- "@octokit/openapi-webhooks-types": "12.0.3",
+ "@octokit/openapi-webhooks-types": "12.1.0",
"@octokit/request-error": "^7.0.0",
"@octokit/webhooks-methods": "^6.0.0"
},
@@ -3085,9 +3085,9 @@
}
},
"node_modules/@tailwindcss/node": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz",
- "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
+ "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3097,37 +3097,37 @@
"lightningcss": "1.30.2",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
- "tailwindcss": "4.1.17"
+ "tailwindcss": "4.1.18"
}
},
"node_modules/@tailwindcss/oxide": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz",
- "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
+ "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.17",
- "@tailwindcss/oxide-darwin-arm64": "4.1.17",
- "@tailwindcss/oxide-darwin-x64": "4.1.17",
- "@tailwindcss/oxide-freebsd-x64": "4.1.17",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.17",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.17",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.17",
- "@tailwindcss/oxide-wasm32-wasi": "4.1.17",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.17"
+ "@tailwindcss/oxide-android-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-x64": "4.1.18",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.18",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz",
- "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
+ "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
"cpu": [
"arm64"
],
@@ -3142,9 +3142,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz",
- "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
+ "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
"cpu": [
"arm64"
],
@@ -3159,9 +3159,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz",
- "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
+ "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
"cpu": [
"x64"
],
@@ -3176,9 +3176,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz",
- "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
+ "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
"cpu": [
"x64"
],
@@ -3193,9 +3193,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz",
- "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
+ "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
"cpu": [
"arm"
],
@@ -3210,9 +3210,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz",
- "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
+ "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
"cpu": [
"arm64"
],
@@ -3227,9 +3227,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz",
- "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
+ "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
"cpu": [
"arm64"
],
@@ -3244,9 +3244,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz",
- "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
+ "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
"cpu": [
"x64"
],
@@ -3261,9 +3261,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz",
- "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
+ "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
"cpu": [
"x64"
],
@@ -3278,9 +3278,9 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz",
- "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
+ "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@@ -3296,10 +3296,10 @@
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "^1.6.0",
- "@emnapi/runtime": "^1.6.0",
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
"@emnapi/wasi-threads": "^1.1.0",
- "@napi-rs/wasm-runtime": "^1.0.7",
+ "@napi-rs/wasm-runtime": "^1.1.0",
"@tybys/wasm-util": "^0.10.1",
"tslib": "^2.4.0"
},
@@ -3308,7 +3308,7 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
- "version": "1.6.0",
+ "version": "1.7.1",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -3319,7 +3319,7 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
- "version": "1.6.0",
+ "version": "1.7.1",
"dev": true,
"inBundle": true,
"license": "MIT",
@@ -3339,14 +3339,14 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
- "version": "1.0.7",
+ "version": "1.1.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "^1.5.0",
- "@emnapi/runtime": "^1.5.0",
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
"@tybys/wasm-util": "^0.10.1"
}
},
@@ -3368,9 +3368,9 @@
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz",
- "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
+ "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
"cpu": [
"arm64"
],
@@ -3385,9 +3385,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz",
- "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
+ "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
"cpu": [
"x64"
],
@@ -3402,17 +3402,17 @@
}
},
"node_modules/@tailwindcss/postcss": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.17.tgz",
- "integrity": "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz",
+ "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
- "@tailwindcss/node": "4.1.17",
- "@tailwindcss/oxide": "4.1.17",
+ "@tailwindcss/node": "4.1.18",
+ "@tailwindcss/oxide": "4.1.18",
"postcss": "^8.4.41",
- "tailwindcss": "4.1.17"
+ "tailwindcss": "4.1.18"
}
},
"node_modules/@tybys/wasm-util": {
@@ -3536,9 +3536,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.10.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
- "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
+ "version": "25.0.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
+ "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3559,9 +3559,9 @@
"license": "MIT"
},
"node_modules/@types/pg": {
- "version": "8.15.6",
- "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz",
- "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz",
+ "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3643,21 +3643,20 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz",
- "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz",
+ "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.48.0",
- "@typescript-eslint/type-utils": "8.48.0",
- "@typescript-eslint/utils": "8.48.0",
- "@typescript-eslint/visitor-keys": "8.48.0",
- "graphemer": "^1.4.0",
+ "@typescript-eslint/scope-manager": "8.51.0",
+ "@typescript-eslint/type-utils": "8.51.0",
+ "@typescript-eslint/utils": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
- "ts-api-utils": "^2.1.0"
+ "ts-api-utils": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3667,23 +3666,23 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.48.0",
+ "@typescript-eslint/parser": "^8.51.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz",
- "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz",
+ "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "8.48.0",
- "@typescript-eslint/types": "8.48.0",
- "@typescript-eslint/typescript-estree": "8.48.0",
- "@typescript-eslint/visitor-keys": "8.48.0",
+ "@typescript-eslint/scope-manager": "8.51.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0",
"debug": "^4.3.4"
},
"engines": {
@@ -3699,14 +3698,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz",
- "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz",
+ "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.48.0",
- "@typescript-eslint/types": "^8.48.0",
+ "@typescript-eslint/tsconfig-utils": "^8.51.0",
+ "@typescript-eslint/types": "^8.51.0",
"debug": "^4.3.4"
},
"engines": {
@@ -3721,14 +3720,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz",
- "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz",
+ "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.48.0",
- "@typescript-eslint/visitor-keys": "8.48.0"
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3739,9 +3738,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz",
- "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz",
+ "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3756,17 +3755,17 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz",
- "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz",
+ "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.48.0",
- "@typescript-eslint/typescript-estree": "8.48.0",
- "@typescript-eslint/utils": "8.48.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0",
+ "@typescript-eslint/utils": "8.51.0",
"debug": "^4.3.4",
- "ts-api-utils": "^2.1.0"
+ "ts-api-utils": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3781,9 +3780,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz",
- "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz",
+ "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3795,21 +3794,21 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz",
- "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz",
+ "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.48.0",
- "@typescript-eslint/tsconfig-utils": "8.48.0",
- "@typescript-eslint/types": "8.48.0",
- "@typescript-eslint/visitor-keys": "8.48.0",
+ "@typescript-eslint/project-service": "8.51.0",
+ "@typescript-eslint/tsconfig-utils": "8.51.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0",
"debug": "^4.3.4",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"tinyglobby": "^0.2.15",
- "ts-api-utils": "^2.1.0"
+ "ts-api-utils": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3823,16 +3822,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz",
- "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz",
+ "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.48.0",
- "@typescript-eslint/types": "8.48.0",
- "@typescript-eslint/typescript-estree": "8.48.0"
+ "@typescript-eslint/scope-manager": "8.51.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3847,13 +3846,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz",
- "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz",
+ "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/types": "8.51.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -4614,7 +4613,6 @@
"version": "2.8.28",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz",
"integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==",
- "dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
@@ -5388,9 +5386,9 @@
}
},
"node_modules/enhanced-resolve": {
- "version": "5.18.3",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
- "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "version": "5.18.4",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
+ "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5615,9 +5613,9 @@
}
},
"node_modules/eslint": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
- "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -5628,7 +5626,7 @@
"@eslint/config-helpers": "^0.4.2",
"@eslint/core": "^0.17.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.1",
+ "@eslint/js": "9.39.2",
"@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -5676,13 +5674,13 @@
}
},
"node_modules/eslint-config-next": {
- "version": "16.0.6",
- "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.0.6.tgz",
- "integrity": "sha512-nx0Z2S50TlcSQ2RtyULCff5tlKTwqF/ICh3U9s8C/e2aRXAm1Ootdb7BEHGZmejtJSgsFq8PVFdlWy8BHiz2pg==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.1.tgz",
+ "integrity": "sha512-55nTpVWm3qeuxoQKLOjQVciKZJUphKrNM0fCcQHAIOGl6VFXgaqeMfv0aKJhs7QtcnlAPhNVqsqRfRjeKBPIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@next/eslint-plugin-next": "16.0.6",
+ "@next/eslint-plugin-next": "16.1.1",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
@@ -6378,9 +6376,9 @@
}
},
"node_modules/fastq": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
- "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -6780,13 +6778,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
@@ -9052,13 +9043,14 @@
"license": "MIT"
},
"node_modules/next": {
- "version": "16.0.7",
- "resolved": "https://registry.npmjs.org/next/-/next-16.0.7.tgz",
- "integrity": "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/next/-/next-16.1.1.tgz",
+ "integrity": "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==",
"license": "MIT",
"dependencies": {
- "@next/env": "16.0.7",
+ "@next/env": "16.1.1",
"@swc/helpers": "0.5.15",
+ "baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
"styled-jsx": "5.1.6"
@@ -9070,14 +9062,14 @@
"node": ">=20.9.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "16.0.7",
- "@next/swc-darwin-x64": "16.0.7",
- "@next/swc-linux-arm64-gnu": "16.0.7",
- "@next/swc-linux-arm64-musl": "16.0.7",
- "@next/swc-linux-x64-gnu": "16.0.7",
- "@next/swc-linux-x64-musl": "16.0.7",
- "@next/swc-win32-arm64-msvc": "16.0.7",
- "@next/swc-win32-x64-msvc": "16.0.7",
+ "@next/swc-darwin-arm64": "16.1.1",
+ "@next/swc-darwin-x64": "16.1.1",
+ "@next/swc-linux-arm64-gnu": "16.1.1",
+ "@next/swc-linux-arm64-musl": "16.1.1",
+ "@next/swc-linux-x64-gnu": "16.1.1",
+ "@next/swc-linux-x64-musl": "16.1.1",
+ "@next/swc-win32-arm64-msvc": "16.1.1",
+ "@next/swc-win32-x64-msvc": "16.1.1",
"sharp": "^0.34.4"
},
"peerDependencies": {
@@ -9253,9 +9245,9 @@
}
},
"node_modules/npm": {
- "version": "11.6.4",
- "resolved": "https://registry.npmjs.org/npm/-/npm-11.6.4.tgz",
- "integrity": "sha512-ERjKtGoFpQrua/9bG0+h3xiv/4nVdGViCjUYA1AmlV24fFvfnSB7B7dIfZnySQ1FDLd0ZVrWPsLLp78dCtJdRQ==",
+ "version": "11.7.0",
+ "resolved": "https://registry.npmjs.org/npm/-/npm-11.7.0.tgz",
+ "integrity": "sha512-wiCZpv/41bIobCoJ31NStIWKfAxxYyD1iYnWCtiyns8s5v3+l8y0HCP/sScuH6B5+GhIfda4HQKiqeGZwJWhFw==",
"bundleDependencies": [
"@isaacs/string-locale-compare",
"@npmcli/arborist",
@@ -9334,8 +9326,8 @@
],
"dependencies": {
"@isaacs/string-locale-compare": "^1.1.0",
- "@npmcli/arborist": "^9.1.8",
- "@npmcli/config": "^10.4.4",
+ "@npmcli/arborist": "^9.1.9",
+ "@npmcli/config": "^10.4.5",
"@npmcli/fs": "^5.0.0",
"@npmcli/map-workspaces": "^5.0.3",
"@npmcli/metavuln-calculator": "^9.0.3",
@@ -9360,11 +9352,11 @@
"is-cidr": "^6.0.1",
"json-parse-even-better-errors": "^5.0.0",
"libnpmaccess": "^10.0.3",
- "libnpmdiff": "^8.0.11",
- "libnpmexec": "^10.1.10",
- "libnpmfund": "^7.0.11",
+ "libnpmdiff": "^8.0.12",
+ "libnpmexec": "^10.1.11",
+ "libnpmfund": "^7.0.12",
"libnpmorg": "^8.0.1",
- "libnpmpack": "^9.0.11",
+ "libnpmpack": "^9.0.12",
"libnpmpublish": "^11.1.3",
"libnpmsearch": "^9.0.1",
"libnpmteam": "^8.0.2",
@@ -9472,7 +9464,7 @@
}
},
"node_modules/npm/node_modules/@npmcli/arborist": {
- "version": "9.1.8",
+ "version": "9.1.9",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -9518,7 +9510,7 @@
}
},
"node_modules/npm/node_modules/@npmcli/config": {
- "version": "10.4.4",
+ "version": "10.4.5",
"inBundle": true,
"license": "ISC",
"dependencies": {
@@ -10256,11 +10248,11 @@
}
},
"node_modules/npm/node_modules/libnpmdiff": {
- "version": "8.0.11",
+ "version": "8.0.12",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.8",
+ "@npmcli/arborist": "^9.1.9",
"@npmcli/installed-package-contents": "^4.0.0",
"binary-extensions": "^3.0.0",
"diff": "^8.0.2",
@@ -10274,11 +10266,11 @@
}
},
"node_modules/npm/node_modules/libnpmexec": {
- "version": "10.1.10",
+ "version": "10.1.11",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.8",
+ "@npmcli/arborist": "^9.1.9",
"@npmcli/package-json": "^7.0.0",
"@npmcli/run-script": "^10.0.0",
"ci-info": "^4.0.0",
@@ -10296,11 +10288,11 @@
}
},
"node_modules/npm/node_modules/libnpmfund": {
- "version": "7.0.11",
+ "version": "7.0.12",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.8"
+ "@npmcli/arborist": "^9.1.9"
},
"engines": {
"node": "^20.17.0 || >=22.9.0"
@@ -10319,11 +10311,11 @@
}
},
"node_modules/npm/node_modules/libnpmpack": {
- "version": "9.0.11",
+ "version": "9.0.12",
"inBundle": true,
"license": "ISC",
"dependencies": {
- "@npmcli/arborist": "^9.1.8",
+ "@npmcli/arborist": "^9.1.9",
"@npmcli/run-script": "^10.0.0",
"npm-package-arg": "^13.0.0",
"pacote": "^21.0.2"
@@ -12172,9 +12164,9 @@
"license": "MIT"
},
"node_modules/react": {
- "version": "19.2.0",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
- "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
+ "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
"peer": true,
"engines": {
@@ -12182,22 +12174,22 @@
}
},
"node_modules/react-dom": {
- "version": "19.2.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
- "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
- "react": "^19.2.0"
+ "react": "^19.2.3"
}
},
"node_modules/react-is": {
- "version": "19.2.0",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz",
- "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==",
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz",
+ "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==",
"license": "MIT"
},
"node_modules/react-tabs": {
@@ -13438,13 +13430,13 @@
}
},
"node_modules/swr": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.7.tgz",
- "integrity": "sha512-ZEquQ82QvalqTxhBVv/DlAg2mbmUjF4UgpPg9wwk4ufb9rQnZXh1iKyyKBqV6bQGu1Ie7L1QwSYO07qFIa1p+g==",
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.8.tgz",
+ "integrity": "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
- "use-sync-external-store": "^1.4.0"
+ "use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -13467,9 +13459,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.1.17",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
- "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
+ "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
"dev": true,
"license": "MIT"
},
@@ -13633,9 +13625,9 @@
"license": "MIT"
},
"node_modules/ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz",
+ "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -13646,9 +13638,9 @@
}
},
"node_modules/ts-jest": {
- "version": "29.4.5",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz",
- "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==",
+ "version": "29.4.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
+ "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -13883,16 +13875,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.48.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz",
- "integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz",
+ "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.48.0",
- "@typescript-eslint/parser": "8.48.0",
- "@typescript-eslint/typescript-estree": "8.48.0",
- "@typescript-eslint/utils": "8.48.0"
+ "@typescript-eslint/eslint-plugin": "8.51.0",
+ "@typescript-eslint/parser": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0",
+ "@typescript-eslint/utils": "8.51.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -14479,9 +14471,9 @@
}
},
"node_modules/zod": {
- "version": "4.1.13",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
- "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.4.tgz",
+ "integrity": "sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==",
"license": "MIT",
"peer": true,
"funding": {
diff --git a/package.json b/package.json
index 7a8667be..3a04e48a 100644
--- a/package.json
+++ b/package.json
@@ -23,51 +23,51 @@
"@fortawesome/free-regular-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0",
"@fortawesome/react-fontawesome": "^3.1.1",
- "@mui/icons-material": "^7.3.5",
+ "@mui/icons-material": "^7.3.6",
"@mui/material": "^7.0.1",
"@octokit/auth-app": "^8.1.2",
"@octokit/core": "^7.0.6",
- "@octokit/webhooks": "~14.1.3",
+ "@octokit/webhooks": "~14.2.0",
"core-js": "^3.47.0",
"encoding": "^0.1.13",
"figma-squircle": "^1.1.0",
"install": "^0.13.0",
"ioredis": "^5.8.2",
"mobx": "^6.15.0",
- "next": "16.0.7",
+ "next": "16.1.1",
"next-auth": "^5.0.0-beta.30",
- "npm": "^11.6.4",
+ "npm": "^11.7.0",
"nprogress": "^0.2.0",
"octokit": "^5.0.5",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
"redis-semaphore": "^5.6.2",
"redoc": "^2.5.2",
"sharp": "^0.34.2",
"styled-components": "^6.1.19",
- "swr": "^2.3.7",
+ "swr": "^2.3.8",
"usehooks-ts": "^3.1.1",
"yaml": "^2.8.2",
- "zod": "^4.1.13"
+ "zod": "^4.3.4"
},
"devDependencies": {
"@auth/pg-adapter": "^1.11.1",
- "@tailwindcss/postcss": "^4.1.17",
+ "@tailwindcss/postcss": "^4.1.18",
"@types/jest": "^30.0.0",
- "@types/node": "^24.10.1",
+ "@types/node": "^25.0.3",
"@types/nprogress": "^0.2.3",
- "@types/pg": "^8.15.6",
- "@typescript-eslint/eslint-plugin": "^8.48.0",
+ "@types/pg": "^8.16.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
- "@typescript-eslint/parser": "^8.48.0",
- "typescript-eslint": "^8.48.0",
- "eslint": "^9.39.1",
- "eslint-config-next": "^16.0.6",
+ "@typescript-eslint/eslint-plugin": "^8.51.0",
+ "@typescript-eslint/parser": "^8.51.0",
+ "typescript-eslint": "^8.51.0",
+ "eslint": "^9.39.2",
+ "eslint-config-next": "^16.1.1",
"pg": "^8.16.3",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.4",
- "ts-jest": "^29.4.5",
+ "ts-jest": "^29.4.6",
"typescript": "^5.9.3"
}
}
diff --git a/src/app/(authed)/(project-doc)/[...slug]/page.tsx b/src/app/(authed)/(project-doc)/[...slug]/page.tsx
index 66bb1423..02473315 100644
--- a/src/app/(authed)/(project-doc)/[...slug]/page.tsx
+++ b/src/app/(authed)/(project-doc)/[...slug]/page.tsx
@@ -36,8 +36,8 @@ export default function Page() {
{project && (!version || !specification) && !refreshing &&
}
- {refreshing && // project data is currently being fetched - show loading indicator
-
+ {!project && !version && !specification && refreshing &&
+
}
{!project && !refreshing && }
>
diff --git a/src/app/api/diff/[owner]/[repository]/[...path]/route.ts b/src/app/api/diff/[owner]/[repository]/[...path]/route.ts
new file mode 100644
index 00000000..7486ce4d
--- /dev/null
+++ b/src/app/api/diff/[owner]/[repository]/[...path]/route.ts
@@ -0,0 +1,45 @@
+import { NextRequest, NextResponse } from "next/server"
+import { session, diffCalculator } from "@/composition"
+import { makeUnauthenticatedAPIErrorResponse } from "@/common"
+
+interface GetDiffParams {
+ owner: string
+ repository: string
+ path: string[]
+}
+
+export async function GET(req: NextRequest, { params }: { params: Promise }) {
+ const isAuthenticated = await session.getIsAuthenticated()
+ if (!isAuthenticated) {
+ return makeUnauthenticatedAPIErrorResponse()
+ }
+
+ const { path: paramsPath, owner, repository } = await params
+ const path = paramsPath.join("/")
+
+ const toRef = req.nextUrl.searchParams.get("to")
+ const baseRefOid = req.nextUrl.searchParams.get("baseRefOid")
+
+ if (!toRef) {
+ return NextResponse.json({ error: "Missing 'to' parameter" }, { status: 400 })
+ }
+
+ if (!baseRefOid) {
+ return NextResponse.json({ error: "Missing 'baseRefOid' parameter" }, { status: 400 })
+ }
+
+ try {
+ const diff = await diffCalculator.calculateDiff(
+ owner,
+ repository,
+ path,
+ baseRefOid,
+ toRef
+ )
+
+ return NextResponse.json(diff)
+ } catch (error) {
+ const message = error instanceof Error ? error.message : "Unknown error while calculating diff"
+ return NextResponse.json({ error: message }, { status: 500 })
+ }
+}
diff --git a/src/common/github/GitHubClient.ts b/src/common/github/GitHubClient.ts
index 3ecf10e1..6a47674f 100644
--- a/src/common/github/GitHubClient.ts
+++ b/src/common/github/GitHubClient.ts
@@ -8,6 +8,8 @@ import IGitHubClient, {
GetPullRequestFilesRequest,
AddCommentToPullRequestRequest,
UpdatePullRequestCommentRequest,
+ CompareCommitsRequest,
+ CompareCommitsResponse,
RepositoryContent,
PullRequestComment,
PullRequestFile
@@ -119,4 +121,15 @@ export default class GitHubClient implements IGitHubClient {
body: request.body
})
}
+
+ async compareCommitsWithBasehead(request: CompareCommitsRequest): Promise {
+ const oauthToken = await this.oauthTokenDataSource.getOAuthToken()
+ const octokit = new Octokit({ auth: oauthToken.accessToken })
+ const response = await octokit.rest.repos.compareCommitsWithBasehead({
+ owner: request.repositoryOwner,
+ repo: request.repositoryName,
+ basehead: `${request.baseRefOid}...${request.headRefOid}`
+ })
+ return { mergeBaseSha: response.data.merge_base_commit.sha }
+ }
}
diff --git a/src/common/github/IGitHubClient.ts b/src/common/github/IGitHubClient.ts
index a739f69c..601de84b 100644
--- a/src/common/github/IGitHubClient.ts
+++ b/src/common/github/IGitHubClient.ts
@@ -70,6 +70,17 @@ export type UpdatePullRequestCommentRequest = {
readonly body: string
}
+export type CompareCommitsRequest = {
+ readonly repositoryOwner: string
+ readonly repositoryName: string
+ readonly baseRefOid: string
+ readonly headRefOid: string
+}
+
+export type CompareCommitsResponse = {
+ readonly mergeBaseSha: string
+}
+
export default interface IGitHubClient {
graphql(request: GraphQLQueryRequest): Promise
getRepositoryContent(request: GetRepositoryContentRequest): Promise
@@ -77,4 +88,5 @@ export default interface IGitHubClient {
getPullRequestComments(request: GetPullRequestCommentsRequest): Promise
addCommentToPullRequest(request: AddCommentToPullRequestRequest): Promise
updatePullRequestComment(request: UpdatePullRequestCommentRequest): Promise
+ compareCommitsWithBasehead(request: CompareCommitsRequest): Promise
}
diff --git a/src/common/github/OAuthTokenRefreshingGitHubClient.ts b/src/common/github/OAuthTokenRefreshingGitHubClient.ts
index 48b6373e..ad33dd8a 100644
--- a/src/common/github/OAuthTokenRefreshingGitHubClient.ts
+++ b/src/common/github/OAuthTokenRefreshingGitHubClient.ts
@@ -7,6 +7,8 @@ import IGitHubClient, {
AddCommentToPullRequestRequest,
UpdatePullRequestCommentRequest,
GetPullRequestFilesRequest,
+ CompareCommitsRequest,
+ CompareCommitsResponse,
RepositoryContent,
PullRequestComment,
PullRequestFile
@@ -76,7 +78,13 @@ export default class OAuthTokenRefreshingGitHubClient implements IGitHubClient {
return await this.gitHubClient.updatePullRequestComment(request)
})
}
-
+
+ async compareCommitsWithBasehead(request: CompareCommitsRequest): Promise {
+ return await this.send(async () => {
+ return await this.gitHubClient.compareCommitsWithBasehead(request)
+ })
+ }
+
private async send(fn: () => Promise): Promise {
const oauthToken = await this.oauthTokenDataSource.getOAuthToken()
try {
diff --git a/src/common/github/RepoRestrictedGitHubClient.ts b/src/common/github/RepoRestrictedGitHubClient.ts
index 558492f3..eb15dd5c 100644
--- a/src/common/github/RepoRestrictedGitHubClient.ts
+++ b/src/common/github/RepoRestrictedGitHubClient.ts
@@ -1,15 +1,17 @@
-import {
- IGitHubClient,
- AddCommentToPullRequestRequest,
- GetPullRequestCommentsRequest,
- GetPullRequestFilesRequest,
- GetRepositoryContentRequest,
- GraphQLQueryRequest,
- GraphQlQueryResponse,
- PullRequestComment,
- PullRequestFile,
- RepositoryContent,
- UpdatePullRequestCommentRequest
+import {
+ IGitHubClient,
+ AddCommentToPullRequestRequest,
+ GetPullRequestCommentsRequest,
+ GetPullRequestFilesRequest,
+ GetRepositoryContentRequest,
+ GraphQLQueryRequest,
+ GraphQlQueryResponse,
+ PullRequestComment,
+ PullRequestFile,
+ RepositoryContent,
+ UpdatePullRequestCommentRequest,
+ CompareCommitsRequest,
+ CompareCommitsResponse
} from "@/common";
export class RepoRestrictedGitHubClient implements IGitHubClient {
@@ -54,6 +56,11 @@ export class RepoRestrictedGitHubClient implements IGitHubClient {
return this.gitHubClient.updatePullRequestComment(request);
}
+ compareCommitsWithBasehead(request: CompareCommitsRequest): Promise {
+ if (!this.isRepositoryNameValid(request.repositoryName)) return Promise.reject(new Error("Invalid repository name"));
+ return this.gitHubClient.compareCommitsWithBasehead(request);
+ }
+
private isRepositoryNameValid(repositoryName: string): boolean {
return repositoryName.endsWith(this.repositoryNameSuffix);
}
diff --git a/src/common/ui/HighlightText.tsx b/src/common/ui/HighlightText.tsx
index db6570e1..67ec7fe8 100644
--- a/src/common/ui/HighlightText.tsx
+++ b/src/common/ui/HighlightText.tsx
@@ -1,5 +1,5 @@
"use client"
-import React from "react"
+
import { SxProps, Typography, TypographyVariant } from "@mui/material"
import styled from "@emotion/styled"
diff --git a/src/common/ui/SpacedList.tsx b/src/common/ui/SpacedList.tsx
index 313acaef..1aa2b11a 100644
--- a/src/common/ui/SpacedList.tsx
+++ b/src/common/ui/SpacedList.tsx
@@ -1,21 +1,25 @@
import React from "react"
import { List, Box, SxProps } from "@mui/material"
-const SpacedList = ({
- itemSpacing,
- sx,
- children
-}: {
+interface SpacedListProps {
itemSpacing: number
sx?: SxProps
children?: React.ReactNode
-}) => {
+}
+
+const SpacedList = ({ itemSpacing, sx, children }: SpacedListProps) => {
+ const childrenArray = React.Children.toArray(children)
+ const lastIndex = childrenArray.length - 1
+
return (
-
- {React.Children.map(children, (child, idx) => (
-
+
+ {childrenArray.map((child, idx) => (
+
{child}
))}
diff --git a/src/composition.ts b/src/composition.ts
index 10f4e504..8187b966 100644
--- a/src/composition.ts
+++ b/src/composition.ts
@@ -53,6 +53,8 @@ import {
import { RepoRestrictedGitHubClient } from "./common/github/RepoRestrictedGitHubClient"
import RsaEncryptionService from "./features/encrypt/EncryptionService"
import RemoteConfigEncoder from "./features/projects/domain/RemoteConfigEncoder"
+import { OasDiffCalculator } from "./features/diff/data/OasDiffCalculator"
+import { IOasDiffCalculator } from "./features/diff/data/IOasDiffCalculator"
const gitHubAppCredentials = {
appId: env.getOrThrow("GITHUB_APP_ID"),
@@ -230,4 +232,6 @@ export const gitHubHookHandler = new GitHubHookHandler({
})
})
})
-})
\ No newline at end of file
+})
+
+export const diffCalculator: IOasDiffCalculator = new OasDiffCalculator(gitHubClient)
diff --git a/src/features/diff/data/IOasDiffCalculator.ts b/src/features/diff/data/IOasDiffCalculator.ts
new file mode 100644
index 00000000..92261518
--- /dev/null
+++ b/src/features/diff/data/IOasDiffCalculator.ts
@@ -0,0 +1,19 @@
+import { DiffChange } from "../domain/DiffChange"
+
+export interface DiffResult {
+ from: string
+ to: string
+ changes: DiffChange[]
+ error?: string | null
+ isNewFile?: boolean
+}
+
+export interface IOasDiffCalculator {
+ calculateDiff(
+ owner: string,
+ repository: string,
+ path: string,
+ baseRefOid: string,
+ toRef: string
+ ): Promise
+}
diff --git a/src/features/diff/data/OasDiffCalculator.ts b/src/features/diff/data/OasDiffCalculator.ts
new file mode 100644
index 00000000..a15fe67c
--- /dev/null
+++ b/src/features/diff/data/OasDiffCalculator.ts
@@ -0,0 +1,119 @@
+import { execFileSync } from "child_process"
+import { DiffChange } from "@/features/diff/domain/DiffChange"
+import type { IGitHubClient } from "@/common"
+import { DiffResult, IOasDiffCalculator } from "./IOasDiffCalculator"
+
+/**
+ * Validates that a URL originates from a trusted GitHub domain.
+ * @param url - The URL to validate
+ * @returns true if the URL is from a trusted GitHub domain, false otherwise
+ */
+function isValidGitHubUrl(url: string): boolean {
+ try {
+ const parsedUrl = new URL(url)
+ const trustedDomains = [
+ "raw.githubusercontent.com",
+ "github.com",
+ "api.github.com"
+ ]
+ return trustedDomains.includes(parsedUrl.hostname)
+ } catch {
+ return false
+ }
+}
+
+export class OasDiffCalculator implements IOasDiffCalculator {
+ constructor(private readonly githubClient: IGitHubClient) {}
+
+ async calculateDiff(
+ owner: string,
+ repository: string,
+ path: string,
+ baseRefOid: string,
+ toRef: string
+ ): Promise {
+ // Calculate merge-base for diff
+ const mergeBaseResult = await this.githubClient.compareCommitsWithBasehead({
+ repositoryOwner: owner,
+ repositoryName: repository,
+ baseRefOid: baseRefOid,
+ headRefOid: toRef
+ })
+ const fromRef = mergeBaseResult.mergeBaseSha
+
+ // If comparing same refs, return empty diff
+ if (fromRef === toRef) {
+ return {
+ from: fromRef,
+ to: toRef,
+ changes: []
+ }
+ }
+
+ // Fetch spec content from both refs
+ let spec1
+
+ try {
+ spec1 = await this.githubClient.getRepositoryContent({
+ repositoryOwner: owner,
+ repositoryName: repository,
+ path: path,
+ ref: fromRef
+ })
+ } catch {
+ // File doesn't exist in base ref - this is a new file
+ return {
+ from: fromRef,
+ to: toRef,
+ changes: [],
+ isNewFile: true
+ }
+ }
+
+ const spec2 = await this.githubClient.getRepositoryContent({
+ repositoryOwner: owner,
+ repositoryName: repository,
+ path: path,
+ ref: toRef
+ })
+
+ // Validate URLs originate from GitHub
+ if (!isValidGitHubUrl(spec1.downloadURL)) {
+ throw new Error(
+ `Invalid URL for base spec: ${spec1.downloadURL}. URL must originate from a trusted GitHub domain.`
+ )
+ }
+ if (!isValidGitHubUrl(spec2.downloadURL)) {
+ throw new Error(
+ `Invalid URL for head spec: ${spec2.downloadURL}. URL must originate from a trusted GitHub domain.`
+ )
+ }
+
+ // Execute oasdiff
+ const diffData = (() => {
+ try {
+ const result = execFileSync(
+ "oasdiff",
+ ["changelog", "--format", "json", spec1.downloadURL, spec2.downloadURL],
+ { encoding: "utf8" }
+ )
+ return JSON.parse(result) as DiffChange[]
+ } catch (error) {
+ const errorMessage =
+ error instanceof Error
+ ? error.message
+ : "Unknown error when executing OpenAPI diff command"
+ throw new Error(
+ `Failed to execute OpenAPI diff tool. Please ensure "oasdiff" is installed and available in PATH. (${errorMessage})`
+ )
+ }
+ })()
+
+ return {
+ from: fromRef,
+ to: toRef,
+ changes: diffData,
+ error: null
+ }
+ }
+}
diff --git a/src/features/diff/domain/DiffChange.ts b/src/features/diff/domain/DiffChange.ts
new file mode 100644
index 00000000..83c61036
--- /dev/null
+++ b/src/features/diff/domain/DiffChange.ts
@@ -0,0 +1,14 @@
+import { z } from "zod"
+
+export const DiffChangeSchema = z.object({
+ id: z.string(),
+ text: z.string(),
+ level: z.number(),
+ operation: z.string().optional(),
+ operationId: z.string().optional(),
+ path: z.string().optional(),
+ source: z.string().optional(),
+ section: z.string().optional(),
+})
+
+export type DiffChange = z.infer
diff --git a/src/features/docs/navigation/index.ts b/src/features/docs/navigation/index.ts
new file mode 100644
index 00000000..4a871967
--- /dev/null
+++ b/src/features/docs/navigation/index.ts
@@ -0,0 +1 @@
+export { scrollToOperation } from "./scrollToOperation"
diff --git a/src/features/docs/navigation/scrollToOperation.ts b/src/features/docs/navigation/scrollToOperation.ts
new file mode 100644
index 00000000..11abac83
--- /dev/null
+++ b/src/features/docs/navigation/scrollToOperation.ts
@@ -0,0 +1,108 @@
+import { DocumentationVisualizer } from "@/features/settings/domain"
+
+/**
+ * Generates a SwaggerUI-compatible operationId from HTTP method and path.
+ * SwaggerUI generates IDs in the format: {method}_{path_normalized}
+ * where path is normalized by replacing / with _ and {param} with _param_
+ */
+function generateSwaggerOperationId(method: string, path: string): string {
+ const normalizedPath = path
+ .replace(/\//g, "_")
+ .replace(/\{([^}]+)\}/g, "_$1_")
+ return `${method.toLowerCase()}${normalizedPath}`
+}
+
+/**
+ * Finds and scrolls to a SwaggerUI operation element.
+ * SwaggerUI elements have IDs in the format: operations-{tag}-{operationId}
+ * Since we may not know the tag, we search for elements containing the operationId.
+ */
+function scrollToSwaggerOperation(operationId: string): boolean {
+ const block = Array.from(document.querySelectorAll('[id^="operations-"]'))
+ .find(el => el.id.endsWith(`-${operationId}`))
+
+ if (!block) return false
+
+ block.scrollIntoView({ behavior: "smooth", block: "start" })
+
+ // Only expand if not already open (SwaggerUI adds is-open class to the block itself)
+ if (!block.classList.contains("is-open")) {
+ const button = block.querySelector(".opblock-summary-control")
+ if (button instanceof HTMLElement) {
+ button.click()
+ }
+ }
+
+ return true
+}
+
+/**
+ * Scrolls to an operation in Redocly by setting the hash on the iframe.
+ */
+function scrollToRedoclyOperation(operationId?: string, method?: string, path?: string): void {
+ const iframe = document.querySelector("iframe")
+ if (!iframe?.contentWindow) {
+ return
+ }
+
+ // try direct operationId link first
+ if (operationId) {
+ iframe.contentWindow.location.hash = `operation/${operationId}`
+ return
+ }
+
+ if (!method || !path) {
+ return
+ }
+
+ // fallback to method+path matching
+ const encodedPath = path.replace(/\//g, "~1")
+ const links = Array.from(
+ iframe.contentDocument?.querySelectorAll(`a[href*="${encodedPath}"]`) ?? []
+ )
+
+ for (const link of links) {
+ const href = link.getAttribute("href")
+ if (href?.includes(`/${method.toLowerCase()}`) || href?.endsWith(`/${method.toLowerCase()}`)) {
+ const hash = href.startsWith("#") ? href.substring(1) : href
+ iframe.contentWindow.location.hash = hash
+ return
+ }
+ }
+}
+
+/**
+ * Scrolls to an operation in the documentation viewer.
+ * Each visualizer has its own mechanism for deep linking.
+ */
+export function scrollToOperation(
+ visualizer: DocumentationVisualizer,
+ operationId?: string,
+ method?: string,
+ path?: string
+): void {
+ if (!operationId && (!method || !path)) {
+ return
+ }
+
+ switch (visualizer) {
+ case DocumentationVisualizer.SWAGGER: {
+ // If operationId is not provided, try to generate it from method+path
+ const swaggerOpId = operationId ?? (method && path ? generateSwaggerOperationId(method, path) : undefined)
+ if (swaggerOpId) {
+ scrollToSwaggerOperation(swaggerOpId)
+ }
+ break
+ }
+
+ case DocumentationVisualizer.STOPLIGHT:
+ if (operationId) {
+ window.location.hash = `/operations/${operationId}`
+ }
+ break
+
+ case DocumentationVisualizer.REDOCLY:
+ scrollToRedoclyOperation(operationId, method, path)
+ break
+ }
+}
diff --git a/src/features/projects/data/GitHubProjectDataSource.ts b/src/features/projects/data/GitHubProjectDataSource.ts
index 17086513..9976ac45 100644
--- a/src/features/projects/data/GitHubProjectDataSource.ts
+++ b/src/features/projects/data/GitHubProjectDataSource.ts
@@ -1,3 +1,4 @@
+import { createHash } from "crypto"
import { IEncryptionService } from "@/features/encrypt/EncryptionService"
import {
Project,
@@ -122,6 +123,7 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
const specifications = ref.files.filter(file => {
return this.isOpenAPISpecification(file.name)
}).map(file => {
+ const isFileChanged = ref.changedFiles?.includes(file.name) ?? false
return {
id: file.name,
name: file.name,
@@ -131,7 +133,17 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
path: file.name,
ref: ref.id
}),
- editURL: `https://github.com/${ownerName}/${repositoryName}/edit/${ref.name}/${file.name}`,
+ editURL: `https://github.com/${ownerName}/${repositoryName}/edit/${ref.name}/${encodeURIComponent(file.name)}`,
+ diffURL: isFileChanged ? this.getGitHubDiffURL({
+ ownerName,
+ repositoryName,
+ path: file.name,
+ baseRefOid: ref.baseRefOid,
+ headRefOid: ref.id
+ }) : undefined,
+ diffBaseBranch: isFileChanged ? ref.baseRef : undefined,
+ diffBaseOid: isFileChanged ? ref.baseRefOid : undefined,
+ diffPrUrl: isFileChanged && ref.prNumber ? `https://github.com/${ownerName}/${repositoryName}/pull/${ref.prNumber}` : undefined,
isDefault: false // initial value
}
}).sort((a, b) => a.name.localeCompare(b.name))
@@ -140,7 +152,7 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
name: ref.name,
specifications: specifications,
url: `https://github.com/${ownerName}/${repositoryName}/tree/${ref.name}`,
- isDefault: isDefaultRef || false
+ isDefault: isDefaultRef || false,
}
}
@@ -161,7 +173,29 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
path: string
ref: string
}): string {
- return `/api/blob/${ownerName}/${repositoryName}/${path}?ref=${ref}`
+ const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/')
+ return `/api/blob/${ownerName}/${repositoryName}/${encodedPath}?ref=${ref}`
+ }
+
+ private getGitHubDiffURL({
+ ownerName,
+ repositoryName,
+ path,
+ baseRefOid,
+ headRefOid
+ }: {
+ ownerName: string;
+ repositoryName: string;
+ path: string;
+ baseRefOid: string | undefined;
+ headRefOid: string }
+ ): string | undefined {
+ if (!baseRefOid) {
+ return undefined
+ } else {
+ const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/')
+ return `/api/diff/${ownerName}/${repositoryName}/${encodedPath}?baseRefOid=${baseRefOid}&to=${headRefOid}`
+ }
}
private addRemoteVersions(
@@ -185,11 +219,14 @@ export default class GitHubProjectDataSource implements IProjectDataSource {
};
const encodedRemoteConfig = this.remoteConfigEncoder.encode(remoteConfig);
+ // 16 hex chars (64 bits) - sufficient for change detection, not cryptographic security
+ const configHash = createHash("sha256").update(JSON.stringify(remoteConfig)).digest("hex").slice(0, 16);
return {
id: this.makeURLSafeID((e.id || e.name).toLowerCase()),
name: e.name,
url: `/api/remotes/${encodedRemoteConfig}`,
+ urlHash: configHash,
isDefault: false // initial value
};
})
diff --git a/src/features/projects/data/GitHubRepositoryDataSource.ts b/src/features/projects/data/GitHubRepositoryDataSource.ts
index 569a4bd1..6a776e34 100644
--- a/src/features/projects/data/GitHubRepositoryDataSource.ts
+++ b/src/features/projects/data/GitHubRepositoryDataSource.ts
@@ -46,6 +46,14 @@ type GraphQLGitHubRepositoryRef = {
}
}
+type GraphQLPullRequest = {
+ readonly number: number
+ readonly headRefName: string
+ readonly baseRefName: string
+ readonly baseRefOid: string
+ readonly changedFiles: string[]
+}
+
export default class GitHubProjectDataSource implements IGitHubRepositoryDataSource {
private readonly loginsDataSource: IGitHubLoginDataSource
private readonly graphQlClient: IGitHubGraphQLClient
@@ -99,9 +107,34 @@ export default class GitHubProjectDataSource implements IGitHubRepositoryDataSou
return !alreadyAdded
})
})
- .then(repositories => {
+ .then(async repositories => {
+ // Fetch PRs for all repositories in a single query
+ const allPullRequests = await this.getOpenPullRequestsForRepositories(
+ repositories.map(repo => ({
+ owner: repo.owner.login,
+ name: repo.name
+ }))
+ )
+
// Map from the internal model to the public model.
return repositories.map(repository => {
+ const repoKey = `${repository.owner.login}/${repository.name}`
+ const pullRequests = allPullRequests.get(repoKey) || new Map()
+
+ const branches = repository.branches.edges.map(branch => {
+ const pr = pullRequests.get(branch.node.name)
+
+ return {
+ id: branch.node.target.oid,
+ name: branch.node.name,
+ baseRef: pr?.baseRefName,
+ baseRefOid: pr?.baseRefOid,
+ prNumber: pr?.number,
+ files: branch.node.target.tree.entries,
+ changedFiles: pr?.changedFiles
+ }
+ })
+
return {
name: repository.name,
owner: repository.owner.login,
@@ -111,13 +144,7 @@ export default class GitHubProjectDataSource implements IGitHubRepositoryDataSou
},
configYml: repository.configYml,
configYaml: repository.configYaml,
- branches: repository.branches.edges.map(branch => {
- return {
- id: branch.node.target.oid,
- name: branch.node.name,
- files: branch.node.target.tree.entries
- }
- }),
+ branches: branches,
tags: repository.tags.edges.map(branch => {
return {
id: branch.node.target.oid,
@@ -130,6 +157,85 @@ export default class GitHubProjectDataSource implements IGitHubRepositoryDataSou
})
}
+ private async getOpenPullRequestsForRepositories(
+ repositories: Array<{ owner: string, name: string }>
+ ): Promise