Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6d70057
small improvements
abrulic Sep 7, 2025
3503ff6
fix for standalone .mdx files + typesafety for generated pages and se…
abrulic Sep 7, 2025
f60013e
small changes
abrulic Sep 8, 2025
0ce7516
updates
abrulic Sep 10, 2025
933ad24
small updates
abrulic Sep 10, 2025
5d145a4
fixes
abrulic Sep 11, 2025
324471b
fixes
abrulic Sep 11, 2025
978887a
small updates
abrulic Sep 11, 2025
45e5517
deployed app using standard fly launch and fly deploy commands
abrulic Sep 12, 2025
9831bb1
Merge branch 'main' into improvements
abrulic Sep 12, 2025
8b73a61
fixes in loding content collections and some UI fixes
abrulic Sep 12, 2025
66675a5
removed content-collections:build script from package.json
abrulic Sep 12, 2025
ab416e8
accordion component updated
abrulic Sep 12, 2025
c1a4fbe
added on PR fly deploy action
abrulic Sep 12, 2025
219f3aa
removed flydotio/dockerfile
abrulic Sep 12, 2025
69144df
fixed content-ollections Page imports
abrulic Sep 12, 2025
747aefe
fly deploy workflow update
abrulic Sep 12, 2025
b231a6c
fly deploy workflow update
abrulic Sep 12, 2025
2222945
content-collections cli returned
abrulic Sep 12, 2025
1e49bd5
added small console log for debugging
abrulic Sep 12, 2025
cb8a2f3
added small console log for debugging
abrulic Sep 12, 2025
e225d77
added small console log for debugging
abrulic Sep 12, 2025
1f1f090
added small console log for debugging and pr-close workflow
abrulic Sep 12, 2025
0104233
small fix
abrulic Sep 12, 2025
eec49e9
updated env_vars in ci.yml
abrulic Sep 12, 2025
b649f7b
debounced search
abrulic Sep 12, 2025
1e653ee
small update in seo.ts and favion.ico changed
abrulic Sep 12, 2025
849285b
added meta for index page
abrulic Sep 12, 2025
fb234fa
small fix in filename
abrulic Sep 12, 2025
a802352
fix with seo image?
abrulic Sep 12, 2025
5dbf938
small fixes in padding, leading, font size
abrulic Sep 15, 2025
5a7a8c8
update in fly.toml, dockerfile and package.json
abrulic Sep 15, 2025
2b2c5b3
small change in dockerifle
abrulic Sep 15, 2025
2ae70d1
added http_checks in fly.toml
abrulic Sep 15, 2025
38abb62
small update in fly.toml
abrulic Sep 15, 2025
40b3f9d
small update?
abrulic Sep 15, 2025
5578c64
fly tol update
abrulic Sep 15, 2025
2926f85
previous state?
abrulic Sep 15, 2025
13504f9
check?
abrulic Sep 15, 2025
f68730a
previous state
abrulic Sep 15, 2025
d2c3788
small fix
abrulic Sep 16, 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
89 changes: 89 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
node_modules
public/build
build
dist
out
coverage
.history
.react-router

# Other Coverage tools
*.lcov

# macOS
.DS_*

# Cache Directories and files
.cache
.yarn*
.env*
!.env.example
.swp*
.turbo
.npm
.stylelintcache
*.tsbuildinfo
.node_repl_history

# Lock files from other package managers
package-lock.json
yarn.lock

# General tempory files and directories
t?mp
.t?mp
*.t?mp

# Docusaurus cache and generated files
.docusaurus

# Output of 'npm pack'
*.tgz
*.tar
*.tar.gz
*.tar.bz2
*.tbz
*.zip

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
vite.config.ts.*

# Playwright various test reports
test-results
playwright-report
blob-report


# Editors
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf

# Dont commit sqlite database files
*.db
*.sqlite
*.sqlite3
*.db-journal


# Content collections output files
.content-collections

# Output base directory of the documentation
generated-docs/
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
GITHUB_OWNER="github-owner" # Your username or organization name (Optional. For edit/report an issue for the documentation page)
GITHUB_REPO="github-repo" # Repository name (Optional. For edit/report an issue for the documentation page)
APP_ROOT_PATH="/path/to/your/app" # Optional. Default is `process.cwd()`
GITHUB_REPO_URL="github-repo-url" # Optional. If you want to have GitHub icon link in the header or footer
23 changes: 22 additions & 1 deletion .github/workflows/validate.yml → .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: 🚀 Validation Pipeline
name: 🚀 Validation & Deploy Pipeline
concurrency:
group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Expand Down Expand Up @@ -67,3 +67,24 @@ jobs:
# Only works if you set `reportOnFailure: true` in your vite config as specified above
if: always()
uses: davelosert/vitest-coverage-report-action@v2


deploy:
needs: [lint, typecheck, check-unused, vitest]
name: 🚀 Deploy PR Preview
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: forge-42/fly-deploy@v1.0.0-rc.2
id: deploy
env:
FLY_ORG: ${{ vars.FLY_ORG }}
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
FLY_REGION: ${{ vars.FLY_REGION }}
with:
app_name: ${{github.event.repository.name}}-${{ github.event.number }}
env_vars: |
APP_ENV=staging
GITHUB_OWNER=${{github.repository_owner}}
GITHUB_REPO=${{github.event.repository.name}}
GITHUB_REPO_URL=https://github.com/${{ github.repository }}
27 changes: 27 additions & 0 deletions .github/workflows/pr-close.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: 🧹 PR Close

concurrency:
group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
pull_request:
branches: [main]
types: closed

jobs:

destroy-pr-preview:
name: 🧹 Destroy PR Preview
runs-on: ubuntu-latest
environment:
name: pr-preview
steps:
- uses: actions/checkout@v4
- uses: forge-42/fly-destroy@v1.0.0-rc.2
id: destroy
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
FLY_ORG: ${{ vars.FLY_ORG }}
with:
app_name: ${{ env.FLY_ORG }}-${{ github.event.number }}
51 changes: 51 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

# syntax = docker/dockerfile:1.4

# Base dependencies stage
ARG NODE_VERSION=22.17.0
FROM node:${NODE_VERSION}-slim AS base

LABEL fly_launch_runtime="Node.js"

# Node.js app lives here
WORKDIR /app

# Set production environment
ENV NODE_ENV="production"

# Install pnpm
ARG PNPM_VERSION=10.13.0
RUN npm install -g pnpm@$PNPM_VERSION


# Throw-away build stage to reduce size of final image
FROM base AS build

# Install packages needed to build node modules
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3 git

# Install node modules
COPY .npmrc package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod=false

# Copy application code
COPY . .

# Build application
RUN pnpm run generate:docs
RUN pnpm run build

# Remove development dependencies
RUN pnpm prune --prod


# Final stage for app image
FROM base

# Copy built application
COPY --from=build /app /app

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD [ "pnpm", "run", "start" ]
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This folder contains all the resources used by the documentation site, such as S

`content/`

This folder contains sections and subsections with .mdx files that hold your documentation content. Below is the recommended structure to follow.
This folder contains .md and .mdx files that hold your documentation content. Below is the recommended structure to follow.


An example of a valid content/ folder structure for organizing your package documentation:
Expand Down Expand Up @@ -46,12 +46,15 @@ content/
├── 01-buttons.mdx
└── 02-modals.mdx
```
- Top-level .mdx files (like 01-changelog.mdx) are allowed.
- Sections (like 04-getting-started, 05-core-features) are subfolders inside the content/ folder.
- Subsections (like 03-data-management, 04-ui-components) are nested folders within sections.
- Each section or subsection should have an index.md file for its sidebar title.
- Top-level .mdx files (like 01-changelog.mdx) are allowed, but we recommend placing them in order before the sections, as shown in the example.

### Example of the valid `02-introduction.mdx` file:
- Sections (like 04-getting-started, 05-core-features) are subfolders inside the `content` folder.

- Subsections (like 03-data-management, 04-ui-components) are nested folders within sections. Filenames inside them should start with `01-*.mdx`.

- Each section or subsection should include an `index.md` file, which defines its sidebar title.

### Example of the valid `**/*.mdx` file:
```
---
title: "Introduction to Forge42 Base Stack"
Expand All @@ -75,7 +78,7 @@ cd my-app
npm install
```

### Example of the valid `04-getting-started/index.md` file:
### Example of the valid `**/*.md` file:
```
---
title: Getting Started
Expand Down
2 changes: 1 addition & 1 deletion app/components/command-k/components/search-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function SearchInput({ value, onChange, placeholder, ref }: SearchInputPr
return (
<div
className={cn(
"flex items-center gap-3 border-[var(--color-input-border)] border-b bg-[var(--color-input-bg)] px-4 py-4"
"flex items-center gap-3 border-[var(--color-input-border)] border-b bg-[var(--color-input-bg)] px-4 py-3"
)}
>
<Icon name="Search" className="size-5 flex-shrink-0 text-[var(--color-input-icon)]" />
Expand Down
6 changes: 3 additions & 3 deletions app/components/command-k/components/trigger-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const TriggerButton = ({
type="button"
onClick={onOpen}
className={cn(
"group flex items-center gap-2 rounded-lg border px-3 py-2 text-sm shadow-sm transition-all duration-200",
"group flex items-center gap-2 rounded-lg border px-2 py-1.5 text-sm shadow-sm transition-all duration-200 xl:px-3 xl:py-2",
"border-[var(--color-trigger-border)] bg-[var(--color-trigger-bg)] text-[var(--color-trigger-text)]",
"hover:border-[var(--color-trigger-hover-border)] hover:bg-[var(--color-trigger-hover-bg)] hover:shadow-md",
"focus:border-[var(--color-trigger-focus-border)] focus:outline-none focus:ring-2 focus:ring-[var(--color-trigger-focus-ring)]"
Expand All @@ -22,8 +22,8 @@ export const TriggerButton = ({
name="Search"
className={cn("size-4 transition-colors", "group-hover:text-[var(--color-trigger-hover-text)]")}
/>
<span className="hidden sm:inline">{placeholder}</span>
<div className="ml-auto hidden items-center gap-1 sm:flex">
<span className="hidden xl:inline">{placeholder}</span>
<div className="ml-auto hidden items-center gap-1 xl:flex">
<kbd
className={cn(
"rounded border border-[var(--color-kbd-border)] bg-[var(--color-kbd-bg)] px-1.5 py-0.5 font-mono text-xs",
Expand Down
3 changes: 1 addition & 2 deletions app/components/command-k/create-search-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,11 @@ function extractHeadingSections(rawMdx: string) {

export function createSearchIndex(pages: Page[]) {
return pages
.filter((page) => page._meta.fileName !== "_index.mdx" && page.slug !== "_index")
.filter((page) => page.slug !== "_index")
.flatMap((page) => {
const pageSlug = getPageSlug(page)
const pageUrl = pageSlug.startsWith("/") ? pageSlug : `/${pageSlug}`
const sections = extractHeadingSections(page.rawMdx)

return sections.map((section) => {
const heading = section.heading === "_intro" ? page.title : section.heading

Expand Down
12 changes: 12 additions & 0 deletions app/components/command-k/hooks/use-debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useEffect, useState } from "react"

export function useDebounce<T>(value: T, delay = 250) {
const [debouncedValue, setDebouncedValue] = useState<T>(value)

useEffect(() => {
const id = setTimeout(() => setDebouncedValue(value), delay)
return () => clearTimeout(id)
}, [value, delay])

return debouncedValue
}
32 changes: 19 additions & 13 deletions app/components/command-k/hooks/use-search.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useState } from "react"
import { useEffect, useRef, useState } from "react"
import { useFetcher } from "react-router"
import z from "zod"
import type { Version } from "~/utils/version-resolvers"
import { versions } from "~/utils/versions"
import type { SearchResult } from "../search-types"
import { useDebounce } from "./use-debounce"

export const commandKSearchParamsSchema = z.object({
query: z.string(),
Expand All @@ -23,31 +24,36 @@ function createCommandKSearchParams(params: Record<string, string>) {
return { params: new URLSearchParams(result.data) }
}

const debounceMs = 250
const minChars = 1

export function useSearch({ version }: { version: Version }) {
const fetcher = useFetcher<{ results: SearchResult[] }>()
const [query, setQuery] = useState("")
//we will show results as soon as we have a non-empty query
//this does not debounce or wait for fetcher.state === "idle".
const debouncedQuery = useDebounce(query, debounceMs)
const lastLoadedRef = useRef<string | null>(null)

const results = query.trim() ? (fetcher.data?.results ?? []) : []

function search(q: string) {
const trimmed = q.trim()
setQuery(q)
}

if (!trimmed) {
setQuery("")
useEffect(() => {
const trimmed = debouncedQuery.trim()
if (!trimmed || trimmed.length < minChars) {
lastLoadedRef.current = null
return
}

setQuery(trimmed)
if (lastLoadedRef.current === trimmed) return
lastLoadedRef.current = trimmed

const { params } = createCommandKSearchParams({ query: trimmed, version })
if (!params) {
// biome-ignore lint/suspicious/noConsole: keep for debugging
console.error("Failed to create search parameters.")
return
}
if (!params) return

fetcher.load(`/search?${params.toString()}`)
}
}, [debouncedQuery, version, fetcher])

return {
results,
Expand Down
28 changes: 28 additions & 0 deletions app/components/icon-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ComponentProps } from "react"
import { Icon } from "~/ui/icon/icon"
import type { IconName } from "~/ui/icon/icons/types"
import { cn } from "~/utils/css"

interface IconLinkProps extends ComponentProps<"a"> {
name: IconName
}

export const IconLink = ({ name, className, ...props }: IconLinkProps) => {
const { href } = props
const isExternal = typeof href === "string" && /^https?:\/\//i.test(href)
return (
<a
className={cn(
"group relative inline-flex cursor-pointer items-center justify-center rounded-full text-[var(--color-text-active)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border)] focus-visible:ring-offset-2",
className
)}
target={isExternal ? "_blank" : undefined}
rel="noopener noreferrer"
aria-label={name}
href={href}
{...props}
>
<Icon name={name} className="size-4 transition-all duration-300 xl:size-5" />
</a>
)
}
2 changes: 1 addition & 1 deletion app/components/logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ReactNode } from "react"

export const Logo = ({ children }: { children: ReactNode }) => {
return (
<div className="relative block font-semibold font-space text-[var(--color-text-active)] text-lg md:text-2xl xl:text-4xl">
<div className="relative block font-semibold font-space text-[var(--color-text-active)] text-lg md:text-2xl xl:text-3xl">
{children}
</div>
)
Expand Down
Loading
Loading