Compare commits
9 Commits
v2.2.13-rc
...
v2.2.13-rc
| Author | SHA1 | Date | |
|---|---|---|---|
| 04599827f9 | |||
| 5368f1a64d | |||
| 241c97e346 | |||
| 3334e327bf | |||
| 145bff90b6 | |||
| d2bf48fc60 | |||
| 5e7ee84dc9 | |||
| b42e53495d | |||
| 8813ed3910 |
@@ -17,9 +17,6 @@ env:
|
|||||||
PUPPETEER_SKIP_DOWNLOAD: "true"
|
PUPPETEER_SKIP_DOWNLOAD: "true"
|
||||||
COREPACK_NPM_REGISTRY: "https://registry.npmmirror.com"
|
COREPACK_NPM_REGISTRY: "https://registry.npmmirror.com"
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: deploy-pipeline
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -146,6 +143,13 @@ jobs:
|
|||||||
echo "short_sha=$SHORT_SHA"
|
echo "short_sha=$SHORT_SHA"
|
||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: 🚦 Priority Gate
|
||||||
|
env:
|
||||||
|
GITEA_PAT: ${{ secrets.GITEA_PAT }}
|
||||||
|
CURRENT_RUN_ID: ${{ github.run_id }}
|
||||||
|
CURRENT_TARGET: ${{ steps.determine.outputs.target }}
|
||||||
|
run: bash scripts/priority-gate.sh
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
# JOB 2: QA (Lint, Typecheck, Test)
|
# JOB 2: QA (Lint, Typecheck, Test)
|
||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
25
INSTRUCTIONS.md
Normal file
25
INSTRUCTIONS.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Antigravity Instructions & Project Rules
|
||||||
|
|
||||||
|
This document contains critical project-specific rules for the e-tib.com codebase. Antigravity must read and follow these rules at all times.
|
||||||
|
|
||||||
|
## 1. Bilingual Site (German & English) - STRICT
|
||||||
|
- **Always update BOTH languages!** The website is bilingual.
|
||||||
|
- When changing content, adding pages, or updating text, you MUST always check and update both `content/de/` and `content/en/` simultaneously.
|
||||||
|
- When adding new components with text, ensure they are built to support both languages or update the respective translation files/mechanisms. Do NOT just hardcode German text and forget the English version.
|
||||||
|
|
||||||
|
## 2. No Dummy Data / No Hallucinations
|
||||||
|
- Do not invent or hallucinate placeholder text (e.g., "Nachweis liegt vor" for certificates that don't exist).
|
||||||
|
- Only implement what is explicitly requested or what matches provided assets/data.
|
||||||
|
- If data is missing, ask the user instead of inventing placeholders.
|
||||||
|
|
||||||
|
## 3. Git Workflow & RC Tagging
|
||||||
|
- When instructed to "commit and tag" (or similar), always follow this process:
|
||||||
|
1. Add and commit the changes with conventional commits (`feat:`, `fix:`, `chore:`, etc.).
|
||||||
|
2. Check the latest tag using `git describe --tags --abbrev=0`.
|
||||||
|
3. Increment the RC (Release Candidate) number of the current version (e.g., if latest is `v2.2.13-rc19`, the new tag is `v2.2.13-rc20`).
|
||||||
|
4. Create the tag: `git tag vX.Y.Z-rcN`.
|
||||||
|
5. Push the commit and the tag: `git push origin main --tags`.
|
||||||
|
|
||||||
|
## 4. Work Exclusively on the Correct Branch
|
||||||
|
- Follow the `USER_GLOBAL` directive: If on `main`, create a branch unless explicitly told to work on `main`.
|
||||||
|
- For e-tib.com, often small fixes are done on `main` if the user commands it ("auf main arbeiten"), but always verify context.
|
||||||
@@ -186,7 +186,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
|||||||
<div className={`flex flex-col min-h-screen ${isFullBleed ? '' : 'bg-white'}`}>
|
<div className={`flex flex-col min-h-screen ${isFullBleed ? '' : 'bg-white'}`}>
|
||||||
{/* Generic Hero Section (only for standard pages) */}
|
{/* Generic Hero Section (only for standard pages) */}
|
||||||
{!isFullBleed && (
|
{!isFullBleed && (
|
||||||
<section className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
|
<section key={`hero-section-${slug}`} className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
|
||||||
<div className="absolute inset-0 opacity-20 pointer-events-none">
|
<div className="absolute inset-0 opacity-20 pointer-events-none">
|
||||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
|
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default async function StandortDetail(props: { params: Promise<{ locale:
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen bg-white">
|
<div className="flex flex-col min-h-screen bg-white">
|
||||||
{/* Generic Hero Section to match standard pages */}
|
{/* Generic Hero Section to match standard pages */}
|
||||||
<section className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
|
<section key={`standort-hero-${slug}`} className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
|
||||||
<div className="absolute inset-0 opacity-20 pointer-events-none">
|
<div className="absolute inset-0 opacity-20 pointer-events-none">
|
||||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
|
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Badge, Container, Heading } from '@/components/ui';
|
import { Badge, Container, Heading } from '@/components/ui';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
@@ -39,6 +40,7 @@ const itemVariants = {
|
|||||||
export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
||||||
const { title, badge, subtitle, backgroundImage, alignment, ctaLabel, ctaHref } = props;
|
const { title, badge, subtitle, backgroundImage, alignment, ctaLabel, ctaHref } = props;
|
||||||
const bgSrc = backgroundImage?.sizes?.card?.url || backgroundImage?.url;
|
const bgSrc = backgroundImage?.sizes?.card?.url || backgroundImage?.url;
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
@@ -46,6 +48,7 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
|||||||
>
|
>
|
||||||
{bgSrc && (
|
{bgSrc && (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
key={`hero-bg-${pathname}`}
|
||||||
className="absolute inset-0 z-0"
|
className="absolute inset-0 z-0"
|
||||||
initial={{ scale: 1.05, opacity: 0 }}
|
initial={{ scale: 1.05, opacity: 0 }}
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
@@ -75,6 +78,7 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
<Container className={`relative z-10 ${alignment === 'center' ? 'max-w-5xl' : ''}`}>
|
<Container className={`relative z-10 ${alignment === 'center' ? 'max-w-5xl' : ''}`}>
|
||||||
<motion.div
|
<motion.div
|
||||||
|
key={`hero-content-${pathname}`}
|
||||||
className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`}
|
className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`}
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/Button';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
interface HeroVideoProps {
|
interface HeroVideoProps {
|
||||||
data?: any;
|
data?: any;
|
||||||
@@ -28,6 +29,7 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
const subtitle = props.subtitle || props.description || data?.subtitle || 'Wir verbinden Infrastruktur mit Präzision. Von Horizontalbohrungen bis zu komplexen Leitungsnetzen.';
|
const subtitle = props.subtitle || props.description || data?.subtitle || 'Wir verbinden Infrastruktur mit Präzision. Von Horizontalbohrungen bis zu komplexen Leitungsnetzen.';
|
||||||
const explicitNoVideo = props.videoUrl === 'none' || props.videoUrl === '';
|
const explicitNoVideo = props.videoUrl === 'none' || props.videoUrl === '';
|
||||||
const videoUrl = explicitNoVideo ? null : (props.videoUrl || data?.videoUrl || '/assets/dummy-hero.mp4');
|
const videoUrl = explicitNoVideo ? null : (props.videoUrl || data?.videoUrl || '/assets/dummy-hero.mp4');
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
const posterSrc = props.backgroundImage?.url || props.posterImage?.url || data?.posterImage?.url || "/assets/photos/DJI_0048.JPG";
|
const posterSrc = props.backgroundImage?.url || props.posterImage?.url || data?.posterImage?.url || "/assets/photos/DJI_0048.JPG";
|
||||||
const posterAlt = props.backgroundImage?.alt || data?.posterImage?.alt || "E-TIB Gruppe Baustelle";
|
const posterAlt = props.backgroundImage?.alt || data?.posterImage?.alt || "E-TIB Gruppe Baustelle";
|
||||||
@@ -56,7 +58,7 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
|
|
||||||
{videoUrl ? (
|
{videoUrl ? (
|
||||||
<video
|
<video
|
||||||
key={videoUrl}
|
key={`${pathname}-${videoUrl}`}
|
||||||
className="absolute inset-0 w-full h-full object-cover z-1 pointer-events-none filter contrast-125 saturate-110 brightness-90"
|
className="absolute inset-0 w-full h-full object-cover z-1 pointer-events-none filter contrast-125 saturate-110 brightness-90"
|
||||||
src={videoUrl}
|
src={videoUrl}
|
||||||
autoPlay
|
autoPlay
|
||||||
@@ -67,6 +69,7 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Image
|
<Image
|
||||||
|
key={`img-${pathname}-${posterSrc}`}
|
||||||
src={posterSrc}
|
src={posterSrc}
|
||||||
alt={posterAlt}
|
alt={posterAlt}
|
||||||
fill
|
fill
|
||||||
@@ -88,6 +91,7 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
|
|
||||||
<div className="container relative z-[4] text-center px-4">
|
<div className="container relative z-[4] text-center px-4">
|
||||||
<motion.div
|
<motion.div
|
||||||
|
key={`hero-content-${pathname}`}
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1] }}
|
transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1] }}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React, { useState } from 'react';
|
|||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react';
|
import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter, usePathname } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
|
||||||
import { Location, defaultLocations, minorLocations, projectLocations } from '@/lib/map-data';
|
import { Location, defaultLocations, minorLocations, projectLocations } from '@/lib/map-data';
|
||||||
@@ -37,6 +37,7 @@ export function InteractiveGermanyMap({
|
|||||||
}: InteractiveGermanyMapProps) {
|
}: InteractiveGermanyMapProps) {
|
||||||
const [activeLocation, setActiveLocation] = useState<Location | null>(null);
|
const [activeLocation, setActiveLocation] = useState<Location | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
const t = useTranslations('InteractiveGermanyMap');
|
const t = useTranslations('InteractiveGermanyMap');
|
||||||
const tStandard = useTranslations('StandardPage');
|
const tStandard = useTranslations('StandardPage');
|
||||||
@@ -57,7 +58,7 @@ export function InteractiveGermanyMap({
|
|||||||
const minorNodes = locations.filter((l) => l.type === 'minor_node');
|
const minorNodes = locations.filter((l) => l.type === 'minor_node');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={isHero ? "relative w-full" : "relative w-full max-w-7xl mx-auto py-16 md:py-24 px-4 sm:px-6 mt-16 md:mt-20"}>
|
<div key={isHero ? `hero-map-${pathname}` : undefined} className={isHero ? "relative w-full" : "relative w-full max-w-7xl mx-auto py-16 md:py-24 px-4 sm:px-6 mt-16 md:mt-20"}>
|
||||||
<div className={`bg-[#050B14] ${isHero ? 'pt-32 pb-16 md:pt-40 md:pb-24' : 'rounded-[2.5rem] md:rounded-[3.5rem] shadow-2xl border border-white/5'} overflow-visible relative group/map`}>
|
<div className={`bg-[#050B14] ${isHero ? 'pt-32 pb-16 md:pt-40 md:pb-24' : 'rounded-[2.5rem] md:rounded-[3.5rem] shadow-2xl border border-white/5'} overflow-visible relative group/map`}>
|
||||||
{/* Animated Border Glow */}
|
{/* Animated Border Glow */}
|
||||||
{!isHero && <AnimatedGlossyBorder opacity={0.7} className="z-30" />}
|
{!isHero && <AnimatedGlossyBorder opacity={0.7} className="z-30" />}
|
||||||
|
|||||||
@@ -8,20 +8,34 @@ export function EUFundingBadge() {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
transition={{ duration: 2, ease: [0.16, 1, 0.3, 1] }}
|
transition={{ duration: 1.5, ease: [0.16, 1, 0.3, 1] }}
|
||||||
className="absolute top-0 right-0 w-[350px] md:w-[550px] h-[350px] md:h-[550px] z-0 pointer-events-none select-none"
|
className="absolute top-0 right-0 w-[260px] md:w-[380px] h-[260px] md:h-[380px] z-[40] pointer-events-none select-none overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
maskImage: 'radial-gradient(ellipse at top right, black 10%, transparent 70%)',
|
// Perfekter weicher Fade, der sicherstellt, dass harte Kanten komplett unsichtbar werden
|
||||||
WebkitMaskImage: 'radial-gradient(ellipse at top right, black 10%, transparent 70%)',
|
maskImage: 'radial-gradient(100% 100% at top right, black 10%, rgba(0,0,0,0.8) 30%, transparent 70%)',
|
||||||
|
WebkitMaskImage: 'radial-gradient(100% 100% at top right, black 10%, rgba(0,0,0,0.8) 30%, transparent 70%)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
{/* Background Flag */}
|
||||||
src="/assets/eu-flag-perspective.png"
|
<div className="absolute inset-0 w-full h-full opacity-95">
|
||||||
alt="European Union Background"
|
<Image
|
||||||
fill
|
src="/assets/eu-flag.webp"
|
||||||
className="object-cover object-top opacity-50 mix-blend-screen"
|
alt="European Union Background"
|
||||||
priority
|
fill
|
||||||
/>
|
className="object-cover object-top"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Text overlaid on the flag */}
|
||||||
|
<div className="absolute top-0 right-0 p-5 md:p-8 flex flex-col items-end text-right z-10 drop-shadow-[0_2px_8px_rgba(0,0,0,0.9)]">
|
||||||
|
<span className="block text-[9px] md:text-[11px] font-bold text-white/90 tracking-wide leading-snug drop-shadow-md">
|
||||||
|
Kofinanziert von der
|
||||||
|
</span>
|
||||||
|
<span className="block text-[11px] md:text-[13px] font-extrabold text-white tracking-wider leading-snug uppercase drop-shadow-md">
|
||||||
|
Europäischen Union
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useAnalytics } from '../analytics/useAnalytics';
|
|||||||
import { AnalyticsEvents } from '../analytics/analytics-events';
|
import { AnalyticsEvents } from '../analytics/analytics-events';
|
||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { ChevronRight } from 'lucide-react';
|
import { ChevronRight } from 'lucide-react';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
import { AISearchResults } from '../search/AISearchResults';
|
import { AISearchResults } from '../search/AISearchResults';
|
||||||
const HeroIllustration = dynamic(() => import('./HeroIllustration'), { ssr: false });
|
const HeroIllustration = dynamic(() => import('./HeroIllustration'), { ssr: false });
|
||||||
const AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });
|
const AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });
|
||||||
@@ -15,6 +16,7 @@ const AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });
|
|||||||
export default function Hero({ data }: { data?: any }) {
|
export default function Hero({ data }: { data?: any }) {
|
||||||
const t = useTranslations('Home.hero');
|
const t = useTranslations('Home.hero');
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
|
const pathname = usePathname();
|
||||||
const { trackEvent } = useAnalytics();
|
const { trackEvent } = useAnalytics();
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
@@ -85,7 +87,7 @@ export default function Hero({ data }: { data?: any }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Section className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
|
<Section key={`home-hero-${pathname}`} className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
|
||||||
<Container className="relative z-10 text-center md:text-left text-white w-full order-2 md:order-none">
|
<Container className="relative z-10 text-center md:text-left text-white w-full order-2 md:order-none">
|
||||||
<div className="max-w-5xl mx-auto md:mx-0">
|
<div className="max-w-5xl mx-auto md:mx-0">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -143,7 +143,7 @@
|
|||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"preinstall": "npx only-allow pnpm"
|
"preinstall": "npx only-allow pnpm"
|
||||||
},
|
},
|
||||||
"version": "2.2.12",
|
"version": "2.2.13-rc.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"@parcel/watcher",
|
"@parcel/watcher",
|
||||||
|
|||||||
BIN
public/assets/eu-flag.webp
Normal file
BIN
public/assets/eu-flag.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
84
scripts/priority-gate.sh
Executable file
84
scripts/priority-gate.sh
Executable file
@@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -z "$GITEA_PAT" ]; then
|
||||||
|
echo "No GITEA_PAT provided, skipping priority gate."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$CURRENT_RUN_ID" ] || [ -z "$CURRENT_TARGET" ] || [ -z "$GITHUB_REPOSITORY" ]; then
|
||||||
|
echo "Missing required environment variables."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare -A PRIO=( ["production"]=40 ["staging"]=30 ["testing"]=20 ["branch"]=10 )
|
||||||
|
CURRENT_PRIO=${PRIO[$CURRENT_TARGET]:-0}
|
||||||
|
|
||||||
|
echo "Current Run: $CURRENT_RUN_ID, Target: $CURRENT_TARGET, Priority: $CURRENT_PRIO"
|
||||||
|
|
||||||
|
API_BASE="https://git.infra.mintel.me/api/v1/repos/$GITHUB_REPOSITORY/actions/runs"
|
||||||
|
|
||||||
|
# Fetch running and waiting workflows
|
||||||
|
LIMIT=50
|
||||||
|
RESPONSE=$(curl -s -H "Authorization: token $GITEA_PAT" "$API_BASE?limit=$LIMIT")
|
||||||
|
|
||||||
|
# Verify response is valid JSON
|
||||||
|
if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
|
||||||
|
echo "Failed to parse API response from Gitea."
|
||||||
|
echo "$RESPONSE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use jq to extract running or waiting jobs that are NOT the current one
|
||||||
|
RUNS=$(echo "$RESPONSE" | jq -c '.[] | select(.id != '"$CURRENT_RUN_ID"') | select(.status == "running" or .status == "waiting")')
|
||||||
|
|
||||||
|
if [ -z "$RUNS" ]; then
|
||||||
|
echo "No other running/waiting workflows found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r run; do
|
||||||
|
if [ -z "$run" ]; then continue; fi
|
||||||
|
|
||||||
|
RUN_ID=$(echo "$run" | jq -r '.id')
|
||||||
|
REF=$(echo "$run" | jq -r '.ref')
|
||||||
|
REF_NAME=${REF#refs/tags/}
|
||||||
|
REF_NAME=${REF_NAME#refs/heads/}
|
||||||
|
|
||||||
|
# Determine target of the running workflow
|
||||||
|
TARGET="branch"
|
||||||
|
if [ "$REF" == "refs/heads/main" ] || [ "$REF_NAME" == "main" ]; then
|
||||||
|
TARGET="testing"
|
||||||
|
elif [[ "$REF" == refs/tags/* ]] || [[ "$REF" == refs/heads/v* ]]; then
|
||||||
|
if [[ "$REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
TARGET="production"
|
||||||
|
else
|
||||||
|
TARGET="staging"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
RUN_PRIO=${PRIO[$TARGET]:-0}
|
||||||
|
|
||||||
|
echo "Found running workflow ID: $RUN_ID (Target: $TARGET, Priority: $RUN_PRIO)"
|
||||||
|
|
||||||
|
if [ "$CURRENT_PRIO" -gt "$RUN_PRIO" ]; then
|
||||||
|
echo "Current workflow has HIGHER priority ($CURRENT_PRIO > $RUN_PRIO). Canceling run $RUN_ID..."
|
||||||
|
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$RUN_ID/cancel"
|
||||||
|
elif [ "$CURRENT_PRIO" -lt "$RUN_PRIO" ]; then
|
||||||
|
echo "Current workflow has LOWER priority ($CURRENT_PRIO < $RUN_PRIO). Aborting self."
|
||||||
|
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$CURRENT_RUN_ID/cancel"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
# Equal priority: Newer wins
|
||||||
|
if [ "$CURRENT_RUN_ID" -gt "$RUN_ID" ]; then
|
||||||
|
echo "Equal priority, but current is newer. Canceling run $RUN_ID..."
|
||||||
|
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$RUN_ID/cancel"
|
||||||
|
else
|
||||||
|
echo "Equal priority, but current is older. Aborting self."
|
||||||
|
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$CURRENT_RUN_ID/cancel"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$RUNS"
|
||||||
|
|
||||||
|
echo "Priority gate cleared. Continuing deployment."
|
||||||
Reference in New Issue
Block a user