feat: Add services/static

This commit is contained in:
deytenit committed 2026-01-27 00:01:55 +03:00
1 parent bc87ca601e
commit fa977037d5
12 files changed
+459

No files matched your search

@@ -0,0 +1,134 @@
name: services/static - next/required
on:
push:
branches: [next]
paths:
- 'services/static/**'
- 'packages/ui/**'
- 'pnpm-lock.yaml'
concurrency:
group: ci-static-${{ github.ref_name }}
cancel-in-progress: true
jobs:
setup-stage:
name: Setup - Stage
runs-on: ubuntu-latest
outputs:
cache-hit: ${{ steps.cache-pnpm-store.outputs.cache-hit }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- name: Setup Node.js with pnpm cache
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Cache pnpm store
id: cache-pnpm-store
uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
lint-static:
name: Lint - Static
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint static service
run: pnpm --filter @ermnvldmr/static lint
typecheck-static:
name: Typecheck - Static
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck static service
run: pnpm --filter @ermnvldmr/static typecheck
build-static:
name: Build - Static Site
needs: setup-stage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9.12.3
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build static service
run: pnpm --filter @ermnvldmr/static build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: static-dist
path: services/static/dist/
retention-days: 1
deploy-s3:
name: Deploy - S3
needs: [lint-static, typecheck-static, build-static]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/next'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: static-dist
path: services/static/dist/
- name: Deploy to S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.STATIC_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.STATIC_AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.STATIC_AWS_REGION }}
S3_BUCKET: ${{ secrets.STATIC_S3_BUCKET }}
AWS_REGION: ${{ secrets.STATIC_AWS_REGION }}
S3_ENDPOINT_URL: ${{ secrets.STATIC_S3_ENDPOINT_URL }}
run: |
chmod +x ci/scripts/deploy-static.sh
./ci/scripts/deploy-static.sh
+53
View File
@@ -0,0 +1,53 @@
#!/bin/bash
set -e
echo "Deploying static service to S3..."
# Verify required environment variables
if [ -z "$S3_BUCKET" ]; then
echo "Error: S3_BUCKET environment variable is not set"
exit 1
fi
if [ -z "$AWS_REGION" ]; then
echo "Error: AWS_REGION environment variable is not set"
exit 1
fi
if [ -z "$S3_ENDPOINT_URL" ]; then
echo "Error: S3_ENDPOINT_URL environment variable is not set"
exit 1
fi
# Navigate to the dist directory
DIST_DIR="services/static/dist"
if [ ! -d "$DIST_DIR" ]; then
echo "Error: Build directory $DIST_DIR does not exist"
exit 1
fi
# Configure AWS region
export AWS_DEFAULT_REGION="$AWS_REGION"
ENDPOINT_URL="$S3_ENDPOINT_URL"
# Upload index.html
echo "Uploading index.html..."
aws s3 cp "$DIST_DIR/index.html" "s3://$S3_BUCKET/index.html" \
--endpoint-url="$ENDPOINT_URL" \
--content-type "text/html; charset=utf-8" \
--cache-control "max-age=300" \
--metadata-directive REPLACE
# Upload error.html
echo "Uploading error.html..."
aws s3 cp "$DIST_DIR/error.html" "s3://$S3_BUCKET/error.html" \
--endpoint-url="$ENDPOINT_URL" \
--content-type "text/html; charset=utf-8" \
--cache-control "max-age=300" \
--metadata-directive REPLACE
echo "Static files deployed successfully!"
echo "Deployed files:"
echo " - index.html"
echo " - error.html"
+26
View File
@@ -0,0 +1,26 @@
// @ts-check
import react from '@astrojs/react';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
site: 'https://static.ermnvldmr.com',
output: 'static',
build: {
format: 'file',
},
srcDir: './src',
integrations: [react()],
vite: {
plugins: [tailwindcss()],
resolve: {
alias: {
'@ermnvldmr/stl': new URL('../../packages/stl/src', import.meta.url).pathname,
'@ermnvldmr/ui': new URL('../../packages/ui/src', import.meta.url).pathname,
'#': new URL('./src/', import.meta.url).pathname,
},
},
},
});
+34
View File
@@ -0,0 +1,34 @@
{
"name": "@ermnvldmr/static",
"version": "1.0.0",
"description": "static.ermnvldmr.com landing pages",
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"lint": "eslint src --max-warnings 0",
"format": "prettier --write .",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist .turbo node_modules"
},
"dependencies": {
"@astrojs/react": "^4.2.5",
"@ermnvldmr/stl": "workspace:*",
"@ermnvldmr/ui": "workspace:*",
"astro": "^5.7.5",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"tailwindcss": "^4.1.4"
},
"devDependencies": {
"@ermnvldmr/eslint-config": "workspace:*",
"@tailwindcss/vite": "^4.1.4",
"eslint": "^9.25.1",
"prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.11",
"typescript": "^5.8.3"
}
}
+8
View File
@@ -0,0 +1,8 @@
/**
* Type definitions for the static service
*/
/// <reference path="../.astro/types.d.ts" />
interface PerformanceNavigationTiming {
responseStatus?: number;
}
+14
View File
@@ -0,0 +1,14 @@
@import "../../../packages/ui/src/styles/index.css";
@source "../../../packages/ui/src";
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
width: 100%;
}
@@ -0,0 +1,49 @@
---
/**
* Base layout for static service pages
*/
export interface Props {
title: string;
description: string;
}
const { title, description } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<script is:inline>
const getThemePreference = () => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
const isDark = getThemePreference() === 'dark';
document.documentElement.classList[isDark ? 'add' : 'remove']('dark');
if (typeof localStorage !== 'undefined') {
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class'],
});
}
</script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="robots" content="noindex,nofollow" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={Astro.url.href} />
</head>
<body>
<slot />
</body>
</html>
+56
View File
@@ -0,0 +1,56 @@
---
import { VStack, HStack } from '@ermnvldmr/ui';
import StaticLayout from '#layouts/StaticLayout/StaticLayout.astro';
import {
AUTHOR_EMAIL,
AUTHOR_NAME,
SITE_URL,
ERROR_TITLE,
ERROR_DESCRIPTION,
} from '#shared/constants';
import { getCurrentYear } from '#shared/utils';
import '#global.css';
---
<StaticLayout title={ERROR_TITLE} description={ERROR_DESCRIPTION}>
<main class="h-screen w-screen">
<VStack className="w-full h-full" align="center" justify="center">
<VStack align="start" gap={8}>
<h1 id="title">Exceptional Situation</h1>
<p id="message">
This location is unavailable at the moment — static content does not dwell here
</p>
<HStack justify="between" className="w-full">
<span>
<a href={`mailto:${AUTHOR_EMAIL}`}>{AUTHOR_NAME}</a>, {getCurrentYear()}
</span>
<span>
<a href={SITE_URL}>ermnvldmr.com</a>
</span>
</HStack>
</VStack>
</VStack>
</main>
<script>
import { getHttpStatusUrl } from '#shared/utils';
function isNavigationTiming(
entry: PerformanceEntry,
): entry is PerformanceNavigationTiming {
return entry.entryType === 'navigation';
}
const navigationData = window.performance.getEntries().find(isNavigationTiming);
if (navigationData?.responseStatus) {
const { responseStatus } = navigationData;
const link = document.createElement('a');
link.href = getHttpStatusUrl(responseStatus);
link.textContent = `#${responseStatus}`;
document.getElementById('title')?.append(' ', link);
}
</script>
</StaticLayout>
+35
View File
@@ -0,0 +1,35 @@
---
import { VStack, HStack } from '@ermnvldmr/ui';
import StaticLayout from '#layouts/StaticLayout/StaticLayout.astro';
import {
AUTHOR_EMAIL,
AUTHOR_NAME,
SITE_DESCRIPTION,
SITE_TITLE,
SITE_URL,
} from '#shared/constants';
import { getCurrentYear } from '#shared/utils';
import '#global.css';
---
<StaticLayout title={SITE_TITLE} description={SITE_DESCRIPTION}>
<main class="h-screen w-screen">
<VStack className="w-full h-full" align="center" justify="center">
<VStack align="start" gap={8}>
<h1>Static Content Dwells Here</h1>
<p>
Other pages direct you down specific paths to the content that resides here
</p>
<HStack justify="between" className="w-full">
<span>
<a href={`mailto:${AUTHOR_EMAIL}`}>{AUTHOR_NAME}</a>, {getCurrentYear()}
</span>
<span>
<a href={SITE_URL}>ermnvldmr.com</a>
</span>
</HStack>
</VStack>
</VStack>
</main>
</StaticLayout>
+17
View File
@@ -0,0 +1,17 @@
/**
* Site title for static landing pages
*/
export const SITE_TITLE = 'Static Storage - ermnvldmr.com';
/**
* Site description for static landing pages
*/
export const SITE_DESCRIPTION = 'Static file storage and content delivery';
export const AUTHOR_NAME = 'Vladimir Eremin';
export const AUTHOR_EMAIL = 'personal@ermnvldmr.com';
export const SITE_URL = 'https://ermnvldmr.com';
export const STATIC_URL = 'https://static.ermnvldmr.com';
export const ERROR_TITLE = 'Exceptional Situation - Static Storage';
export const ERROR_DESCRIPTION = 'This location is unavailable at the moment';
+19
View File
@@ -0,0 +1,19 @@
/**
* Returns the current year
*
* @returns {number} The current year
* @example
* getCurrentYear() // 2024
*/
export const getCurrentYear = (): number => new Date().getFullYear();
/**
* Returns MDN URL for a given HTTP status code
*
* @param {number | string} code - The HTTP status code
* @returns {string} The MDN URL for the status code
* @example
* getHttpStatusUrl(404) // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
*/
export const getHttpStatusUrl = (code: number | string): string =>
`https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/${code}`;
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"#*": ["./*"],
"@ermnvldmr/stl": ["../../../packages/stl/src"],
"@ermnvldmr/ui": ["../../../packages/ui/src"],
"@ermnvldmr/ui/*": ["../../../packages/ui/src/*"]
}
},
"include": [".astro/types.d.ts", "src/**/*"],
"exclude": ["dist", "node_modules", "**/*.config.*"]
}