feat: Add services/static
This commit is contained in:
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
|
||||
Executable
+53
@@ -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"
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Type definitions for the static service
|
||||
*/
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
|
||||
interface PerformanceNavigationTiming {
|
||||
responseStatus?: number;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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';
|
||||
@@ -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}`;
|
||||
@@ -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.*"]
|
||||
}
|
||||
Reference in new issue
Block a user