Add Forgejo CI and Dokploy deployment
Some checks are pending
CI / verify (push) Waiting to run

This commit is contained in:
Neeldhara Misra 2026-07-20 14:22:11 +05:30
parent 263c87d6c7
commit 3d31dc1b55
13 changed files with 110 additions and 13 deletions

8
.dockerignore Normal file
View file

@ -0,0 +1,8 @@
.git
.github
.forgejo
dist
node_modules
npm-debug.log*
Dockerfile*
README.md

41
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,41 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ci-${{ forgejo.ref }}
cancel-in-progress: true
jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Build
run: npm run build

19
Dockerfile Normal file
View file

@ -0,0 +1,19 @@
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine AS runtime
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q --spider http://127.0.0.1/ || exit 1

View file

@ -64,6 +64,12 @@ This project is built with:
Simply open [Lovable](https://lovable.dev/projects/5ee9d1e7-af25-47e9-ab4c-bdbca4554e32) and click on Share -> Publish.
### Forgejo and Dokploy
Pushes and pull requests targeting `main` are verified by the Forgejo Actions workflow in `.forgejo/workflows/ci.yml`. The production container is built from `Dockerfile` and serves the Vite output with NGINX, including React Router fallback handling.
Dokploy is connected to the `Websites/interactives` Forgejo repository with autodeploy enabled. A successful push to `main` triggers a fresh Dockerfile build and replaces the running service only after the image has built successfully.
## Can I connect a custom domain to my Lovable project?
Yes, you can!

22
nginx.conf Normal file
View file

@ -0,0 +1,22 @@
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location = /index.html {
add_header Cache-Control "no-cache";
}
location ~* \.(?:css|js|mjs|jpg|jpeg|png|gif|ico|svg|webp|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
}

View file

@ -67,7 +67,7 @@ const InteractiveGallery = () => {
<div className="space-y-6">
<div className="space-y-4">
<h1 className="text-3xl font-bold text-foreground">Data Structures and Algorithms</h1>
<p className="text-muted-foreground text-lg">Interactive explorations of elementary data structures and algorithms.</p>
<p className="text-muted-foreground text-lg">Interactive explorations of elementary data structures and algorithms.</p>
</div>
{/* Search bar */}
@ -103,4 +103,4 @@ const InteractiveGallery = () => {
</Layout>
);
};
export default InteractiveGallery;
export default InteractiveGallery;

View file

@ -323,7 +323,7 @@ const InteractiveIndex = () => {
// Filter and sort interactives
const filteredAndSortedInteractives = useMemo(() => {
let filtered = allInteractives.filter(interactive => {
const filtered = allInteractives.filter(interactive => {
const matchesSearch = interactive.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
interactive.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
interactive.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
@ -549,4 +549,4 @@ const InteractiveIndex = () => {
);
};
export default InteractiveIndex;
export default InteractiveIndex;

View file

@ -144,7 +144,7 @@ const LadybugClockPuzzle: React.FC<LadybugClockPuzzleProps> = ({
// Run to completion
const runToCompletion = useCallback(() => {
setIsPlaying(false);
let currentSim = simulation;
const currentSim = simulation;
let position = currentSim.position;
const visited = new Set(currentSim.visited);
let moveCount = currentSim.moveCount;
@ -545,4 +545,4 @@ const LadybugClockPuzzle: React.FC<LadybugClockPuzzleProps> = ({
{showSocialShare && <SocialShare title="The Ladybug Clock Puzzle" description="Explore random walks on a clock face and discover why every number has the same probability of being painted last!" url={shareUrl} />}
</div>;
};
export default LadybugClockPuzzle;
export default LadybugClockPuzzle;

View file

@ -34,7 +34,7 @@ const ZeckendorfGame: React.FC<ZeckendorfGameProps> = ({ showSocialShare = true
// Generate a random combination of Fibonacci numbers
const numTerms = Math.floor(Math.random() * 4) + 2; // 2-5 terms
let target = 0;
let usedIndices = new Set<number>();
const usedIndices = new Set<number>();
for (let i = 0; i < numTerms; i++) {
let index;
@ -235,4 +235,4 @@ const ZeckendorfGame: React.FC<ZeckendorfGameProps> = ({ showSocialShare = true
);
};
export default ZeckendorfGame;
export default ZeckendorfGame;

View file

@ -217,7 +217,7 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
case 'odd':
baseCondition = (num % 2 !== 0);
break;
case 'prime':
case 'prime': {
const isPrimeNum = (n: number): boolean => {
if (n < 2) return false;
for (let i = 2; i <= Math.sqrt(n); i++) {
@ -227,6 +227,7 @@ const QuestionGuessingMode: React.FC<QuestionGuessingModeProps> = ({
};
baseCondition = isPrimeNum(num);
break;
}
case 'perfectSquare':
baseCondition = Math.sqrt(num) % 1 === 0;
break;

View file

@ -21,7 +21,7 @@ const Command = React.forwardRef<
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
type CommandDialogProps = DialogProps
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (

View file

@ -2,8 +2,7 @@ import * as React from "react"
import { cn } from "@/lib/utils"
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {

View file

@ -1,4 +1,5 @@
import type { Config } from "tailwindcss";
import tailwindcssAnimate from "tailwindcss-animate";
export default {
darkMode: ["class"],
@ -122,5 +123,5 @@ export default {
}
}
},
plugins: [require("tailwindcss-animate")],
plugins: [tailwindcssAnimate],
} satisfies Config;