Compare commits

..

No commits in common. "main" and "0.2.0-pre.67" have entirely different histories.

48 changed files with 1037 additions and 12737 deletions

View file

@ -51,8 +51,6 @@ jobs:
with:
push: true
tags: forgejo.neshweb.net/firq/firq-dev-website:${{ github.ref_name }}, forgejo.neshweb.net/firq/firq-dev-website:preview
build-args: |
version=${{ github.ref_name }}
create-release:
needs: [ build-site ]

View file

@ -51,8 +51,6 @@ jobs:
with:
push: true
tags: forgejo.neshweb.net/firq/firq-dev-website:${{ github.ref_name }}, forgejo.neshweb.net/firq/firq-dev-website:latest
build-args: |
version=${{ github.ref_name }}
auto-deploy-dockge:
needs: [ build-site ]

View file

@ -12,25 +12,25 @@ jobs:
unlighthouse:
runs-on: docker
container:
image: forgejo.neshweb.net/ci-docker-images/unlighthouse:0.16.3
image: forgejo.neshweb.net/ci-docker-images/unlighthouse:0.4.1
services:
website:
image: forgejo.neshweb.net/firq/firq-dev-website:${{ inputs.containertag }}
options: >-
--hostname website
--workdir /
steps:
- name: Checkout repository
uses: https://code.forgejo.org/actions/checkout@v3
- name: Check availability
run: |
while [ "$(curl -o /dev/null -s -w '%{http_code}' http://website:8081)" -ne 200 ];
while [ "$(curl -o /dev/null -s -w '%{http_code}' http://localhost:8081/)" -ne 200 ];
do echo "Waiting...";
sleep 5;
done;
- name: Run unlighthouse
run: unlighthouse-ci --site "http://website:8081"
run: unlighthouse-ci --site "http://localhost:8081/"
- name: Replace URLs
run: find ./unlighthouse-reports -type f | xargs sed -i "s|http://website:8081|https://preview.firq.dev|g";
run: find ./unlighthouse-reports -type f | xargs sed -i "s|http://localhost:8081|https://preview.firq.dev|g";
- name: Prepare artifacts
run: cp serve.json unlighthouse-reports
- name: Upload reports
@ -73,8 +73,6 @@ jobs:
file: Dockerfile.reports
push: true
tags: forgejo.neshweb.net/firq/firq-dev-website-unlighthouse:latest
build-args: |
version=${{ inputs.containertag }}
auto-deploy-dockge:
needs: [ build-site ]
@ -89,7 +87,5 @@ jobs:
- name: Check status and redeploy
run: |
dockge status firq-dev-unlighthouse
dockge down firq-dev-unlighthouse
dockge update firq-dev-unlighthouse
dockge start firq-dev-unlighthouse
dockge status firq-dev-unlighthouse

View file

@ -1,21 +1,15 @@
FROM forgejo.neshweb.net/ci-docker-images/node-chromium:22 AS build
FROM node:22-alpine AS build
WORKDIR /app
ENV CI=true
COPY . .
RUN npm i
RUN npm run astro telemetry disable
RUN npm run build
RUN rm ./dist/.original.favicon.ico
FROM forgejo.neshweb.net/ci-docker-images/website-serve:2 AS runtime
ARG version=0.0.1
ENV version=${version}
WORKDIR /
COPY --from=build /app/dist /public
COPY --from=build /app/serve.json /public/serve.json
RUN rm /public/.original.favicon.ico
EXPOSE 8081
CMD echo "Website version ${version} - powered by @Firq"; serve --listen 8081 --no-clipboard /public
CMD serve public/ -p 8081

View file

@ -1,10 +1,6 @@
FROM forgejo.neshweb.net/ci-docker-images/website-serve:2 AS runtime
ARG version=0.0.1
ENV version=${version}
WORKDIR /
ADD reports /public
EXPOSE 8081
CMD echo "Website version ${version} - powered by @Firq"; serve --listen 8081 --no-clipboard /public
CMD serve public/ -p 8081

View file

@ -1,8 +1,8 @@
import { defineConfig } from 'astro/config';
import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap";
import criticalCss from 'astro-critical-css';
import astroMetaTags from "astro-meta-tags";
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
@ -11,5 +11,5 @@ export default defineConfig({
outDir: 'dist',
publicDir: 'public',
site: 'https://firq.dev/',
integrations: [sitemap(), mdx(), astroMetaTags(), criticalCss()],
integrations: [sitemap(), mdx(), astroMetaTags()]
});

13125
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{
"name": "@firq/fgosite",
"type": "module",
"version": "0.2.1-pre.1",
"version": "0.2.0-pre.67",
"private": true,
"scripts": {
"dev": "astro dev",
@ -13,19 +13,15 @@
"dependencies": {
"@astro-community/astro-embed-youtube": "^0.5.6",
"@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^4.3.2",
"@astrojs/sitemap": "^3.4.2",
"@astrojs/mdx": "^4.2.3",
"@astrojs/sitemap": "^3.3.0",
"@fontsource-variable/work-sans": "^5.2.5",
"astro": "^5.12.7",
"astro-critical-css": "^0.0.7",
"astro": "^5.6.1",
"astro-meta-tags": "^0.3.1",
"autoprefixer": "^10.4.21",
"autoprefixer": "^10.4.19",
"iconoir": "^7.10.1",
"postcss-preset-env": "^10.1.5",
"typescript": "^5.8.3"
},
"devDependencies": {
"unlighthouse": "^0.17.2"
"typescript": "^5.5.3"
},
"browserslist": [
"last 2 versions",

View file

@ -1,6 +0,0 @@
# Spawning AI
# Prevent datasets from using the following file types
User-Agent: *
Disallow: /
Disallow: *

View file

@ -1,61 +1,8 @@
User-agent: *
Disallow: /assets/data/
User-agent: AI2Bot
User-agent: Ai2Bot-Dolma
User-agent: aiHitBot
User-agent: Amazonbot
User-agent: anthropic-ai
User-agent: Applebot
User-agent: Applebot-Extended
User-agent: Brightbot 1.0
User-agent: Bytespider
User-agent: CCBot
User-agent: ChatGPT-User
User-agent: Claude-Web
User-agent: ClaudeBot
User-agent: cohere-ai
User-agent: cohere-training-data-crawler
User-agent: Cotoyogi
User-agent: Crawlspace
User-agent: Diffbot
User-agent: DuckAssistBot
User-agent: FacebookBot
User-agent: Factset_spyderbot
User-agent: FirecrawlAgent
User-agent: FriendlyCrawler
User-agent: Google-Extended
User-agent: GoogleOther
User-agent: GoogleOther-Image
User-agent: GoogleOther-Video
User-agent: GPTBot
User-agent: iaskspider/2.0
User-agent: ICC-Crawler
User-agent: ImagesiftBot
User-agent: img2dataset
User-agent: imgproxy
User-agent: ISSCyberRiskCrawler
User-agent: Kangaroo Bot
User-agent: Meta-ExternalAgent
User-agent: Meta-ExternalFetcher
User-agent: NovaAct
User-agent: OAI-SearchBot
User-agent: omgili
User-agent: omgilibot
User-agent: Operator
User-agent: PanguBot
User-agent: Perplexity-User
User-agent: PerplexityBot
User-agent: PetalBot
User-agent: Scrapy
User-agent: SemrushBot-OCOB
User-agent: SemrushBot-SWA
User-agent: Sidetrade indexer bot
User-agent: TikTokSpider
User-agent: Timpibot
User-agent: VelenPublicWebCrawler
User-agent: Webzio-Extended
User-agent: YouBot
User-agent: Bytespider
Disallow: /
Sitemap: https://firq.dev/sitemap-index.xml

Binary file not shown.

Before

(image error) Size: 238 KiB

Binary file not shown.

Before

(image error) Size: 22 KiB

After

(image error) Size: 20 KiB

Before After
Before After

View file

@ -20,7 +20,7 @@
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: 'Work Sans Variable', system-ui, 'Segoe UI', sans-serif;
font-family: 'Work Sans Variable', sans-serif;
max-width: min(87.5%, 360px);
background-color: var(--c-primary-background);
color: var(--c-primary-text);

View file

@ -27,6 +27,9 @@ const date = new Date(pubdate).toLocaleDateString('en-GB', options_date)
</a>
<style>
* {
transition: all var(--a-time-default) var(--a-animation-1);
}
.circle {
display: none;
}
@ -74,7 +77,6 @@ const date = new Date(pubdate).toLocaleDateString('en-GB', options_date)
min-height: 100%;
border: 2px solid var(--c-primary-background);
border-radius: 1.25rem;
transition: border-color var(--a-time-default) var(--a-animation-1);
}
a:hover article {
@ -94,17 +96,13 @@ const date = new Date(pubdate).toLocaleDateString('en-GB', options_date)
border-width: 0.25rem;
border-color: var(--c-secondary-background);
border-radius: 40%;
transition:
height var(--a-time-short) var(--a-animation-1),
width var(--a-time-short) var(--a-animation-1),
margin-right var(--a-time-short) var(--a-animation-1),
transform var(--a-time-short) var(--a-animation-1);
transition: all var(--a-time-short) var(--a-animation-1);
}
a:hover > .circle {
height: 1.25rem;
width: 1.25rem;
transform: translateX(-0.125rem);
translate: -0.125rem;
margin-right: 4px;
}

View file

@ -28,13 +28,16 @@ const mlb_image = mlb ? 'mlbalign' : 'hidemlb'
</article>
<style>
* {
transition: all var(--a-time-default) var(--a-animation-1);
}
.hidemlb {
display: none;
height: 1em;
}
article {
transition: border-color var(--a-time-default) var(--a-animation-1);
border-radius: 1.25rem;
border: 2px var(--c-primary-background) solid;
background-color: var(--c-primary-background);

View file

@ -32,14 +32,18 @@ const loadedImage = plsLoadImage(images, imagePath)
</a>
<style>
* {
transition: all var(--a-time-default) var(--a-animation-1);
}
.heading {
display: flex;
height: 3.5rem;
height: fit-content;
min-height: 2.5rem;
line-height: 1.75rem;
font-size: 24px;
color: var(--c-primary-text);
max-width: 150px;
max-width: 200px;
padding-bottom: 0.3rem;
font-weight: 500;
margin: 0px;
@ -48,7 +52,6 @@ const loadedImage = plsLoadImage(images, imagePath)
}
a {
transition: border-color var(--a-time-default) var(--a-animation-1);
border-radius: 1rem;
display: flex;
flex-wrap: wrap;

View file

@ -15,8 +15,11 @@ const target = url.startsWith("/") ? "" : "_blank"
</a>
<style>
* {
transition: all var(--a-time-default) var(--a-animation-1);
}
a {
transition: border-color var(--a-time-default) var(--a-animation-1);
align-items: center;
justify-content: center;
display: flex;

View file

@ -47,7 +47,7 @@ let loading: "eager" | "lazy" = index <= 3 ? "eager" : "lazy";
<style>
article:hover {
transition: border-color var(--a-time-default) var(--a-animation-1), transform var(--a-time-default) var(--a-animation-1);
transition: all var(--a-time-default) var(--a-animation-1);
transform: scale(1);
border-color: var(--c-accent-1);
}
@ -97,7 +97,7 @@ let loading: "eager" | "lazy" = index <= 3 ? "eager" : "lazy";
article:hover .expand-on-hover {
transform: scaleY(1);
border-color: var(--c-accent-1);
transition: border-color var(--a-time-default) var(--a-animation-1), transform var(--a-time-default) var(--a-animation-1);
transition: all var(--a-time-default) var(--a-animation-1);
}
.bond-ce {

View file

@ -57,6 +57,9 @@ const hasuser = user !== undefined ? 'display: flex' : 'display: none'
</a>
<style>
* {
transition: all var(--a-time-default) var(--a-animation-1);
}
a {
text-decoration: none;
display: flex;
@ -75,7 +78,6 @@ const hasuser = user !== undefined ? 'display: flex' : 'display: none'
}
a:hover {
transition: border-color var(--a-time-default) var(--a-animation-1), transform var(--a-time-default) var(--a-animation-1);
transform: scale(1);
border-color: var(--c-accent-1);
z-index: 10;
@ -169,7 +171,7 @@ const hasuser = user !== undefined ? 'display: flex' : 'display: none'
a:hover .expand-on-hover {
transform: scaleY(1);
transition: border-color var(--a-time-default) var(--a-animation-1), transform var(--a-time-default) var(--a-animation-1), background-color var(--a-time-default) var(--a-animation-1);
transition: all var(--a-time-default) var(--a-animation-1);
background-color: var(--c-primary-background);
border-color: var(--c-accent-1);
}

View file

@ -28,12 +28,15 @@ const loadedImage = plsLoadImage(images_logos, imagePath)
</a>
<style>
* {
transition: all var(--a-time-default) var(--a-animation-1);
}
a {
text-decoration: none;
}
article {
transition: border-color var(--a-time-default) var(--a-animation-1);
display: flex;
position: relative;
align-items: center;

View file

@ -33,7 +33,7 @@ const display = fadeout ? "": "display: none"
a {
font-weight: 500;
font-family: 'Work Sans Variable', system-ui, 'Segoe UI', sans-serif;
font-family: 'Work Sans Variable', sans-serif;
color: var(--c-primary-text);
margin: 1rem 0px 0px;
padding: 0.5rem 0.75rem;
@ -41,11 +41,12 @@ const display = fadeout ? "": "display: none"
background: var(--c-primary-background);
border-radius: 10px;
border: 2px var(--c-accent-1) solid;
transition: border-color var(--a-time-default) var(--a-animation-1);
transition: all var(--a-time-default) var(--a-animation-1);
}
a:hover {
border-color: var(--c-accent-1-alt);
transition: all var(--a-time-default) var(--a-animation-1);
}
.fade {
@ -65,7 +66,6 @@ const display = fadeout ? "": "display: none"
@supports (background-clip: text) {
a:hover .fancy {
transition: color var(--a-time-default) var(--a-animation-1);
background: linear-gradient(125deg, var(--c-accent-1), var(--c-accent-1-alt), var(--c-accent-2) );
background-clip: text;
color: transparent;

View file

@ -19,6 +19,9 @@ const icon_src_url = `url("${icon.src}")`
</a>
<style define:vars={{ icon_src_url }}>
* {
transition: all var(--a-time-default) var(--a-animation-1);
}
a {
display: flex;
align-items: center;
@ -31,11 +34,9 @@ const icon_src_url = `url("${icon.src}")`
}
a:hover {
transition: border-color var(--a-time-default) var(--a-animation-1);
border-color: var(--c-accent-1);
& .icon {
transition: background-color var(--a-time-default) var(--a-animation-1);
background-color: var(--c-accent-1);
background: var(--c-accent-1);
}
}

View file

@ -40,13 +40,14 @@ let extraattributes = navtype === 'mobile' ? { tabindex: '0' } : {}
<style define:vars={{ icon_src_url }}>
* {
transition: color var(--a-time-default) var(--a-animation-1), background-color var(--a-time-default) var(--a-animation-1);
transition: all var(--a-time-default) var(--a-animation-1);
}
li {
align-items: center;
justify-content: center;
text-align: left;
display: flex;
width: 200px;
}
li > a {
@ -83,10 +84,4 @@ let extraattributes = navtype === 'mobile' ? { tabindex: '0' } : {}
width: 1.4em;
height: 1.4em;
}
@media (min-width: 304px) {
li {
width: 200px;
}
}
</style>

View file

@ -1,16 +0,0 @@
---
export interface Props {
height?: string
}
const { height } = Astro.props
const ph_height = height || "5rem"
---
<div class="placeholder"></div>
<style define:vars={{ph_height}}>
.placeholder {
visibility: hidden;
width: 100%;
height: var(--ph_height);
}
</style>

View file

@ -23,7 +23,7 @@ const text = buttontext || baseurl
<style>
a {
font-weight: 500;
font-family: 'Work Sans Variable', system-ui, 'Segoe UI', sans-serif;
font-family: 'Work Sans Variable', sans-serif;
color: var(--c-primary-text);
margin: 1rem 0px 0px;
padding: 0.5rem 0.75rem;
@ -37,7 +37,6 @@ const text = buttontext || baseurl
}
a:hover {
transition: border-color var(--a-time-default) var(--a-animation-1);
border-color: var(--c-accent-1);
}
@ -66,18 +65,14 @@ const text = buttontext || baseurl
padding-top: 2rem;
font-size: 3.25rem;
font-weight: 700;
font-family: 'Work Sans Variable', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
font-size-adjust: 0.52;
line-height: 1.4;
font-family: 'Work Sans Variable', sans-serif;
}
.sub {
margin: 0 .5rem;
font-size: 0.9rem;
font-weight: 500;
font-family: 'Work Sans Variable', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
font-size-adjust: 0.52;
line-height: 1.4;
font-family: 'Work Sans Variable', sans-serif;
}
.head {

View file

@ -34,20 +34,14 @@ import LinkContainer from "../links/linkContainer.astro";
padding-top: 2rem;
font-size: 3rem;
font-weight: 700;
font-family: 'Work Sans Variable', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
font-size-adjust: 0.52;
line-height: 1.4;
min-height: 4.8rem;
font-family: 'Work Sans Variable', sans-serif;
}
.sub {
font-size: 1rem;
font-weight: 400;
font-family: 'Work Sans Variable', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
font-size-adjust: 0.52;
line-height: 1.4;
font-family: 'Work Sans Variable', sans-serif;
width: 97.5%;
min-height: 1.5rem;
}
.fancy {

View file

@ -5,9 +5,9 @@ description: 'A handful of observations from the cernunnos fight'
author: 'Requiem & Firq'
tags: ['fgo', 'lostbelt 6', 'cernunnos']
---
import thumbnail_firq from "@assets/thumbnails/WrHudtdfivA.jpg"
import thumbnail_requiem from "@assets/thumbnails/O1f-go7uJQM.jpg"
import YoutubeEmbed from "@components/youtubeEmbed.astro"
import thumbnail_firq from "../../assets/thumbnails/WrHudtdfivA.jpg"
import thumbnail_requiem from "../../assets/thumbnails/O1f-go7uJQM.jpg"
import YoutubeEmbed from "../../components/youtubeEmbed.astro"
## Foreword

View file

@ -63,6 +63,7 @@ COPY --from=build /app/dist /public
COPY --from=build /app/serve.json /public/serve.json
RUN rm -r /public/assets/data/
ENV PORT 8081
EXPOSE 8081
CMD [ "serve", "public/", "-p", "8081" ]
@ -115,8 +116,7 @@ One last hurdle was, ironically, Gitlab. Specifically, it was their `lighthouse`
After some testing, I decided to just make my own version of the `lighthouse` container, without the user but with `unlighthouse` preinstalled (this also helps with processing times, as puppeteer takes a good amount of time to install each run).
<details>
<summary>`Dockerfile` (really simple again)</summary>
The `Dockerfile` can be found below (really simple again):
```dockerfile
FROM node:20.10.0-bookworm
@ -131,8 +131,6 @@ RUN apt-get update && apt-get -y install --no-install-recommends chromium=${CHRO
RUN npm install @unlighthouse/cli puppeteer
```
</details>
With this container, the CI step to actually run the tests became a lot easier:
```yaml

View file

@ -60,9 +60,7 @@ deploy-site:
```
First installing ssh and readying everything from the CI variables, this was a big change from the Pages configuration. But there was one thing that really bothered me: The ssh call. If you look at it, you can see that I left out the commands that are passed to `ssh`. There is a good reason: These are waaaay too long.
<details>
<summary>commands formatted as a shell script</summary>
If displayed as separated shell calls, they would look something like this:
```shell
screen -X -S website-firq-npx kill;
@ -80,8 +78,6 @@ cd ~;
screen -S website-firq-npx -dm npx serve public/ -p 9000 -c serve.json"
```
</details>
With the following directory structure on the remote host, this can easily be explained:
```plain
@ -176,8 +172,7 @@ After completing, the new stage begins.
Since the files are now built on the GitLab server and not in the Proxmox instance, the files need to be moved over during the pipeline. This can be achieved by a great utility known as `rsync`. It can be used for sending data via ssh to any client that supports it, which makes it the ideal tool for my use-case.
I also recommend looking <a href="https://blog.mitsunee.com/post/copy-steam-games-rsync" target="_blank" rel="noopener noreferrer">at this blog post from Mitsunee</a> detailing how to use `ssh` and `rsync` to sync game libraries to the Steam Deck with ease.
<details>
<summary>.gitlab-ci.yml</summary>
In my case, the resulting `.gitlab-ci.yml` looked like this:
```yaml
deploy-site:
@ -206,8 +201,6 @@ deploy-site:
- ssh $DEPLOY_USER@$DEPLOY_HOST "screen -S website-firq-npx -dm npx serve public/ -p 9000 -c serve.json"
```
</details>
First, you have the rsync and ssh setup, which now also creates a new ssh-config for this. This really reduces the amount of arguments that need to be passed to ssh and rsync, making the whole flow less error-prone. My main issue with setting this up was to find out how to structure this, as I needed to setup ssh from the pipeline
without causing any weird issues. But by using a custom ssh-key and config, it became really easy to get this to run.

View file

@ -5,8 +5,8 @@ description: 'Blog post talking about instant death in FGO and how you can take
author: 'Firq'
tags: ['fgo', 'games']
---
import thumbnail from "@assets/thumbnails/UwbNp_dB_VU.jpg"
import YoutubeEmbed from "@components/youtubeEmbed.astro"
import thumbnail from "../../assets/thumbnails/UwbNp_dB_VU.jpg"
import YoutubeEmbed from "../../components/youtubeEmbed.astro"
> **Disclaimer**<br/>
> While writing this, Requiem and I faced a bit of a challenge concerning death rate calculations. Case in point is the passive "Item Construction"

View file

@ -1,237 +0,0 @@
---
title: 'Unlighthouse - Easy Website Testing'
pubDate: 2025-04-12
description: 'How I am automatically testing my site deployments using unlighthouse'
author: 'Firq'
tags: [ 'unlighthouse', 'coding' ]
---
Since one of the first iterations of this site, I was using Google Lighthouse to benchmark my results - and it really paid off at times. Missing ARIA roles, bad color contrast, the fact that the default Youtube embed is actually not that great - all of this was exposed by Lighthouse over time.
How this whole thing evolved and how I am currently using Unlighthouse to make all of this run even smoother will be covered in this article.
## Unlighthouse
First of all, I want to give a big shout-out to <a href="https://harlanzw.com/" target="_blank" rel="noopener noreferrer" >Harlan Wilton (harlan-zw)</a> for making `unlighthouse` - it makes analyzing larges sites so much easier. The package can be installed from `npm` and enables you to easily run Lighthouse for each of your subpages - even from a CI environment. This is a huge improvement given that the best alternative is the `Lighthouse` docker container that is provided by GitLab.
For ease of use, I also build my own version of that GitLab docker container, but with unlighthouse instead. This allows me to easily use it in other CI jobs, with pinned dependencies and resistance against random changes. However, it also requires me to regularly check for updated chromium versions, as this is usually what causes issues when updating the container.
The container is <a href="https://forgejo.neshweb.net/CI-Docker-Images/unlighthouse" target="_blank" rel="noopener noreferrer" >available from here</a> and can be pulled with the following url:
```shell
docker pull forgejo.neshweb.net/ci-docker-images/unlighthouse:latest
```
<details>
<summary>Dockerfile for the unlighthouse container</summary>
```dockerfile
FROM node:22.14-bookworm
LABEL authorname="firq"
LABEL description="unlighthouse container for ci-based lighthouse testing"
WORKDIR /unlighthouse
ENV CHROMIUM_VERSION="135.0.7049.84-1~deb12u1"
ENV UNLIGHTHOUSE_VERSION="0.16.3"
ENV NODE_ENV='production'
# Update path so executable can be run globally
ENV PATH="/unlighthouse/node_modules/.bin:${PATH}"
RUN apt-get update && apt-get -y install --no-install-recommends chromium=${CHROMIUM_VERSION} procps && rm -rf /var/lib/apt/lists/*
RUN npm install @unlighthouse/cli@${UNLIGHTHOUSE_VERSION} puppeteer
```
</details>
## Automation with Forgejo
However, running `unlighthouse` manually after each page build is too much of a hassle - so it was time to automate it. For that reason, I created a corresponding actions job that would run the checks each time a preview version of the site was being built.
In the beginning, I would run the test against the actual deployed instance, but with time it became increasingly obvious that this would be inefficient, mainly because I would run this using two different tags (`x.x.x-pre.x` and `x.x.x-ulh.x`) to trigger the corresponding CI runs.
Regarding this, I decided to instead run everything using a service container. This means that the `unlighthouse` job runs in the main job task, while the site is running as a service container in parallel. This allows access to the container using an alias (`website`) instead of a URL or IP address. (Note: The yaml is a bit reduced, meaning lines that only provide visual context in the UI are removed).
```yaml
jobs:
unlighthouse:
runs-on: docker
container:
image: forgejo.neshweb.net/ci-docker-images/unlighthouse:0.16.3
services:
website:
image: forgejo.neshweb.net/firq/firq-dev-website:${{ inputs.containertag }}
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
- run: |
while [ "$(curl -o /dev/null -s -w '%{http_code}' http://website:8081)" -ne 200 ];
do echo "Waiting...";
sleep 5;
done;
- run: unlighthouse-ci --site "http://website:8081"
- run: find ./unlighthouse-reports -type f | xargs sed -i "s|http://website:8081|https://preview.firq.dev|g";
```
So what is happening here? The first few lines are just there for configuring the runner and starting up the service-container. The container tag is passed from the upstream pipeline, making this setup a lot more flexible. Afterwards, two things happen: The code in the git repo gets checked out, and the job gets put on hold until the service-container is available. This is necessary, as sometimes the service container takes a few seconds to start up.
Afterwards, `unlighthouse` runs against the given site inside the service-container, using the local config for any other settings. After the run concludes, the artifacts inside the `unlighthouse-reports` folder get search-and-replaced once to change the url from the service-container to the preview site. This is mainly for visual consistency, but also has the benefit of making the "Check with PageSpeedInsights" button work.
To enable this to work more fluently, I decided to run the `unlighthouse` CI as a downstream pipeline, which was triggered by one of the CI jobs after the `build-and-push` step concludes. This also allows me to easily pass the new container name to the downstream pipeline for usage.
```yaml
run-unlighthouse:
needs: [ build-site ]
if: success()
runs-on: docker
steps:
- name: Launch workflow
run: |
payload="{\"ref\": \"${GITHUB_REF_NAME}\", \"inputs\": { \"containertag\": \"${GITHUB_REF_NAME}\" }}"
curl -X "POST" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: token ${GITHUB_TOKEN}" \
-d "${payload}" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/workflows/unlighthouse.yml/dispatches" -v
```
And with that, the general CI setup concludes. I won't go into too much detail about how the deployment of the reports works, as it is similar to the main site. If you are interested, see <a href="https://forgejo.neshweb.net/Firq/firq-dev-website/src/branch/dev" target="_blank" rel="noopener noreferrer" >the repository linked here</a> for further details.
## Fine-tuning the config
After setting everything up, it took some good time until I managed to fine-tune the configuration that unlighthouse uses. Don't get me wrong, running with the defaults would probably also work, but from my experience the defaults are set so strict that a perfect score is unobtainable - even if for example <a href="https://pagespeed.web.dev/" target="_blank" rel="noopener noreferrer" >PageSpeedInsights</a> doesn't see any issues.
My strategy for the config was to copy the settings that PageSpeedInsights uses, as this seemed like a good baseline for testing. This means that the Lighthouse settings look like this:
```typescript
lighthouseOptions: {
throttlingMethod: 'devtools',
throttling: {
cpuSlowdownMultiplier: 4,
requestLatencyMs: 150,
downloadThroughputKbps: 1638.4,
uploadThroughputKbps: 1638.4,
},
screenEmulation: { width: 412, height: 823, deviceScaleFactor: 1.75 },
skipAudits: [ 'is-on-https', 'redirects-http', 'uses-http2' ],
}
```
As mentioned previously, as I am using a docker network, I can't easily use HTTPS and HTTP/2. Given that, I decided to disable the corresponding audits, as I know for a fact that this always works for the real deployment.
The other configuration steps are for Puppeteer, which handles the simulation of the site in a CI environment. This is really straightforward:
```typescript
puppeteerOptions: {
args: [ '--no-sandbox', '--disable-setuid-sandbox' ]
},
puppeteerClusterOptions: {
maxConcurrency: 1
},
```
`maxConcurrency` ensures that there is only one inspection task running at the time, as having multiple run in parallel results in some weird situations where the report fails to correctly generate.
The rest of the configuration is for `unlighthouse` itself, regarding both the scanner and the ci environment, as well as the configuration of outputs and sites to check
```typescript
ci: {
budget: 50,
buildStatic: true,
},
scanner: {
sitemap: true,
dynamicSampling: false,
samples: 3,
},
outputPath: 'unlighthouse-reports',
cache: true,
urls
```
However, there is one interesting entry there: `urls`. This is a dynamic list of URLs generated from the sitemap, which happens earlier in the config (what a blessing `export default async` config-files are). This is necessary, as scraping the site from the sitemap directly is not working in my environment (the crawler finds `https://preview.firq.dev`, while I need `http://website:8081` for it to run). After some digging, I raised that usecase with Harlan, who proceeded to enable me to do exactly that in no time (thank you again for doing this - <a href="https://github.com/harlan-zw/unlighthouse/issues/248" target="_blank" rel="noopener noreferrer" >see issue 248 here</a>)
This is where the magic snippet comes in, which 1. fetches the sitemap, 2. replaces the URLs and 3. fetches each of the URLs once to warm up the `serve` webserver to ensure that the server-caching correctly works (improves the performance by a lot).
```typescript
const sitemap = await (await fetch('http://website:8081/sitemap-0.xml')).text();
const urls = sitemap.match(/<loc>(.*?)<\/loc>/g)!.map(
(loc) => loc.replace(/<\/?loc>/g, '').replace(/https:\/\/firq.dev/g, 'http://website:8081')
);
for (const url of urls) { await fetch(url) };
```
Afterwards, `urls` can be used in the config to provide `unlighthouse` with a list of urls to check - already replaced and modified to work with the docker network.
<details>
<summary>View the whole config here</summary>
(It's also really nice that unlighthouse provides type hints for the config, makes figuring out where goes what a lot easier).
```typescript
import type { UserConfig } from 'unlighthouse'
export default async (): Promise<UserConfig> => {
/* fetch sitemap from debug container */
const sitemap = await (await fetch('http://website:8081/sitemap-0.xml')).text();
/* format URLs to work with debug container */
const urls = sitemap.match(/<loc>(.*?)<\/loc>/g)!.map(
(loc) => loc.replace(/<\/?loc>/g, '').replace(/https:\/\/firq.dev/g, 'http://website:8081')
);
/* ensure serve is already "warm", preventing startup lag that reduces performance */
for (const url of urls) { await fetch(url) };
/* actual config */
return {
lighthouseOptions: {
throttlingMethod: 'devtools',
throttling: {
cpuSlowdownMultiplier: 4,
requestLatencyMs: 150,
downloadThroughputKbps: 1638.4,
uploadThroughputKbps: 1638.4,
},
screenEmulation: {
width: 412,
height: 823,
deviceScaleFactor: 1.75,
},
skipAudits: [ 'is-on-https', 'redirects-http', 'uses-http2' ],
},
puppeteerOptions: {
args: [ '--no-sandbox', '--disable-setuid-sandbox' ],
},
puppeteerClusterOptions: {
maxConcurrency: 1
},
ci: {
budget: 50,
buildStatic: true,
},
scanner: {
sitemap: true,
dynamicSampling: false,
samples: 3,
},
outputPath: 'unlighthouse-reports',
cache: true,
urls
}
}
```
</details>
## What's next?
After setting this whole thing up over the course of multiple months, with a variety of issues and shortcomings along the way, I hope that this is now ready for good.
I will, however, write a Forgejo Action for me to reuse in the future, as this would enable me to easily test other sites with the same concept.
If you want to check out the whole thing in action, check out <a href="https://forgejo.neshweb.net/Firq/firq-dev-website/src/branch/dev" target="_blank" rel="noopener noreferrer" >the website repository here </a>. In addition, you can find the reports that get generated <a href="https://unlighthouse.firq.dev" target="_blank" rel="noopener noreferrer" >here at unlighthouse.firq.dev</a>
Anyway, I wish I gave you an interesting insight in how Unlighthouse ensures good site quality - and how YOU can also profit from website testing in your CI.

View file

@ -15,12 +15,6 @@
"name": "Summer Skadi",
"origin": "Fate/Grand Order",
"imageFile": "summerskadi.png",
"link": "https://youtu.be/qmR4s3DHZXw"
},
{
"name": "Summer Scathach",
"origin": "Fate/Grand Order",
"imageFile": "summerscathach.png",
"link": "https://youtu.be/k0E3kgx3eMA"
"link": "https://twitter.com/firq_ow/status/1816355501216596073"
}
]

View file

@ -1,6 +1,6 @@
---
import '@fontsource-variable/work-sans'
import workSans from '@fontsource-variable/work-sans/files/work-sans-latin-wght-normal.woff2?url'
import workSans from '@fontsource-variable/work-sans/files/work-sans-latin-wght-normal.woff2'
import Navbar from '@components/navbar/navbar.astro'
import NavbarEntry from '@components/navbar/navbarEntry.astro'
@ -123,7 +123,7 @@ const mapped_navdata = navdata.map((item) => ({
/* Fonts */
--f-default: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
--f-title: 'Work Sans Variable', system-ui, 'Segoe UI', sans-serif;
--f-title: 'Work Sans Variable', sans-serif;
font-family: var(--f-default);
@ -144,8 +144,6 @@ const mapped_navdata = navdata.map((item) => ({
/* Scrollbar */
scrollbar-color: var(--c-secondary-background) var(--c-primary-background);
/* Font Size Adjusting to prevent LCP issue */
}
body {

View file

@ -29,6 +29,7 @@ const hidden = titleHidden || false ? "visually-hidden" : ""
align-self: center;
display: flex;
flex-flow: row wrap;
padding: 1em;
position: relative;
}
h1 {

View file

@ -3,7 +3,7 @@ import SmallTitle from '@components/titles/smallTitle.astro'
import Layout from './Layout.astro'
const { frontmatter } = Astro.props
const title = `${frontmatter.title} - firq.dev`
const title = `${frontmatter.title} - Firq FGO Site`
const description = frontmatter.description
const options_date: Intl.DateTimeFormatOptions = {
year: 'numeric',

View file

@ -95,7 +95,7 @@ const line = displayLine ? "flex" : "none"
align-items: stretch;
align-content: center;
justify-content: space-around;
padding: 1em 0px;
padding: 1em;
color: var(--c-primary-text);
font-size: 1em;
position: relative;
@ -114,9 +114,6 @@ const line = displayLine ? "flex" : "none"
margin-left: 3rem;
font-size: 1.85rem;
}
.wrapper {
padding: 1em;
}
}
@media (min-width: 1100px) {
.wrapper {
@ -135,7 +132,7 @@ const line = displayLine ? "flex" : "none"
margin-left: 1.75rem;
}
.line {
margin-left: 2.625rem;
margin-left: 2.6rem;
height: calc(100% + 6rem);
translate: 0px -2rem;
}

View file

@ -19,7 +19,7 @@ const { title } = Astro.props
font-size: 24px;
font-weight: 700;
line-height: 32px;
font-family: 'Work Sans Variable', sans-serif;
font-family: 'Work Sans Adaptive', sans-serif;
width: max-content;
margin: 0 auto 0.5rem;
&:after {

View file

@ -44,9 +44,9 @@ const release = `https://forgejo.neshweb.net/Firq/firq-dev-website/releases/tag/
rel="noopener noreferrer">Mitsunee</a
> for the support when building this site. I could not have done it without
you 🧡 Check out <a
href="https://mitsunee.com"
href="https://fgo.mitsunee.com"
target="_blank"
rel="noopener noreferrer">her own website here</a
rel="noopener noreferrer">FGO Timers here</a
>
<div class="sticky-image-wrapper">
<Image src={foxcat} alt="Best Foxcat" />
@ -64,7 +64,7 @@ const release = `https://forgejo.neshweb.net/Firq/firq-dev-website/releases/tag/
>his site here</a
>
<div class="sticky-image-wrapper">
<Image src={okita} alt="" style="height:67px" />
<Image src={okita} alt="" style="height:60px" />
</div>
</div>
<br />

View file

@ -8,7 +8,7 @@ const description = "Error. This shouldn't happen :/"
---
<Layout
title="404 - firq.dev"
title="404 - Firq FGO Site"
currentpage="404"
descriptionOverride={description}
>
@ -31,7 +31,7 @@ const description = "Error. This shouldn't happen :/"
align-items: center;
}
h2 {
font-family: 'Work Sans Variable', system-ui, 'Segoe UI', sans-serif;
font-family: 'Work Sans Variable', sans-serif;
font-weight: 500;
color: var(--c-primary-text);
font-size: 1.25rem;
@ -45,7 +45,7 @@ const description = "Error. This shouldn't happen :/"
text-align: center;
width: fit-content;
font-weight: 500;
font-family: 'Work Sans Variable', system-ui, 'Segoe UI', sans-serif;
font-family: 'Work Sans Variable', sans-serif;
color: var(--c-primary-text);
margin: 1rem 0px 0px;
padding: 0.5rem 0.75rem;

View file

@ -0,0 +1,17 @@
---
import Layout from '../layouts/Layout.astro';
---
<Layout title="Firq FGO Site">
<Navbar/>
<Title/>
<Divider/>
<HomeLayout>
<AboutMe/>
<Favourites/>
</HomeLayout>
</Layout>
<style>
</style>

View file

@ -7,7 +7,6 @@ import CustomFooter from '@layouts/customFooter.astro'
import TechnologyCard from '@components/cards/technologyCard.astro'
import technologydata from '@datafiles/technologydata.json'
import SmallTitle from '@components/titles/smallTitle.astro'
import Placeholder from '@components/placeholder.astro'
const description =
"A summary of the technologies used as well as my contact information. You'll also find disclaimers and thank you notes for the people that helped me."
@ -16,7 +15,7 @@ const subtext =
---
<Layout
title="About - firq.dev"
title="About - Firq FGO Site"
currentpage="about"
descriptionOverride={description}
>
@ -27,7 +26,6 @@ const subtext =
<ContactSection title="Disclaimers">
<CustomFooter />
</ContactSection>
<Placeholder height="1.5rem"/>
</Layout>
<style></style>

View file

@ -17,7 +17,7 @@ blogEntries.sort(
---
<Layout
title="Blog - firq.dev"
title="Blog - Firq FGO Site"
currentpage="blog"
descriptionOverride={description}
>

View file

@ -1,7 +1,6 @@
---
import FavouriteCard from '@components/cards/favouriteCard.astro'
import ProjectCard from '@components/cards/projectCard.astro'
import Placeholder from '@components/placeholder.astro'
import SmallTitle from '@components/titles/smallTitle.astro'
import favouritesdata from '@datafiles/favouritesdata.json'
import BaseSection from '@layouts/baseSection.astro'
@ -28,23 +27,21 @@ const projects = [
const data = structuredClone(favouritesdata);
data.map((item) => { if (item.name !== "Summer Scathach") {item.origin = "First 120 on NA"} else {item.origin = "After Forever"} })
data.map((item) => { item.origin = "First 120 on NA" })
---
<Layout
title="FGO - firq.dev"
title="FGO - Firq FGO Site"
currentpage="fgo"
descriptionOverride={description}
>
<SmallTitle maintext="FGO Infos" subtext={subtext} fadeout={true} />
<SmallTitle maintext="FGO Information" subtext={subtext} fadeout={true} />
<BaseSection title="Highlights">
{data.map((item) => <FavouriteCard {...item} />)}
</BaseSection>
<Placeholder height="1.5rem"/>
<BaseSection title="Infos" titleHidden={true}>
{projects.map((item) => <ProjectCard {...item} />)}
</BaseSection>
<Placeholder height="1.5rem"/>
</Layout>
<style></style>

View file

@ -1,7 +1,6 @@
---
import AboutText from '@components/aboutText.astro'
import FavouriteCard from '@components/cards/favouriteCard.astro'
import Placeholder from '@components/placeholder.astro'
import Hi from '@components/titles/title.astro'
import favouritesdata from '@datafiles/favouritesdata.json'
import Layout from '@layouts/Layout.astro'
@ -12,19 +11,17 @@ const description =
---
<Layout
title="Home - firq.dev"
title="Home - Firq FGO Site"
currentpage="home"
descriptionOverride={description}
>
<Hi />
<BaseSection title="About me">
<Placeholder height="0px"/>
<AboutText/>
</BaseSection>
<BaseSection title="Favourites">
{favouritesdata.map((item) => <FavouriteCard {...item} />)}
</BaseSection>
<Placeholder height="1.5rem"/>
</Layout>
<style></style>

View file

@ -4,7 +4,6 @@ import ProjectCard from '@components/cards/projectCard.astro'
import projects from '@datafiles/projectdata.json'
import SmallTitle from '@components/titles/smallTitle.astro'
import BaseSection from '@layouts/baseSection.astro'
import Placeholder from '@components/placeholder.astro'
const description =
"My personal projects I am currently working on. Some of them are born from necessity, while others are for uni or similar."
@ -14,7 +13,7 @@ const subtext =
---
<Layout
title="Projects - firq.dev"
title="Projects - Firq FGO Site"
currentpage="projects"
descriptionOverride={description}
>
@ -22,7 +21,6 @@ const subtext =
<BaseSection title="Projects" titleHidden={true}>
{projects.map((item) => <ProjectCard {...item} />)}
</BaseSection>
<Placeholder height="1.5rem"/>
</Layout>
<style></style>

View file

@ -1,19 +1,20 @@
---
import CeCard from '@components/cards/ceCard.astro'
import ServantCard from '@components/cards/servantCard.astro'
import Placeholder from '@components/placeholder.astro'
import SmallTitle from '@components/titles/smallTitle.astro'
import cedata from '@datafiles/cedata.json'
import servantdata from '@datafiles/servantdata.json'
import Layout from '@layouts/Layout.astro'
import ServantSection from '@layouts/servantSection.astro'
import ServantCard from '@components/cards/servantCard.astro'
import servantdata from '@datafiles/servantdata.json'
import CeCard from '@components/cards/ceCard.astro'
import cedata from '@datafiles/cedata.json'
import SmallTitle from '@components/titles/smallTitle.astro'
const description =
'A list of all the servants and ces that Firq can offer up on support for TA.'
---
<Layout
title="Servants - firq.dev"
title="Servants - Firq FGO Site"
currentpage="fgo"
descriptionOverride={description}
>
@ -21,11 +22,17 @@ const description =
<ServantSection title="Servants">
{servantdata.map((item, index) => <ServantCard {...item} index={index} />)}
</ServantSection>
<Placeholder height="2.5rem"/>
<div class="placeholder"></div>
<ServantSection title="CEs">
{cedata.map((item) => <CeCard {...item} />)}
</ServantSection>
<Placeholder height="2.5rem"/>
<div class="placeholder"></div>
</Layout>
<style></style>
<style>
.placeholder {
visibility: hidden;
width: 100%;
height: 2.5rem;
}
</style>

View file

@ -12,7 +12,6 @@ import TaCard from '@components/cards/taCard.astro'
import tadata from '@datafiles/tadata.json'
import featured_data from '@datafiles/featureddata.json'
import FgotaHero from '@components/fgotaHero.astro'
import Placeholder from '@components/placeholder.astro'
import SmallTitle from '@components/titles/smallTitle.astro'
const important_data = tadata.filter(function (el) {
@ -35,7 +34,7 @@ const description = 'A collection of TAs previously completed be Firq.'
---
<Layout
title="TA Collection - firq.dev"
title="TA Collection - Firq FGO Site"
currentpage="fgo"
descriptionOverride={description}
>
@ -53,7 +52,13 @@ const description = 'A collection of TAs previously completed be Firq.'
>
{featured_data.map((item) => <TaCard {...item} />)}
</TaSection>
<Placeholder height="5rem"/>
<div class="placeholder"></div>
</Layout>
<style></style>
<style>
.placeholder {
visibility: hidden;
width: 100%;
height: 5rem;
}
</style>

View file

@ -1,52 +1,36 @@
import type { UserConfig } from '@unlighthouse/core'
export default async (): Promise<UserConfig> => {
/* fetch sitemap from debug container */
const sitemap = await (await fetch('http://website:8081/sitemap-0.xml')).text();
/* format URLs to work with debug container */
export default async () => {
const sitemap = await (await fetch('http://localhost:8081/sitemap-0.xml')).text()
const urls = sitemap.match(/<loc>(.*?)<\/loc>/g)!.map(
(loc) => loc.replace(/<\/?loc>/g, '').replace(/https:\/\/firq.dev/g, 'http://website:8081')
);
/* ensure serve is already "warm", preventing startup lag that reduces performance */
for (const url of urls) { await fetch(url) };
/* actual config */
(loc) => loc.replace(/<\/?loc>/g, '').replace(/https:\/\/firq.dev/g, 'http://localhost:8081')
)
return {
lighthouseOptions: {
throttlingMethod: 'simulate',
throttlingMethod: 'devtools',
throttling: {
rttMs: 150,
throughputKbps: 1638.4,
requestLatencyMs: 562.5,
downloadThroughputKbps: 1474.56,
uploadThroughputKbps: 675,
cpuSlowdownMultiplier: 4,
requestLatencyMs: 150,
downloadThroughputKbps: 1638.4,
uploadThroughputKbps: 1638.4,
},
screenEmulation: {
mobile: true,
width: 412,
height: 823,
deviceScaleFactor: 1.75,
disabled: false,
},
skipAudits: [ 'is-on-https', 'redirects-http', 'uses-http2' ],
},
puppeteerOptions: {
args: [ '--no-sandbox', '--disable-setuid-sandbox' ],
},
puppeteerClusterOptions: {
maxConcurrency: 1
args: ['--no-sandbox', '--disable-setuid-sandbox'],
},
ci: {
budget: 50,
buildStatic: true,
},
scanner: {
sitemap: false,
sitemap: true,
dynamicSampling: false,
samples: 1,
samples: 3,
},
outputPath: 'unlighthouse-reports',
cache: true,
urls
}
}