From fa977037d59e9cab810dde7141b6777703f6b499 Mon Sep 17 00:00:00 2001 From: Vladimir Eremin Date: Tue, 27 Jan 2026 00:01:55 +0300 Subject: [PATCH] feat: Add services/static --- .../services-static-next-required.yml | 134 ++++++++++++++++++ ci/scripts/deploy-static.sh | 53 +++++++ services/static/astro.config.mjs | 26 ++++ services/static/package.json | 34 +++++ services/static/src/env.d.ts | 8 ++ services/static/src/global.css | 14 ++ .../layouts/StaticLayout/StaticLayout.astro | 49 +++++++ services/static/src/pages/error.astro | 56 ++++++++ services/static/src/pages/index.astro | 35 +++++ services/static/src/shared/constants.ts | 17 +++ services/static/src/shared/utils.ts | 19 +++ services/static/tsconfig.json | 14 ++ 12 files changed, 459 insertions(+) create mode 100644 .github/workflows/services-static-next-required.yml create mode 100755 ci/scripts/deploy-static.sh create mode 100644 services/static/astro.config.mjs create mode 100644 services/static/package.json create mode 100644 services/static/src/env.d.ts create mode 100644 services/static/src/global.css create mode 100644 services/static/src/layouts/StaticLayout/StaticLayout.astro create mode 100644 services/static/src/pages/error.astro create mode 100644 services/static/src/pages/index.astro create mode 100644 services/static/src/shared/constants.ts create mode 100644 services/static/src/shared/utils.ts create mode 100644 services/static/tsconfig.json diff --git a/.github/workflows/services-static-next-required.yml b/.github/workflows/services-static-next-required.yml new file mode 100644 index 0000000..306b2d4 --- /dev/null +++ b/.github/workflows/services-static-next-required.yml @@ -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 diff --git a/ci/scripts/deploy-static.sh b/ci/scripts/deploy-static.sh new file mode 100755 index 0000000..ed05c1c --- /dev/null +++ b/ci/scripts/deploy-static.sh @@ -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" diff --git a/services/static/astro.config.mjs b/services/static/astro.config.mjs new file mode 100644 index 0000000..561e4ff --- /dev/null +++ b/services/static/astro.config.mjs @@ -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, + }, + }, + }, +}); diff --git a/services/static/package.json b/services/static/package.json new file mode 100644 index 0000000..6ba19af --- /dev/null +++ b/services/static/package.json @@ -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" + } +} diff --git a/services/static/src/env.d.ts b/services/static/src/env.d.ts new file mode 100644 index 0000000..21a969c --- /dev/null +++ b/services/static/src/env.d.ts @@ -0,0 +1,8 @@ +/** + * Type definitions for the static service + */ +/// + +interface PerformanceNavigationTiming { + responseStatus?: number; +} diff --git a/services/static/src/global.css b/services/static/src/global.css new file mode 100644 index 0000000..8a229a1 --- /dev/null +++ b/services/static/src/global.css @@ -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%; +} diff --git a/services/static/src/layouts/StaticLayout/StaticLayout.astro b/services/static/src/layouts/StaticLayout/StaticLayout.astro new file mode 100644 index 0000000..19d003b --- /dev/null +++ b/services/static/src/layouts/StaticLayout/StaticLayout.astro @@ -0,0 +1,49 @@ +--- +/** + * Base layout for static service pages + */ +export interface Props { + title: string; + description: string; +} + +const { title, description } = Astro.props; +--- + + + + + + + + + + + {title} + + + + + + + diff --git a/services/static/src/pages/error.astro b/services/static/src/pages/error.astro new file mode 100644 index 0000000..4e578ea --- /dev/null +++ b/services/static/src/pages/error.astro @@ -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'; +--- + + +
+ + +

Exceptional Situation

+

+ This location is unavailable at the moment — static content does not dwell here +

+ + + {AUTHOR_NAME}, {getCurrentYear()} + + + ermnvldmr.com + + +
+
+
+ +
diff --git a/services/static/src/pages/index.astro b/services/static/src/pages/index.astro new file mode 100644 index 0000000..3668089 --- /dev/null +++ b/services/static/src/pages/index.astro @@ -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'; +--- + + +
+ + +

Static Content Dwells Here

+

+ Other pages direct you down specific paths to the content that resides here +

+ + + {AUTHOR_NAME}, {getCurrentYear()} + + + ermnvldmr.com + + +
+
+
+
diff --git a/services/static/src/shared/constants.ts b/services/static/src/shared/constants.ts new file mode 100644 index 0000000..e5476d6 --- /dev/null +++ b/services/static/src/shared/constants.ts @@ -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'; diff --git a/services/static/src/shared/utils.ts b/services/static/src/shared/utils.ts new file mode 100644 index 0000000..f4c8748 --- /dev/null +++ b/services/static/src/shared/utils.ts @@ -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}`; diff --git a/services/static/tsconfig.json b/services/static/tsconfig.json new file mode 100644 index 0000000..eaa5560 --- /dev/null +++ b/services/static/tsconfig.json @@ -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.*"] +}