Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b93d543
Created functionality for sidebar (diffbar) - Using the logic of the …
oscarlund121 Oct 20, 2025
53358e7
CoPilot review updates
oscarlund121 Oct 21, 2025
dfc5c95
Import update:
oscarlund121 Oct 21, 2025
59b3a3d
diffwidth marked as unused
oscarlund121 Oct 21, 2025
bf99297
Various improvements to the diff view
ulrikandersen Nov 20, 2025
4f7bdab
Merge branch 'develop' into openapi-diff
ulrikandersen Nov 20, 2025
d89b9e1
Fixes build errors
ulrikandersen Nov 20, 2025
994c9a7
Fixes
ulrikandersen Nov 20, 2025
53a978d
Install oasdiff in Docker
ulrikandersen Nov 20, 2025
7a925a3
Add instructuons for oasdiff
ulrikandersen Nov 20, 2025
1d6494e
Ensure diff sidebar is closed by default
ulrikandersen Nov 20, 2025
e3f37ea
Fetch PRs for all repos in one query
ulrikandersen Nov 20, 2025
35da9c6
Disable diff button if diff cannot be determined
ulrikandersen Nov 20, 2025
134687d
Tooltip text was inverted
ulrikandersen Nov 20, 2025
dee68f8
Initial plan
Copilot Nov 20, 2025
a0096bb
Add URL validation for GitHub domains in OasDiffCalculator
Copilot Nov 20, 2025
93dba46
Use IGitHubClient interface instead of concrete class
Copilot Nov 20, 2025
ebc4979
Merge pull request #620 from shapehq/copilot/sub-pr-607
ulrikandersen Nov 20, 2025
a0de56f
Update src/features/sidebar/data/useDiff.ts
ulrikandersen Nov 20, 2025
af121ce
Initial plan
Copilot Nov 20, 2025
2724866
Use change.id as React key instead of array index
Copilot Nov 20, 2025
ec558d3
Update pull request query to only fetch OPEN states
ulrikandersen Nov 20, 2025
0f09ea9
Remove unnecessary React imports from multiple components
ulrikandersen Nov 20, 2025
edfb8e6
Remove semicolons
ulrikandersen Nov 20, 2025
9bb6004
Fix issue in SpacedList
ulrikandersen Nov 20, 2025
87f82f3
Remove unused border
ulrikandersen Nov 20, 2025
0f6711a
Fix linting error
ulrikandersen Nov 20, 2025
084883c
Refactor GitHubRepositoryDataSource for improved readability and cons…
ulrikandersen Nov 20, 2025
8c68abe
Refactor ClientSplitView for consistency and readability
ulrikandersen Nov 20, 2025
0ad48be
Refactor SecondarySplitHeader for improved readability and consistency
ulrikandersen Nov 20, 2025
a76b31c
Resolve merge conflicts with openapi-diff base branch
Copilot Nov 20, 2025
a2e5999
Merge branch 'openapi-diff' into copilot/sub-pr-607
Copilot Nov 20, 2025
850bd84
Merge pull request #622 from shapehq/copilot/sub-pr-607
ulrikandersen Nov 20, 2025
3cb312d
feat: add PR comparison context and new file detection to OpenAPI dif…
ulrikandersen Nov 21, 2025
21dea16
chore: fix linting errors - remove unused variables and escape apostr…
ulrikandersen Nov 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
142 changes: 142 additions & 0 deletions __test__/diff/OasDiffCalculator.test.ts
Original file line number Diff line number Diff line change
@@ -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: []
})
})
Loading
Loading