Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
contents: read
deployments: write
pull-requests: write
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
steps:
- uses: actions/checkout@v6.0.2
- name: Setup Node.js environment
Expand Down Expand Up @@ -53,6 +55,7 @@ jobs:
env:
VITE_DEPLOYMENT_URL: ${{ env.VITE_DEPLOYMENT_URL }}
- name: Deploy
if: ${{ github.actor != 'dependabot[bot]' }}
id: deploy
uses: cloudflare/wrangler-action@v3
with:
Expand All @@ -64,6 +67,7 @@ jobs:
env:
FORCE_COLOR: 0
- name: Comment PR with deployment link
if: ${{ github.actor != 'dependabot[bot]' }}
uses: marocchino/sticky-pull-request-comment@v2
with:
recreate: true
Expand All @@ -74,3 +78,28 @@ jobs:
Deployment Environment: ${{ steps.deploy.outputs.pages-environment }}

${{ steps.deploy.outputs.command-output }}

e2e:
runs-on: ubuntu-latest
name: E2E Tests
needs: deploy
if: ${{ github.actor != 'dependabot[bot]' }}
steps:
- uses: actions/checkout@v6.0.2
- name: Setup Node.js environment
uses: actions/setup-node@v6.3.0
with:
node-version-file: ".node-version"
cache: "yarn"
- name: Enable Corepack
run: corepack enable
- name: Install dependencies
run: yarn install
- name: Build ReScript
run: yarn build:res
- name: Cypress E2E tests
uses: cypress-io/github-action@v7
with:
install: false
browser: chrome
config: baseUrl=${{ needs.deploy.outputs.deployment-url }}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ functions/**/*.mjs
functions/**/*.jsx
__tests__/**/*.mjs
__tests__/**/*.jsx
e2e/**/*.mjs
e2e/**/*.jsx
!_shims.mjs
!_shims.jsx

Expand All @@ -72,4 +74,4 @@ _scripts

# Vitest screenshots
!__tests__/__screenshots__/**/*
.vitest-attachments
.vitest-attachments
13 changes: 12 additions & 1 deletion app/routes.res
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,20 @@ let blogArticleRoutes =
route(path, "./routes/BlogArticleRoute.jsx", ~options={id: path})
)

let communityRoutes =
MdxFile.scanPaths(~dir="markdown-pages/community", ~alias="community")->Array.map(path =>
route(path, "./routes/CommunityRoute.jsx", ~options={id: path})
)

let mdxRoutes = mdxRoutes("./routes/MdxRoute.jsx")->Array.filter(r =>
!(
r.path
->Option.map(path => path === "blog" || String.startsWith(path, "blog/"))
->Option.map(path =>
path === "blog" ||
String.startsWith(path, "blog/") ||
path === "community" ||
String.startsWith(path, "community/")
)
->Option.getOr(false)
)
)
Expand All @@ -57,6 +67,7 @@ let default = [
...stdlibRoutes,
...beltRoutes,
...blogArticleRoutes,
...communityRoutes,
...mdxRoutes,
route("*", "./routes/NotFoundRoute.jsx"),
]
119 changes: 119 additions & 0 deletions app/routes/CommunityRoute.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
type loaderData = {
compiledMdx: CompiledMdx.t,
entries: array<TableOfContents.entry>,
title: string,
description: string,
filePath: string,
categories: array<SidebarLayout.Sidebar.Category.t>,
}

let convertToNavItems = (items, rootPath) =>
Array.map(items, (item): SidebarLayout.Sidebar.NavItem.t => {
let href = switch item.Mdx.slug {
| Some(slug) => `${rootPath}/${slug}`
| None => rootPath
}
{
name: item.title,
href,
}
})

let getGroup = (groups, groupName): SidebarLayout.Sidebar.Category.t => {
{
name: groupName,
items: groups
->Dict.get(groupName)
->Option.getOr([]),
}
}

let getAllGroups = (groups, groupNames): array<SidebarLayout.Sidebar.Category.t> =>
groupNames->Array.map(item => getGroup(groups, item))

let communityTableOfContents = async () => {
let groups =
(await Mdx.allMdx(~filterByPaths=["markdown-pages/community"]))
->Mdx.filterMdxPages("community")
->Mdx.groupBySection
->Dict.mapValues(values => values->Mdx.sortSection->convertToNavItems("/community"))

getAllGroups(groups, ["Resources"])
}

let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => {
let {pathname} = WebAPI.URL.make(~url=request.url)
let filePath = MdxFile.resolveFilePath(
(pathname :> string),
~dir="markdown-pages/community",
~alias="community",
)

let raw = await Node.Fs.readFile(filePath, "utf-8")
let {frontmatter}: MarkdownParser.result = MarkdownParser.parseSync(raw)

let description = switch frontmatter {
| Object(dict) =>
switch dict->Dict.get("description") {
| Some(String(s)) => s
| _ => ""
}
| _ => ""
}

let title = switch frontmatter {
| Object(dict) =>
switch dict->Dict.get("title") {
| Some(String(s)) => s
| _ => ""
}
| _ => ""
}

let compiledMdx = await MdxFile.compileMdx(raw, ~filePath, ~remarkPlugins=Mdx.plugins)

let markdownTree = Mdast.fromMarkdown(raw)
let tocResult = Mdast.toc(markdownTree, {maxDepth: 2})

let headers = Dict.make()
Mdast.reduceHeaders(tocResult.map, headers)

let entries =
headers
->Dict.toArray
->Array.map(((header, url)): TableOfContents.entry => {
header,
href: (url :> string),
})
->Array.slice(~start=2)

let categories = await communityTableOfContents()

{
compiledMdx,
entries,
title: `${title} | ReScript Community`,
description,
filePath,
categories,
}
}

let default = () => {
let {compiledMdx, entries, filePath, categories} = ReactRouter.useLoaderData()

let editHref = `https://github.com/rescript-lang/rescript-lang.org/blob/master/${filePath}`

<>
<CommunityLayout categories entries>
<div className="markdown-body">
<MdxContent compiledMdx />
</div>
<a
href=editHref className="inline text-14 hover:underline text-fire" rel="noopener noreferrer"
>
{React.string("Edit")}
</a>
</CommunityLayout>
</>
}
12 changes: 12 additions & 0 deletions app/routes/CommunityRoute.resi
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type loaderData = {
compiledMdx: CompiledMdx.t,
entries: array<TableOfContents.entry>,
title: string,
description: string,
filePath: string,
categories: array<SidebarLayout.Sidebar.Category.t>,
}

let loader: ReactRouter.Loader.t<loaderData>

let default: unit => React.element
21 changes: 0 additions & 21 deletions app/routes/MdxRoute.res
Original file line number Diff line number Diff line change
Expand Up @@ -115,19 +115,6 @@ let reactTableOfContents = async () => {
categories
}

let communityTableOfContents = async () => {
let groups =
(await allMdx(~filterByPaths=["markdown-pages/community"]))
->filterMdxPages("community")
->groupBySection
->Dict.mapValues(values => values->sortSection->convertToNavItems("/community"))

// these are the categories that appear in the sidebar
let categories: array<SidebarLayout.Sidebar.Category.t> = getAllGroups(groups, ["Resources"])

categories
}

let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => {
let {pathname} = WebAPI.URL.make(~url=request.url)

Expand Down Expand Up @@ -163,8 +150,6 @@ let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => {
await manualTableOfContents()
} else if pathname->String.includes("docs/react") {
await reactTableOfContents()
} else if pathname->String.includes("community") {
await communityTableOfContents()
} else {
[]
}
Expand Down Expand Up @@ -236,8 +221,6 @@ let loader: ReactRouter.Loader.t<loaderData> = async ({request}) => {
"ReScript React"
} else if path->String.includes("docs/manual") {
"ReScript Language Manual"
} else if path->String.includes("community") {
"ReScript Community"
} else {
"ReScript"
}
Expand Down Expand Up @@ -352,10 +335,6 @@ let default = () => {
</>
}
</>
} else if (pathname :> string)->String.includes("community") {
<CommunityLayout categories entries>
<div className="markdown-body"> {component()} </div>
</CommunityLayout>
} else {
switch loaderData.mdxSources {
| Some(mdxSources) =>
Expand Down
18 changes: 18 additions & 0 deletions cypress.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig } from "cypress";

export default defineConfig({
allowCypressEnv: false,
retries: {
runMode: 2,
openMode: 0,
},
e2e: {
baseUrl: "http://localhost:8080",
specPattern: "e2e/**/*.cy.jsx",
supportFile: "cypress/support/e2e.js",
video: false,
screenshotOnRunFailure: false,
defaultCommandTimeout: 10000,
pageLoadTimeout: 30000,
},
});
23 changes: 23 additions & 0 deletions cypress/support/e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const knownHydrationErrors = [
/Hydration failed because the initial UI does not match what was rendered on the server\.?/,
/Text content does not match server-rendered HTML\.?/,
/There was an error while hydrating\.?/,
/Minified React error #418\b/,
/Minified React error #423\b/,
/Minified React error #425\b/,
];

Cypress.on("uncaught:exception", (err) => {
const message = err && err.message ? err.message : "";
const isKnownHydrationError = knownHydrationErrors.some((pattern) =>
pattern.test(message),
);

if (isKnownHydrationError) {
console.warn("Suppressing known React hydration exception in Cypress:", {
message,
error: err,
});
return false;
}
});
Loading