initial boilerplate code
All checks were successful
E2E Tests with Cypress / cypress (push) Successful in 3m12s

This commit is contained in:
Aleksa Bandic 2025-04-12 07:05:13 +02:00
commit a9b8e7bc9e
23 changed files with 3949 additions and 0 deletions

8
.dockerignore Normal file
View file

@ -0,0 +1,8 @@
*
!src/
!package.json
!yarn.lock
!.env*
!next-env.d.ts
!next.config.ts
!tsconfig.json

3
.env.example Normal file
View file

@ -0,0 +1,3 @@
# create .env.development, .env.production and .env.test
# placeholder, no variables required for now

3
.eslintrc.json Normal file
View file

@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript", "prettier"]
}

View file

@ -0,0 +1,71 @@
name: E2E Tests with Cypress
on:
push:
branches:
- main
- dev
pull_request:
branches:
- main
jobs:
cypress:
runs-on: ubuntu-host-docker
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Start test app with Docker Compose
run: docker compose up --build -d app-test
- name: Wait for test app to be healthy
run: |
SERVICE_NAME="app-test"
# wait for the container to exist
until CONTAINER_ID=$(docker compose ps -q $SERVICE_NAME 2>/dev/null) && [ -n "$CONTAINER_ID" ]; do
echo "Waiting for $SERVICE_NAME container to be created..."
sleep 5
done
# now check health status
while [ "$(docker inspect -f '{{.State.Health.Status}}' $CONTAINER_ID)" != "healthy" ]; do
echo "Waiting for $SERVICE_NAME to become healthy..."
sleep 5
done
echo "$SERVICE_NAME is healthy!"
- name: Build Docker Cypress image
run: |
tar -ch \
cypress.config.ts \
cypress/e2e \
cypress/tsconfig.json \
tsconfig.json \
Dockerfile.cypress \
| docker build -f Dockerfile.cypress -t cypress-tests -
- name: Run Cypress tests with artifact collection
run: |
mkdir -p cypress-videos cypress-screenshots
docker run --network host \
-v $(pwd)/cypress-videos:/e2e/cypress/videos \
-v $(pwd)/cypress-screenshots:/e2e/cypress/screenshots \
-t cypress-tests || true
- name: Upload test artifacts
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
if: always()
with:
name: cypress-artifacts.zip
path: |
cypress-videos/
cypress-screenshots/
retention-days: 3
- name: Cleanup Docker resources
if: always()
run: |
docker compose down --volumes --rmi local --remove-orphans
docker system prune -af \
--filter "label!=production"

44
.gitignore vendored Normal file
View file

@ -0,0 +1,44 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# env files (can opt-in for committing if needed)
.env*
# include the example environment file
!.env.example
# typescript
*.tsbuildinfo
next-env.d.ts
# cypress
cypress/screenshots/
cypress/videos/
cypress/reports/
cypress-screenshots/
cypress-videos/

2
.prettierignore Normal file
View file

@ -0,0 +1,2 @@
node_modules
.next

8
.prettierrc Normal file
View file

@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"printWidth": 80,
"arrowParens": "always"
}

14
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
"editor.formatOnSave": true, // Prettier will format on save
"editor.defaultFormatter": "esbenp.prettier-vscode", // Use Prettier for formatting
"eslint.format.enable": false, // Don't let ESLint format
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit", // Run ESLint fixes on save
"source.organizeImports": "explicit" // Organize imports on save
},
"editor.quickSuggestions": {
"other": true,
"comments": false,
"strings": true
}
}

17
Dockerfile Normal file
View file

@ -0,0 +1,17 @@
# base stage
FROM node:22-alpine AS base
WORKDIR /app
COPY . .
# add curl for healthchecks
RUN apk add --no-cache curl
# development mode stage
FROM base AS dev
RUN yarn install --frozen-lockfile
CMD ["yarn", "dev"]
# production mode stage
FROM base AS prod
RUN yarn install --production --frozen-lockfile
RUN yarn build
CMD ["yarn", "start"]

12
Dockerfile.cypress Normal file
View file

@ -0,0 +1,12 @@
FROM cypress/browsers:latest
WORKDIR /e2e
COPY tsconfig.json .
COPY cypress.config.ts .
COPY cypress ./cypress
RUN yarn init -y && \
yarn add -D cypress typescript
CMD ["npx", "cypress", "run"]

22
LICENSE.md Normal file
View file

@ -0,0 +1,22 @@
# License for Viewing and Learning
Copyright (c) 2025 Aleksa Bandic
This code is publicly available for **viewing and learning purposes**. You are welcome to explore it, study it, and use it as inspiration to improve your own skills.
However, please note the following restrictions:
1. **No Unauthorized Use**
- This code **may not** be copied, modified, or used in personal, public, or commercial projects without explicit permission.
2. **No Redistribution**
- You may **not** re-upload or share this code as your own in any form.
3. **Learning Purposes Only**
- You are encouraged to reference this code to understand concepts, but direct reuse in your own public projects is **not allowed**.
If you're interested in using this code in a project or have any questions, feel free to reach out!
📩 Contact: aleksa@aleksa.sh

31
README.md Normal file
View file

@ -0,0 +1,31 @@
# aleksa.sh
![Next JS](https://img.shields.io/badge/Next-black?style=for-the-badge&logo=next.js&logoColor=white)![Yarn](https://img.shields.io/badge/yarn-%232C8EBB.svg?style=for-the-badge&logo=yarn&logoColor=white)![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)![cypress](https://img.shields.io/badge/-cypress-%23E5E5E5?style=for-the-badge&logo=cypress&logoColor=058a5e)
my personal website and a sandbox to practice react/next.js and setting up + deploying relevant projects.
## running
requires docker and docker compose (optionally node + yarn to use cypress without docker)
run dev (w/ hot reload) with `docker compose up app-dev`
run prod with `docker compose up app-prod`
for e2e, run the app in test mode with `docker compose up app-test`, then start a native cypress run with `yarn e2e`. for a docker cypress run instead, use a tarball with custom build context to circumvent the default .dockerignore file like in the ci example.
test expansion is planned for the future.
## ci/cd
forgejo runner used (labelled `ubuntu-host-docker`) is a host passthrough runner that has access to an ubuntu host machine with docker and docker compose installed. refer to this [forgejo actions documentation](https://forgejo.org/docs/latest/admin/actions/#host) for more info.
make sure to always have a cleanup task at the end to prevent piling up containers on the host machine.
the ci workflow syntax and tasks may undergo frequent changes due to forgejo actions being wip.
future commits will introduce auto-deployment in cd.
## contact
feel free to reach out to me for any purpose on aleksa@aleksa.sh :)

11
cypress.config.ts Normal file
View file

@ -0,0 +1,11 @@
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
supportFile: false,
baseUrl: 'http://localhost:3001',
},
video: true,
screenshotsFolder: 'cypress/screenshots',
videosFolder: 'cypress/videos',
});

View file

@ -0,0 +1,8 @@
describe('Homepage', () => {
it('should load successfully', () => {
cy.visit('/');
cy.contains('aleksa.sh').should('exist');
cy.screenshot();
cy.wait(500);
});
});

14
cypress/tsconfig.json Normal file
View file

@ -0,0 +1,14 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"],
"baseUrl": "../",
"noEmit": true,
"isolatedModules": false,
"moduleResolution": "node"
},
"include": ["**/*.ts"],
"exclude": []
}

41
docker-compose.yaml Normal file
View file

@ -0,0 +1,41 @@
x-common: &common # shared values
build: &build-base
context: .
dockerfile: Dockerfile
ports: &ports
- '3000:3000'
healthcheck:
&healthcheck # curl outputs to /dev/null to prevent clutter in logs
test: ['CMD', 'curl', '-sf', 'http://localhost:3000', '-o', '/dev/null']
interval: 5s
timeout: 3s
retries: 1
start_period: 3s
services:
app-dev:
build:
<<: *build-base
target: dev
volumes:
- ./src:/app/src # enables hot reload
- /app/node_modules # keeps installed node_modules
ports: *ports
healthcheck: *healthcheck
app-prod: &app-prod
labels:
- 'production'
build:
<<: *build-base
target: prod
ports: *ports
healthcheck: *healthcheck
app-test:
<<: *app-prod
environment:
- NODE_ENV=test # override environment variables
ports:
- '3001:3000' # override ports to prevent conflicts while testing
healthcheck: *healthcheck # uses internal port 3000, not host 3001

7
next.config.ts Normal file
View file

@ -0,0 +1,7 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;

29
package.json Normal file
View file

@ -0,0 +1,29 @@
{
"name": "aleksa.sh",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"e2e": "cypress run"
},
"dependencies": {
"axios": "^1.8.4",
"next": "^15.3.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"cypress": "^14.3.0",
"eslint": "^9.24.0",
"eslint-config-next": "15.3.0",
"eslint-config-prettier": "^10.1.2",
"prettier": "3.5.3",
"typescript": "^5.8.3"
}
}

19
src/app/layout.tsx Normal file
View file

@ -0,0 +1,19 @@
import type { Metadata } from 'next';
import '../styles/globals.css';
export const metadata: Metadata = {
title: 'aleksa.sh',
description: 'My personal website and sandbox project',
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html>
<body>{children}</body>
</html>
);
}

13
src/app/page.tsx Normal file
View file

@ -0,0 +1,13 @@
'use client'; //mark as client app
export default function Home() {
return (
<div className="container">
<main>
<h1 className="title">aleksa.sh</h1>
<p className="subTitle">coming soon</p>
</main>
<footer></footer>
</div>
);
}

41
src/styles/globals.css Normal file
View file

@ -0,0 +1,41 @@
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #171717;
--foreground: #ededed;
}
}
body {
color: var(--foreground);
background: var(--background);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
text-align: center;
}
.container {
display: flex;
flex-direction: column;
}
.title {
font-size: 2rem;
margin: 0;
padding-bottom: 0.5rem;
font-family: 'Courier New', Courier, monospace;
}
.subTitle {
font-size: 1.3rem;
margin: 0;
color: gray;
font-family: 'Courier New', Courier, monospace;
}

28
tsconfig.json Normal file
View file

@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

3503
yarn.lock Normal file

File diff suppressed because it is too large Load diff