chore: prepare first release 1.0.1
This commit is contained in:
73
README.md
73
README.md
@@ -1,77 +1,82 @@
|
|||||||
# Mintel Monorepo
|
# Mintel Monorepo
|
||||||
|
|
||||||
This monorepo manages multiple client websites using a shared technology stack: Next.js, TypeScript, and React.
|
This monorepo is the central "factory" for all Mintel client websites. It provides a standardized, versioned core of configurations, utilities, and infrastructure templates to ensure consistency, security, and rapid deployment across the entire portfolio.
|
||||||
|
|
||||||
## Project Structure
|
## 🏗 Project Structure
|
||||||
|
|
||||||
- `apps/`: Client websites (e.g., `sample-website`).
|
- **`apps/`**: Client website implementations (e.g., `sample-website`). These are consumers of the shared packages.
|
||||||
- `packages/`: Shared packages under the `@mintel` namespace.
|
- **`packages/`**: Shared, versioned npm packages under the `@mintel` namespace.
|
||||||
- `@mintel/tsconfig`: Shared TypeScript configurations.
|
- [`@mintel/tsconfig`](packages/tsconfig/README.md): Centralized TypeScript configurations.
|
||||||
- `@mintel/eslint-config`: Shared ESLint configurations.
|
- [`@mintel/eslint-config`](packages/eslint-config/README.md): Shared linting rules and best practices.
|
||||||
- `@mintel/next-config`: Shared Next.js configuration wrapper.
|
- [`@mintel/next-config`](packages/next-config/README.md): A powerful Next.js configuration wrapper with built-in i18n and Sentry support.
|
||||||
- `@mintel/next-utils`: Reusable logic (i18n, rate limiting, etc.).
|
- [`@mintel/next-utils`](packages/next-utils/README.md): Reusable logic for i18n, environment validation, and rate limiting.
|
||||||
- `@mintel/infra`: Infrastructure templates (Docker, Gitea Actions).
|
- [`@mintel/infra`](packages/infra/README.md): Production-ready Docker and Gitea Actions templates.
|
||||||
- `@mintel/cli`: CLI tool for project setup and migration.
|
- [`@mintel/cli`](packages/cli/README.md): Automation tool for scaffolding new projects.
|
||||||
|
|
||||||
## Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- [pnpm](https://pnpm.io/) (v10+)
|
- [pnpm](https://pnpm.io/) (v10+)
|
||||||
- Node.js (v20+)
|
- Node.js (v20+)
|
||||||
|
- Access to the private registry: `https://npm.infra.mintel.me`
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
To run development mode for all shared packages:
|
||||||
```bash
|
```bash
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
To build all shared packages:
|
||||||
```bash
|
```bash
|
||||||
pnpm build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Creating a New Project
|
## 🛠 Creating a New Client Project
|
||||||
|
|
||||||
Use the Mintel CLI to set up a new project or migrate an existing one:
|
Never copy-paste files manually. Use the Mintel CLI to scaffold a new project with all best practices pre-configured:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm --filter @mintel/cli start init apps/my-new-website
|
pnpm --filter @mintel/cli start init apps/client-name.com
|
||||||
```
|
```
|
||||||
|
|
||||||
## Versioning and Releasing
|
This command automatically:
|
||||||
|
1. Sets up the directory structure.
|
||||||
|
2. Links all `@mintel` shared packages.
|
||||||
|
3. Configures `next-intl` (i18n) and Sentry.
|
||||||
|
4. Injects Docker and Gitea Actions deployment workflows.
|
||||||
|
|
||||||
We use [Changesets](https://github.com/changesets/changesets) for version management.
|
## 🔄 Release Cycle (Monorepo)
|
||||||
|
|
||||||
### 1. Add a changeset
|
We use [Changesets](https://github.com/changesets/changesets) to manage the versioning and publishing of shared packages.
|
||||||
When you make a change that requires a version bump, run:
|
|
||||||
|
### 1. Documenting Changes
|
||||||
|
When you modify a package in `packages/*`, create a changeset:
|
||||||
```bash
|
```bash
|
||||||
pnpm changeset
|
pnpm changeset
|
||||||
```
|
```
|
||||||
|
Follow the prompts to select the package and the version bump type (patch, minor, major).
|
||||||
|
|
||||||
### 2. Version packages
|
### 2. Versioning
|
||||||
To bump versions based on accumulated changesets:
|
When ready to release, bump the versions:
|
||||||
```bash
|
```bash
|
||||||
pnpm version-packages
|
pnpm version-packages
|
||||||
```
|
```
|
||||||
|
This updates `package.json` files and generates `CHANGELOG.md` entries.
|
||||||
|
|
||||||
### 3. Publish to registry
|
### 3. Publishing
|
||||||
To build and publish all changed packages to the private registry:
|
The release to the private registry is automated via Gitea Actions. On every push to `main`, the `release.yml` workflow runs `pnpm release`. If new versions are detected, they are published to `https://npm.infra.mintel.me`.
|
||||||
```bash
|
|
||||||
pnpm release
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deployment
|
## 🌐 Infrastructure & Deployment
|
||||||
|
|
||||||
Projects are hosted on Hetzner with Docker and Traefik, deployed via Gitea Actions. See `@mintel/infra` for templates.
|
Client websites scaffolded via the CLI use a **tag-based deployment** strategy:
|
||||||
|
- **Push to `main`**: Deploys to the `testing` environment.
|
||||||
|
- **Git Tag `v*.*.*-rc.*`**: Deploys to the `staging` environment.
|
||||||
|
- **Git Tag `v*.*.*`**: Deploys to the `production` environment.
|
||||||
|
|
||||||
## Registry
|
See the [`@mintel/infra`](packages/infra/README.md) package for detailed template documentation.
|
||||||
|
|
||||||
Private npm registry: [https://npm.infra.mintel.me](https://npm.infra.mintel.me)
|
|
||||||
|
|||||||
8
apps/sample-website/CHANGELOG.md
Normal file
8
apps/sample-website/CHANGELOG.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# sample-website
|
||||||
|
|
||||||
|
## 0.1.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @mintel/next-utils@1.0.1
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sample-website",
|
"name": "sample-website",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
7
packages/cli/CHANGELOG.md
Normal file
7
packages/cli/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# @mintel/cli
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Initial release of the Mintel factory packages.
|
||||||
@@ -1,26 +1,33 @@
|
|||||||
# @mintel/cli
|
# @mintel/cli
|
||||||
|
|
||||||
CLI tool for managing the Mintel monorepo and scaffolding new client websites.
|
The Mintel CLI is the primary automation tool for managing the monorepo and ensuring all client websites follow the same high-quality standards and infrastructure patterns.
|
||||||
|
|
||||||
## Installation
|
## 🚀 Installation
|
||||||
|
|
||||||
|
The CLI is intended to be used within the monorepo:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## 🛠 Commands
|
||||||
|
|
||||||
### `init <path>`
|
### `init <path>`
|
||||||
|
|
||||||
Initializes a new website project in the specified path (relative to the monorepo root).
|
Scaffolds a new, production-ready client website in the specified path.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm --filter @mintel/cli start init apps/my-new-website
|
pnpm --filter @mintel/cli start init apps/my-new-website.com
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will:
|
#### What it does:
|
||||||
1. Create the project directory.
|
1. **Project Structure**: Creates a modern Next.js directory layout.
|
||||||
2. Generate `package.json`, `tsconfig.json`, and `eslint.config.mjs` extending `@mintel` defaults.
|
2. **Shared Configs**: Generates `package.json`, `tsconfig.json`, and `eslint.config.mjs` that extend the `@mintel` shared packages.
|
||||||
3. Set up a localized Next.js structure (`src/app/[locale]`).
|
3. **Localization**: Sets up a localized routing structure (`src/app/[locale]`) with `next-intl` pre-configured.
|
||||||
4. Configure `next-intl` middleware and request config.
|
4. **Error Tracking**: Injects Sentry/GlitchTip instrumentation.
|
||||||
5. Inject production-ready `Dockerfile`, `docker-compose.yml`, and Gitea Actions deployment workflows.
|
5. **Environment Safety**: Adds a validation script (`scripts/validate-env.ts`) to catch missing secrets at build time.
|
||||||
|
6. **Infrastructure**: Injects the universal `Dockerfile`, `docker-compose.yml`, and the tag-based Gitea Actions deployment workflow.
|
||||||
|
|
||||||
|
## 🛠 Development
|
||||||
|
|
||||||
|
To add new features to the scaffold (e.g., new shared files or config templates), modify `packages/cli/src/index.ts`.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/cli",
|
"name": "@mintel/cli",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
@@ -99,6 +99,21 @@ export default nextConfig;
|
|||||||
eslintConfig
|
eslintConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Create env validation script
|
||||||
|
await fs.ensureDir(path.join(fullPath, "scripts"));
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(fullPath, "scripts/validate-env.ts"),
|
||||||
|
`import { validateMintelEnv } from "@mintel/next-utils";
|
||||||
|
|
||||||
|
try {
|
||||||
|
validateMintelEnv();
|
||||||
|
console.log("✅ Environment variables validated");
|
||||||
|
} catch (error) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
// Create basic src structure
|
// Create basic src structure
|
||||||
await fs.ensureDir(path.join(fullPath, "src/app/[locale]"));
|
await fs.ensureDir(path.join(fullPath, "src/app/[locale]"));
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
@@ -186,10 +201,14 @@ export default function RootLayout({
|
|||||||
|
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
path.join(fullPath, "src/app/[locale]/page.tsx"),
|
path.join(fullPath, "src/app/[locale]/page.tsx"),
|
||||||
`export default function Home() {
|
`import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const t = useTranslations('Index');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<h1>Welcome to ${projectName}</h1>
|
<h1>{t('title')} to ${projectName}</h1>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
7
packages/eslint-config/CHANGELOG.md
Normal file
7
packages/eslint-config/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# @mintel/eslint-config
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Initial release of the Mintel factory packages.
|
||||||
@@ -1,13 +1,28 @@
|
|||||||
# @mintel/eslint-config
|
# @mintel/eslint-config
|
||||||
|
|
||||||
Shared ESLint configurations for Mintel projects.
|
Shared ESLint configurations for Mintel projects, enforcing code quality and consistent style across Next.js and TypeScript codebases.
|
||||||
|
|
||||||
## Usage
|
## 📦 Configurations
|
||||||
|
|
||||||
|
### `next`
|
||||||
|
A comprehensive configuration for Next.js projects.
|
||||||
|
- **Extends**: `next/core-web-vitals` and `next/typescript`.
|
||||||
|
- **Custom Rules**:
|
||||||
|
- `_` prefix for unused variables is allowed.
|
||||||
|
- `any` type is permitted (for rapid migration/prototyping).
|
||||||
|
- React unescaped entities check is disabled.
|
||||||
|
- Image element warnings are enabled (prefer `next/image`).
|
||||||
|
|
||||||
|
## 🚀 Usage
|
||||||
|
|
||||||
|
### In a Next.js App
|
||||||
|
Create an `eslint.config.mjs` in your project root:
|
||||||
|
|
||||||
### Next.js
|
|
||||||
In your `eslint.config.mjs`:
|
|
||||||
```javascript
|
```javascript
|
||||||
import { nextConfig } from "@mintel/eslint-config/next";
|
import { nextConfig } from "@mintel/eslint-config/next";
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 🛠 Development
|
||||||
|
To add new rules, modify `packages/eslint-config/next.js`. Remember to create a changeset if you make breaking changes.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/eslint-config",
|
"name": "@mintel/eslint-config",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
7
packages/infra/CHANGELOG.md
Normal file
7
packages/infra/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# @mintel/infra
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Initial release of the Mintel factory packages.
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
# @mintel/infra
|
# @mintel/infra
|
||||||
|
|
||||||
Infrastructure templates for Mintel projects.
|
Production-ready infrastructure templates for Mintel client websites, optimized for Hetzner, Traefik, and Gitea Actions.
|
||||||
|
|
||||||
## Contents
|
## 📦 Contents
|
||||||
|
|
||||||
- `docker/`: Universal `Dockerfile.nextjs` and `docker-compose.template.yml`.
|
### `docker/`
|
||||||
- `gitea/`: Production-ready `deploy-action.yml` for Gitea Actions.
|
- **`Dockerfile.nextjs`**: A multi-stage build optimized for Next.js standalone output. Supports build-time ARGs for Umami and Base URL.
|
||||||
|
- **`docker-compose.template.yml`**: A Traefik-ready compose file with automatic HTTPS redirection, security headers, and network isolation.
|
||||||
|
|
||||||
## Usage
|
### `gitea/`
|
||||||
|
- **`deploy-action.yml`**: A high-transparency deployment workflow.
|
||||||
|
- **Testing**: Automatic deploy on push to `main`.
|
||||||
|
- **Staging**: Deploy via `v*-rc.*` tags.
|
||||||
|
- **Production**: Deploy via `v*.*.*` tags.
|
||||||
|
- **Features**: Gotify notifications, health checks, and automatic `.env` generation.
|
||||||
|
|
||||||
These files are automatically injected into new projects by the `@mintel/cli`.
|
## 🚀 Usage
|
||||||
|
|
||||||
|
These templates are automatically injected into new projects by the [`@mintel/cli`](../cli/README.md). If you need to update an existing project, you can manually copy them from this package.
|
||||||
|
|||||||
@@ -2,101 +2,162 @@ name: Build & Deploy
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, staging]
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-deploy:
|
build-and-deploy:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
# LOGGING: Workflow Start - Full Transparency
|
# Workflow Start & Basic Info
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
- name: 📋 Log Workflow Start
|
- name: 📢 Workflow Start
|
||||||
run: |
|
run: |
|
||||||
echo "🚀 Starting deployment for ${{ github.repository }} (${{ github.ref }})"
|
echo "┌──────────────────────────────────────────────────────────────┐"
|
||||||
echo " • Branch: ${{ github.ref_name }}"
|
echo "│ 🚀 Deployment Workflow gestartet │"
|
||||||
echo " • Commit: ${{ github.sha }}"
|
echo "├──────────────────────────────────────────────────────────────┤"
|
||||||
echo " • Timestamp: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
echo "│ Repository: ${{ github.repository }} │"
|
||||||
|
echo "│ Ref: ${{ github.ref }} │"
|
||||||
|
echo "│ Ref-Name: ${{ github.ref_name }} │"
|
||||||
|
echo "│ Commit: ${{ github.sha }} │"
|
||||||
|
echo "│ Actor: ${{ github.actor }} │"
|
||||||
|
echo "│ Datum: $(date -u +'%Y-%m-%d %H:%M:%S UTC') │"
|
||||||
|
echo "└──────────────────────────────────────────────────────────────┘"
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
# LOGGING: Registry Login Phase
|
# Environment bestimmen + Commit-Message holen
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
- name: 🔐 Login to private registry
|
- name: 🔍 Environment & Version ermitteln
|
||||||
|
id: determine
|
||||||
run: |
|
run: |
|
||||||
echo "🔐 Authenticating with registry.infra.mintel.me..."
|
TAG="${{ github.ref_name }}"
|
||||||
|
SHORT_SHA="${{ github.sha }}"
|
||||||
|
SHORT_SHA="${SHORT_SHA:0:9}"
|
||||||
|
|
||||||
|
# Get base domain from secret or env if possible, otherwise placeholder
|
||||||
|
# In a real project, you'd likely have a primary domain secret
|
||||||
|
DOMAIN_BASE=$(echo "${{ secrets.NEXT_PUBLIC_BASE_URL }}" | sed -E 's|https?://||' | sed -E 's|/.*||')
|
||||||
|
|
||||||
|
# Commit-Message holen (erste Zeile)
|
||||||
|
COMMIT_MSG=$(git log -1 --pretty=%s || echo "No commit message available")
|
||||||
|
|
||||||
|
if [[ "${{ github.ref_type }}" == "branch" && "$TAG" == "main" ]]; then
|
||||||
|
TARGET="testing"
|
||||||
|
IMAGE_TAG="main-${SHORT_SHA}"
|
||||||
|
ENV_FILE=".env.testing"
|
||||||
|
TRAEFIK_HOST="\`testing.${DOMAIN_BASE}\`"
|
||||||
|
IS_PROD="false"
|
||||||
|
GOTIFY_TITLE="🧪 Testing-Deploy"
|
||||||
|
GOTIFY_PRIORITY=4
|
||||||
|
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||||
|
if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
TARGET="production"
|
||||||
|
IMAGE_TAG="$TAG"
|
||||||
|
ENV_FILE=".env.prod"
|
||||||
|
TRAEFIK_HOST="\`${DOMAIN_BASE}\`, \`www.${DOMAIN_BASE}\`"
|
||||||
|
IS_PROD="true"
|
||||||
|
GOTIFY_TITLE="🚀 Production-Release"
|
||||||
|
GOTIFY_PRIORITY=6
|
||||||
|
elif [[ "$TAG" =~ -rc\. || "$TAG" =~ -beta\. || "$TAG" =~ -alpha\. ]]; then
|
||||||
|
TARGET="staging"
|
||||||
|
IMAGE_TAG="$TAG"
|
||||||
|
ENV_FILE=".env.staging"
|
||||||
|
TRAEFIK_HOST="\`staging.${DOMAIN_BASE}\`"
|
||||||
|
IS_PROD="false"
|
||||||
|
GOTIFY_TITLE="🧪 Staging-Deploy (Pre-Release)"
|
||||||
|
GOTIFY_PRIORITY=5
|
||||||
|
else
|
||||||
|
TARGET="skip"
|
||||||
|
GOTIFY_TITLE="❓ Unbekannter Tag"
|
||||||
|
GOTIFY_PRIORITY=3
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
TARGET="skip"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "target=$TARGET" >> $GITHUB_OUTPUT
|
||||||
|
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
|
||||||
|
echo "env_file=$ENV_FILE" >> $GITHUB_OUTPUT
|
||||||
|
echo "traefik_host=$TRAEFIK_HOST" >> $GITHUB_OUTPUT
|
||||||
|
echo "is_prod=$IS_PROD" >> $GITHUB_OUTPUT
|
||||||
|
echo "gotify_title=$GOTIFY_TITLE" >> $GITHUB_OUTPUT
|
||||||
|
echo "gotify_priority=$GOTIFY_PRIORITY" >> $GITHUB_OUTPUT
|
||||||
|
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||||
|
echo "commit_msg=$COMMIT_MSG" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: ⏭️ Skip Deployment
|
||||||
|
if: steps.determine.outputs.target == 'skip'
|
||||||
|
run: |
|
||||||
|
echo "Deployment übersprungen – kein passender Trigger (main oder v*-Tag)"
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Registry Login
|
||||||
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
- name: 🔐 Registry Login
|
||||||
|
run: |
|
||||||
|
echo "🔐 Login zu registry.infra.mintel.me ..."
|
||||||
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
# LOGGING: Build Phase
|
# Build & Push
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
- name: 🏗️ Build Docker image
|
- name: 🏗️ Docker Image bauen & pushen
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_BASE_URL: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_BASE_URL || (secrets.STAGING_NEXT_PUBLIC_BASE_URL || secrets.NEXT_PUBLIC_BASE_URL) }}
|
IMAGE_TAG: ${{ steps.determine.outputs.image_tag }}
|
||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }}
|
NEXT_PUBLIC_BASE_URL: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_BASE_URL || (steps.determine.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_BASE_URL || secrets.TESTING_NEXT_PUBLIC_BASE_URL || secrets.NEXT_PUBLIC_BASE_URL) }}
|
||||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }}
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (steps.determine.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.TESTING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }}
|
||||||
|
NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (steps.determine.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.TESTING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }}
|
||||||
run: |
|
run: |
|
||||||
echo "🏗️ Building Docker image (linux/arm64) for branch ${{ github.ref_name }}..."
|
echo "🏗️ Building → ${{ steps.determine.outputs.target }} / $IMAGE_TAG"
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--pull \
|
--pull \
|
||||||
--platform linux/arm64 \
|
--platform linux/arm64 \
|
||||||
--build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
--build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
||||||
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
|
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
|
||||||
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="$NEXT_PUBLIC_UMAMI_SCRIPT_URL" \
|
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="$NEXT_PUBLIC_UMAMI_SCRIPT_URL" \
|
||||||
-t registry.infra.mintel.me/${{ github.repository }}:${{ github.sha }} \
|
-t registry.infra.mintel.me/${{ github.repository }}:$IMAGE_TAG \
|
||||||
-t registry.infra.mintel.me/${{ github.repository }}:latest \
|
|
||||||
--push .
|
--push .
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
# LOGGING: Deployment Phase
|
# Deploy via SSH
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
- name: 🚀 Deploy to server
|
- name: 🚀 Deploy to ${{ steps.determine.outputs.target }}
|
||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_BASE_URL: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_BASE_URL || (secrets.STAGING_NEXT_PUBLIC_BASE_URL || secrets.NEXT_PUBLIC_BASE_URL) }}
|
IMAGE_TAG: ${{ steps.determine.outputs.image_tag }}
|
||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }}
|
ENV_FILE: ${{ steps.determine.outputs.env_file }}
|
||||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ github.ref_name == 'main' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }}
|
TRAEFIK_HOST: ${{ steps.determine.outputs.traefik_host }}
|
||||||
SENTRY_DSN: ${{ github.ref_name == 'main' && secrets.SENTRY_DSN || (secrets.STAGING_SENTRY_DSN || secrets.SENTRY_DSN) }}
|
NEXT_PUBLIC_BASE_URL: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_BASE_URL || (steps.determine.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_BASE_URL || secrets.TESTING_NEXT_PUBLIC_BASE_URL || secrets.NEXT_PUBLIC_BASE_URL) }}
|
||||||
MAIL_HOST: ${{ github.ref_name == 'main' && secrets.MAIL_HOST || (secrets.STAGING_MAIL_HOST || secrets.MAIL_HOST) }}
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || (steps.determine.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.TESTING_NEXT_PUBLIC_UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID) }}
|
||||||
MAIL_PORT: ${{ github.ref_name == 'main' && secrets.MAIL_PORT || (secrets.STAGING_MAIL_PORT || secrets.MAIL_PORT) }}
|
NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ steps.determine.outputs.target == 'production' && secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || (steps.determine.outputs.target == 'staging' && secrets.STAGING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.TESTING_NEXT_PUBLIC_UMAMI_SCRIPT_URL || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL) }}
|
||||||
MAIL_USERNAME: ${{ github.ref_name == 'main' && secrets.MAIL_USERNAME || (secrets.STAGING_MAIL_USERNAME || secrets.MAIL_USERNAME) }}
|
SENTRY_DSN: ${{ steps.determine.outputs.target == 'production' && secrets.SENTRY_DSN || (steps.determine.outputs.target == 'staging' && secrets.STAGING_SENTRY_DSN || secrets.TESTING_SENTRY_DSN || secrets.SENTRY_DSN) }}
|
||||||
MAIL_PASSWORD: ${{ github.ref_name == 'main' && secrets.MAIL_PASSWORD || (secrets.STAGING_MAIL_PASSWORD || secrets.MAIL_PASSWORD) }}
|
MAIL_HOST: ${{ steps.determine.outputs.target == 'production' && secrets.MAIL_HOST || (steps.determine.outputs.target == 'staging' && secrets.STAGING_MAIL_HOST || secrets.TESTING_MAIL_HOST || secrets.MAIL_HOST) }}
|
||||||
MAIL_FROM: ${{ github.ref_name == 'main' && secrets.MAIL_FROM || (secrets.STAGING_MAIL_FROM || secrets.MAIL_FROM) }}
|
MAIL_PORT: ${{ steps.determine.outputs.target == 'production' && secrets.MAIL_PORT || (steps.determine.outputs.target == 'staging' && secrets.STAGING_MAIL_PORT || secrets.TESTING_MAIL_PORT || secrets.MAIL_PORT) }}
|
||||||
MAIL_RECIPIENTS: ${{ github.ref_name == 'main' && secrets.MAIL_RECIPIENTS || (secrets.STAGING_MAIL_RECIPIENTS || secrets.MAIL_RECIPIENTS) }}
|
MAIL_USERNAME: ${{ steps.determine.outputs.target == 'production' && secrets.MAIL_USERNAME || (steps.determine.outputs.target == 'staging' && secrets.STAGING_MAIL_USERNAME || secrets.TESTING_MAIL_USERNAME || secrets.MAIL_USERNAME) }}
|
||||||
|
MAIL_PASSWORD: ${{ steps.determine.outputs.target == 'production' && secrets.MAIL_PASSWORD || (steps.determine.outputs.target == 'staging' && secrets.STAGING_MAIL_PASSWORD || secrets.TESTING_MAIL_PASSWORD || secrets.MAIL_PASSWORD) }}
|
||||||
|
MAIL_FROM: ${{ steps.determine.outputs.target == 'production' && secrets.MAIL_FROM || (steps.determine.outputs.target == 'staging' && secrets.STAGING_MAIL_FROM || secrets.TESTING_MAIL_FROM || secrets.MAIL_FROM) }}
|
||||||
|
MAIL_RECIPIENTS: ${{ steps.determine.outputs.target == 'production' && secrets.MAIL_RECIPIENTS || (steps.determine.outputs.target == 'staging' && secrets.STAGING_MAIL_RECIPIENTS || secrets.TESTING_MAIL_RECIPIENTS || secrets.MAIL_RECIPIENTS) }}
|
||||||
run: |
|
run: |
|
||||||
BRANCH=${{ github.ref_name }}
|
echo "Deploying ${{ steps.determine.outputs.target }} → $IMAGE_TAG"
|
||||||
|
|
||||||
# Derive domain from NEXT_PUBLIC_BASE_URL (strip https:// and trailing slash)
|
# SSH vorbereiten
|
||||||
DOMAIN=$(echo "$NEXT_PUBLIC_BASE_URL" | sed -E 's|https?://||' | sed -E 's|/.*||')
|
|
||||||
|
|
||||||
if [ "$BRANCH" = "main" ]; then
|
|
||||||
ENV_FILE=.env.prod
|
|
||||||
# For production, we want both root and www
|
|
||||||
TRAEFIK_HOST="\`$DOMAIN\`, \`www.$DOMAIN\`"
|
|
||||||
else
|
|
||||||
ENV_FILE=.env.staging
|
|
||||||
TRAEFIK_HOST="\`$DOMAIN\`"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🚀 Deploying branch $BRANCH to $ENV_FILE..."
|
|
||||||
echo "🌐 Domain: $DOMAIN"
|
|
||||||
|
|
||||||
# Setup SSH
|
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_ed25519
|
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
|
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
# Create .env file content
|
# .env-Datei erstellen
|
||||||
cat > /tmp/app.env << EOF
|
cat > /tmp/app.env << EOF
|
||||||
# ============================================================================
|
# Generated by CI - ${{ steps.determine.outputs.target }} - $(date -u)
|
||||||
# Environment Configuration ($BRANCH)
|
|
||||||
# ============================================================================
|
|
||||||
# Auto-generated by CI/CD workflow
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
|
NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
|
||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||||
@@ -108,90 +169,80 @@ jobs:
|
|||||||
MAIL_PASSWORD=$MAIL_PASSWORD
|
MAIL_PASSWORD=$MAIL_PASSWORD
|
||||||
MAIL_FROM=$MAIL_FROM
|
MAIL_FROM=$MAIL_FROM
|
||||||
MAIL_RECIPIENTS=$MAIL_RECIPIENTS
|
MAIL_RECIPIENTS=$MAIL_RECIPIENTS
|
||||||
|
|
||||||
# Deployment variables for docker-compose
|
IMAGE_TAG=$IMAGE_TAG
|
||||||
IMAGE_TAG=${{ github.sha }}
|
|
||||||
TRAEFIK_HOST=$TRAEFIK_HOST
|
TRAEFIK_HOST=$TRAEFIK_HOST
|
||||||
ENV_FILE=$ENV_FILE
|
ENV_FILE=$ENV_FILE
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
APP_DIR="/home/deploy/sites/${{ github.event.repository.name }}"
|
APP_DIR="/home/deploy/sites/${{ github.event.repository.name }}"
|
||||||
ssh -o StrictHostKeyChecking=accept-new root@${{ secrets.SSH_HOST }} "mkdir -p $APP_DIR"
|
ssh -o StrictHostKeyChecking=accept-new root@${{ secrets.SSH_HOST }} "mkdir -p $APP_DIR"
|
||||||
scp -o StrictHostKeyChecking=accept-new /tmp/app.env root@${{ secrets.SSH_HOST }}:$APP_DIR/$ENV_FILE
|
scp -o StrictHostKeyChecking=accept-new /tmp/app.env root@${{ secrets.SSH_HOST }}:$APP_DIR/$ENV_FILE
|
||||||
scp -o StrictHostKeyChecking=accept-new docker-compose.yml root@${{ secrets.SSH_HOST }}:$APP_DIR/docker-compose.yml
|
scp -o StrictHostKeyChecking=accept-new docker-compose.yml root@${{ secrets.SSH_HOST }}:$APP_DIR/docker-compose.yml
|
||||||
|
|
||||||
ssh -o StrictHostKeyChecking=accept-new root@${{ secrets.SSH_HOST }} bash << EOF
|
ssh -o StrictHostKeyChecking=accept-new root@${{ secrets.SSH_HOST }} bash << 'EOF'
|
||||||
set -e
|
set -e
|
||||||
cd $APP_DIR
|
APP_DIR="/home/deploy/sites/${{ github.event.repository.name }}"
|
||||||
|
cd $APP_DIR
|
||||||
chmod 600 $ENV_FILE
|
|
||||||
chown deploy:deploy $ENV_FILE
|
chmod 600 $ENV_FILE
|
||||||
|
chown deploy:deploy $ENV_FILE
|
||||||
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
|
||||||
|
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
echo "📥 Pulling images..."
|
|
||||||
IMAGE_TAG=${{ github.sha }} ENV_FILE=$ENV_FILE TRAEFIK_HOST="$TRAEFIK_HOST" docker compose --env-file $ENV_FILE pull
|
echo "→ Pulling image: $IMAGE_TAG"
|
||||||
|
IMAGE_TAG=$IMAGE_TAG ENV_FILE=$ENV_FILE TRAEFIK_HOST="$TRAEFIK_HOST" docker compose --env-file $ENV_FILE pull
|
||||||
echo "🚀 Starting containers..."
|
|
||||||
IMAGE_TAG=${{ github.sha }} ENV_FILE=$ENV_FILE TRAEFIK_HOST="$TRAEFIK_HOST" docker compose --env-file $ENV_FILE up -d
|
echo "→ Starting containers..."
|
||||||
|
IMAGE_TAG=$IMAGE_TAG ENV_FILE=$ENV_FILE TRAEFIK_HOST="$TRAEFIK_HOST" docker compose --env-file $ENV_FILE up -d
|
||||||
echo "🧹 Cleaning up old images..."
|
|
||||||
docker system prune -f
|
docker system prune -f --filter "until=168h"
|
||||||
|
|
||||||
echo "⏳ Giving the app a few seconds to warm up..."
|
echo "→ Waiting 15s for warmup..."
|
||||||
sleep 10
|
sleep 15
|
||||||
|
|
||||||
echo "🔍 Checking container status..."
|
echo "→ Container status:"
|
||||||
docker compose --env-file $ENV_FILE ps
|
docker compose --env-file $ENV_FILE ps
|
||||||
|
|
||||||
if ! docker compose --env-file $ENV_FILE ps | grep -q "Up"; then
|
if ! docker compose --env-file $ENV_FILE ps | grep -q "Up"; then
|
||||||
echo "❌ Container failed to start"
|
echo "❌ Fehler: Container nicht Up!"
|
||||||
docker compose --env-file $ENV_FILE logs --tail=100
|
docker compose --env-file $ENV_FILE logs --tail=150
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✅ Deployment complete!"
|
echo "✅ Deployment erfolgreich auf ${{ steps.determine.outputs.target }}!"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
rm -f /tmp/app.env
|
rm -f /tmp/app.env
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
# LOGGING: Workflow Summary
|
# Summary & Gotify
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
- name: 📊 Workflow Summary
|
- name: 📊 Deployment Summary
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
echo "📊 Status: ${{ job.status }}"
|
echo "┌──────────────────────────────┐"
|
||||||
echo "🎯 Target: ${{ secrets.SSH_HOST }}"
|
echo "│ Deployment Summary │"
|
||||||
echo "🌿 Branch: ${{ github.ref_name }}"
|
echo "├──────────────────────────────┤"
|
||||||
|
echo "│ Status: ${{ job.status }} │"
|
||||||
|
echo "│ Umgebung: ${{ steps.determine.outputs.target || 'skipped' }} │"
|
||||||
|
echo "│ Version: ${{ steps.determine.outputs.image_tag }} │"
|
||||||
|
echo "│ Commit: ${{ steps.determine.outputs.short_sha }} │"
|
||||||
|
echo "│ Message: ${{ steps.determine.outputs.commit_msg }} │"
|
||||||
|
echo "└──────────────────────────────┘"
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
- name: 🔔 Gotify - Success
|
||||||
# NOTIFICATION: Gotify
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
|
||||||
- name: 🔔 Gotify Notification (Success)
|
|
||||||
if: success()
|
if: success()
|
||||||
run: |
|
run: |
|
||||||
echo "Sending success notification to Gotify..."
|
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
||||||
curl -k -s -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
-F "title=${{ steps.determine.outputs.gotify_title }}" \
|
||||||
-F "title=✅ Deployment Success: ${{ github.repository }}" \
|
-F "message=Erfolgreich deployt auf **${{ steps.determine.outputs.target }}**\n\nVersion: **${{ steps.determine.outputs.image_tag }}**\nCommit: ${{ steps.determine.outputs.short_sha }} (${{ steps.determine.outputs.commit_msg }})\nVon: ${{ github.actor }}\nRun: ${{ github.run_id }}" \
|
||||||
-F "message=The deployment of ${{ github.repository }} (branch: ${{ github.ref_name }}) was successful.
|
-F "priority=${{ steps.determine.outputs.gotify_priority }}" || true
|
||||||
|
|
||||||
Commit: ${{ github.sha }}
|
|
||||||
Actor: ${{ github.actor }}
|
|
||||||
Run ID: ${{ github.run_id }}" \
|
|
||||||
-F "priority=5"
|
|
||||||
|
|
||||||
- name: 🔔 Gotify Notification (Failure)
|
- name: 🔔 Gotify - Failure
|
||||||
if: failure()
|
if: failure()
|
||||||
run: |
|
run: |
|
||||||
echo "Sending failure notification to Gotify..."
|
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
||||||
curl -k -s -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
-F "title=❌ Deployment FEHLGESCHLAGEN – ${{ steps.determine.outputs.target || 'unknown' }}" \
|
||||||
-F "title=❌ Deployment Failed: ${{ github.repository }}" \
|
-F "message=**Fehler beim Deploy auf ${{ steps.determine.outputs.target }}**\n\nVersion: ${{ steps.determine.outputs.image_tag || '?' }}\nCommit: ${{ steps.determine.outputs.short_sha || '?' }}\nVon: ${{ github.actor }}\nRun: ${{ github.run_id }}\n\nBitte Logs prüfen!" \
|
||||||
-F "message=The deployment of ${{ github.repository }} (branch: ${{ github.ref_name }}) failed!
|
-F "priority=8" || true
|
||||||
|
|
||||||
Commit: ${{ github.sha }}
|
|
||||||
Actor: ${{ github.actor }}
|
|
||||||
Run ID: ${{ github.run_id }}
|
|
||||||
|
|
||||||
Please check the logs for details." \
|
|
||||||
-F "priority=8"
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/infra",
|
"name": "@mintel/infra",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
7
packages/next-config/CHANGELOG.md
Normal file
7
packages/next-config/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# @mintel/next-config
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Initial release of the Mintel factory packages.
|
||||||
@@ -1,16 +1,31 @@
|
|||||||
# @mintel/next-config
|
# @mintel/next-config
|
||||||
|
|
||||||
Shared Next.js configuration wrapper for Mintel projects. Integrates `next-intl` and Sentry by default.
|
A powerful Next.js configuration wrapper that standardizes internationalization, error tracking, and security across all Mintel client websites.
|
||||||
|
|
||||||
## Usage
|
## ✨ Features
|
||||||
|
|
||||||
|
- **`next-intl` Integration**: Automatically wraps your config with the internationalization plugin.
|
||||||
|
- **Sentry/GlitchTip**: Pre-configured error tracking with treeshaking and silent CI builds.
|
||||||
|
- **Standalone Output**: Optimized for Docker deployments by default.
|
||||||
|
- **Security Headers**: Strict Content Security Policy (CSP) and SVG safety.
|
||||||
|
- **Analytics Proxy**: Built-in rewrites for Umami analytics (`/stats/*`) and GlitchTip (`/errors/*`).
|
||||||
|
|
||||||
|
## 🚀 Usage
|
||||||
|
|
||||||
In your `next.config.ts`:
|
In your `next.config.ts`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import mintelNextConfig from "@mintel/next-config";
|
import mintelNextConfig from "@mintel/next-config";
|
||||||
|
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
// Your project specific config
|
// Your project specific config (redirects, etc.)
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mintelNextConfig(nextConfig);
|
export default mintelNextConfig(nextConfig);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 🌐 Environment Variables
|
||||||
|
The following variables are used by this config:
|
||||||
|
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL`: URL to your Umami instance.
|
||||||
|
- `SENTRY_DSN`: Your GlitchTip/Sentry DSN.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/next-config",
|
"name": "@mintel/next-config",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
7
packages/next-utils/CHANGELOG.md
Normal file
7
packages/next-utils/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# @mintel/next-utils
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Initial release of the Mintel factory packages.
|
||||||
@@ -1,28 +1,47 @@
|
|||||||
# @mintel/next-utils
|
# @mintel/next-utils
|
||||||
|
|
||||||
Reusable utilities for Mintel Next.js projects.
|
A collection of reusable utilities and helpers for Mintel Next.js projects, focusing on internationalization, environment safety, and security.
|
||||||
|
|
||||||
## Features
|
## ✨ Features
|
||||||
|
|
||||||
- **i18n**: Standardized middleware and request configuration for `next-intl`.
|
### 🌍 Internationalization (i18n)
|
||||||
- **Env Validation**: Zod-based environment variable validation.
|
Standardized helpers for `next-intl`:
|
||||||
- **Rate Limiting**: Simple in-memory rate limiting for server actions.
|
- `createMintelMiddleware`: A logging-enabled middleware wrapper.
|
||||||
|
- `createMintelI18nRequestConfig`: Centralized request configuration for server-side translations.
|
||||||
|
|
||||||
## Usage
|
### 🔐 Environment Validation
|
||||||
|
Zod-based validation to ensure your app never boots with missing secrets:
|
||||||
|
- `validateMintelEnv`: Validates standard Mintel variables (Mail, Sentry, Umami).
|
||||||
|
|
||||||
### i18n Middleware
|
### 🛡 Rate Limiting
|
||||||
|
- `rateLimit`: A simple in-memory rate limiter for protecting server actions and form submissions.
|
||||||
|
|
||||||
|
## 🚀 Usage
|
||||||
|
|
||||||
|
### i18n Middleware (`src/middleware.ts`)
|
||||||
```typescript
|
```typescript
|
||||||
import { createMintelMiddleware } from "@mintel/next-utils";
|
import { createMintelMiddleware } from "@mintel/next-utils";
|
||||||
|
|
||||||
export default createMintelMiddleware({
|
export default createMintelMiddleware({
|
||||||
locales: ["en", "de"],
|
locales: ["en", "de"],
|
||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
|
logRequests: true,
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Env Validation
|
### Env Validation (`scripts/validate-env.ts`)
|
||||||
```typescript
|
```typescript
|
||||||
import { validateMintelEnv } from "@mintel/next-utils";
|
import { validateMintelEnv } from "@mintel/next-utils";
|
||||||
|
|
||||||
const env = validateMintelEnv();
|
validateMintelEnv();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
```typescript
|
||||||
|
import { rateLimit } from "@mintel/next-utils";
|
||||||
|
|
||||||
|
export async function myAction(data: any) {
|
||||||
|
await rateLimit(data.email);
|
||||||
|
// ... logic
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/next-utils",
|
"name": "@mintel/next-utils",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
7
packages/tsconfig/CHANGELOG.md
Normal file
7
packages/tsconfig/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# @mintel/tsconfig
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Initial release of the Mintel factory packages.
|
||||||
@@ -1,21 +1,47 @@
|
|||||||
# @mintel/tsconfig
|
# @mintel/tsconfig
|
||||||
|
|
||||||
Shared TypeScript configurations for Mintel projects.
|
Centralized TypeScript configurations for all Mintel projects, ensuring consistent compiler settings and modern target environments.
|
||||||
|
|
||||||
## Usage
|
## 📦 Configurations
|
||||||
|
|
||||||
### Base Configuration
|
### `base.json`
|
||||||
In your `tsconfig.json`:
|
The foundation for all TypeScript projects in the monorepo.
|
||||||
|
- **Target**: `ES2020`
|
||||||
|
- **Module Resolution**: `bundler`
|
||||||
|
- **Strictness**: `strict: false` (aligned with `klz-2026` standards)
|
||||||
|
- **Features**: Enables `esModuleInterop`, `resolveJsonModule`, and `isolatedModules`.
|
||||||
|
|
||||||
|
### `nextjs.json`
|
||||||
|
Extends `base.json` with specific settings for Next.js applications.
|
||||||
|
- **Plugins**: Includes the `next` TypeScript plugin for enhanced IDE support.
|
||||||
|
- **JSX**: Set to `preserve`.
|
||||||
|
|
||||||
|
## 🚀 Usage
|
||||||
|
|
||||||
|
### In a Next.js App
|
||||||
|
Create a `tsconfig.json` in your project root:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"extends": "@mintel/tsconfig/base.json"
|
"extends": "@mintel/tsconfig/nextjs.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Next.js Configuration
|
### In a Library Package
|
||||||
In your `tsconfig.json`:
|
Create a `tsconfig.json` in your package root:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"extends": "@mintel/tsconfig/nextjs.json"
|
"extends": "@mintel/tsconfig/base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/tsconfig",
|
"name": "@mintel/tsconfig",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"registry": "https://npm.infra.mintel.me"
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
|||||||
Reference in New Issue
Block a user