Compare commits
92 Commits
v1.9.2
...
feature/un
| Author | SHA1 | Date | |
|---|---|---|---|
| c02661ff78 | |||
| 66f28d9cf9 | |||
| 8bbbff7afc | |||
| 59747ee121 | |||
| 599ca63f06 | |||
| 9f8eab2ad2 | |||
| 69c483ac46 | |||
| ac178ccbb0 | |||
| 3f16828bdf | |||
| 0f7cd94e14 | |||
| 2cc6512afa | |||
| 151c98c884 | |||
| ba17d7940e | |||
| 46120dbd56 | |||
| 571c4797fa | |||
| c7d107a578 | |||
| 5f50e98a0a | |||
| 45f831ca1c | |||
| 73356cca30 | |||
| b7e630c1d8 | |||
| 13c8bc29ae | |||
| 51a892fd76 | |||
| 9cc8e573ec | |||
| a80464abc0 | |||
| 0b929d00a1 | |||
| 8ec2ecb454 | |||
| 2a9c5780c3 | |||
| fbfc9feab0 | |||
| 8486261555 | |||
| 5e1f2669e6 | |||
| 541f1c17b7 | |||
| 048fafa3db | |||
| 77e2c4f9b6 | |||
| 4eb1aaf640 | |||
| 61f2f83e0c | |||
| 2dc61e4937 | |||
| a6ca876823 | |||
| f615565323 | |||
| fcbf388ef8 | |||
| cbed10052b | |||
| 560213680c | |||
| 7e2542bf1f | |||
| df6bef7345 | |||
| aa57e8c48b | |||
| 822e8a9d0f | |||
| f0d1fb6647 | |||
| 751ffd59a0 | |||
| d0a17a8a31 | |||
| daa2750f89 | |||
| 29423123b3 | |||
| 5c10eb0009 | |||
| dca35a9900 | |||
| 4430d473cb | |||
| 0c27e3b5d8 | |||
| 616d8a039b | |||
| ee3d7714c2 | |||
| ddf896e3f9 | |||
| b9d0199115 | |||
| 1670b8e5ef | |||
| 1c43d12e4d | |||
| 5cf9922822 | |||
| 9a4a95feea | |||
| d3902c4c77 | |||
| 21ec8a33ae | |||
| 79d221de5e | |||
| 24fde20030 | |||
| 4a4409ca85 | |||
| d96d6a4b13 | |||
| 8f6b12d827 | |||
| a11714d07d | |||
| 52f7e68f25 | |||
| 217ac33675 | |||
| f2b8b136af | |||
| 2e07b213d1 | |||
| a2c1eaefba | |||
| 80ff266f9c | |||
| 6b1c5b7e30 | |||
| 80eefad5ea | |||
| 72556af24c | |||
| 2a5466c6c0 | |||
| 2d36a4ec71 | |||
| ded9da7d32 | |||
| 36ed26ad79 | |||
| 4e72a0baac | |||
| 8ca7eb3f49 | |||
| 32d3ff010a | |||
| cb68e1fb5c | |||
| 1bd7c6aba5 | |||
| 8b0e130b08 | |||
| bd1d33a157 | |||
| b70a89ec86 | |||
| da28305c2d |
5
.changeset/next-config-server-actions.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@mintel/next-config": patch
|
||||
---
|
||||
|
||||
fix: add serverActions.allowedOrigins to support branch deployments
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@mintel/monorepo": patch
|
||||
"acquisition-manager": patch
|
||||
"feedback-commander": patch
|
||||
---
|
||||
|
||||
fix: make directus extension build scripts more resilient
|
||||
18
.env
@@ -3,6 +3,7 @@ IMAGE_TAG=v1.8.19
|
||||
PROJECT_NAME=at-mintel
|
||||
PROJECT_COLOR=#82ed20
|
||||
GITEA_TOKEN=ccce002e30fe16a31a6c9d5a414740af2f72a582
|
||||
GITEA_HOST=https://git.infra.mintel.me
|
||||
OPENROUTER_API_KEY=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
|
||||
ZYTE_API_KEY=1f0f74726f044f55aaafc7ead32cd489
|
||||
REPLICATE_API_KEY=r8_W3grtpXMRfi0u3AM9VdkKbuWdZMmhwU2Tn0yt
|
||||
@@ -11,6 +12,11 @@ DATA_FOR_SEO_API_KEY=bWFyY0BtaW50ZWwubWU6MjQ0YjBjZmIzOGY3NTIzZA==
|
||||
DATA_FOR_SEO_LOGIN=marc@mintel.me
|
||||
DATA_FOR_SEO_PASSWORD=244b0cfb38f7523d
|
||||
|
||||
# Kabelfachmann LLM Configuration
|
||||
KABELFACHMANN_LLM_PROVIDER=openrouter
|
||||
KABELFACHMANN_OLLAMA_MODEL=qwen3.5
|
||||
KABELFACHMANN_OLLAMA_HOST=http://host.docker.internal:11434
|
||||
|
||||
# Authentication
|
||||
GATEKEEPER_PASSWORD=mintel
|
||||
AUTH_COOKIE_NAME=mintel_gatekeeper_session
|
||||
@@ -41,3 +47,15 @@ SENTRY_DSN=
|
||||
# Analytics (Umami)
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
|
||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
|
||||
UMAMI_USERNAME=mmintel
|
||||
UMAMI_PASSWORD=Tim300493.
|
||||
|
||||
# MCP Services
|
||||
QDRANT_URL=http://qdrant:6333
|
||||
SERPBEAR_API_KEY=Tim300493.
|
||||
GLITCHTIP_API_KEY=3d37db66fa1fc3a1cdcd1357728b056225aca950f0163b50c5e922344807ede9
|
||||
|
||||
# Corrected MCP Base URLs
|
||||
UMAMI_BASE_URL=https://analytics.infra.mintel.me
|
||||
GLITCHTIP_BASE_URL=https://errors.infra.mintel.me
|
||||
SERPBEAR_BASE_URL=https://seo.infra.mintel.me
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Project
|
||||
IMAGE_TAG=v1.9.2
|
||||
IMAGE_TAG=v1.9.17
|
||||
PROJECT_NAME=sample-website
|
||||
PROJECT_COLOR=#82ed20
|
||||
|
||||
|
||||
@@ -192,9 +192,6 @@ jobs:
|
||||
file: packages/infra/docker/Dockerfile.gatekeeper
|
||||
name: Gatekeeper (Product)
|
||||
|
||||
- image: image-processor
|
||||
file: apps/image-service/Dockerfile
|
||||
name: Image Processor
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -202,12 +199,31 @@ jobs:
|
||||
- name: 🐳 Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 🔐 Registry Login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: registry.infra.mintel.me
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASS }}
|
||||
- name: 🔐 Discover Valid Registry Token
|
||||
id: discover_token
|
||||
run: |
|
||||
echo "Testing available secrets against git.infra.mintel.me Docker registry..."
|
||||
TOKENS="${{ secrets.GITEA_PAT }} ${{ secrets.MINTEL_PRIVATE_TOKEN }} ${{ secrets.NPM_TOKEN }}"
|
||||
USERS="${{ github.repository_owner }} ${{ github.actor }} marcmintel mintel mmintel"
|
||||
|
||||
for TOKEN in $TOKENS; do
|
||||
if [ -n "$TOKEN" ]; then
|
||||
for U in $USERS; do
|
||||
if [ -n "$U" ]; then
|
||||
echo "Attempting docker login for a token with user $U..."
|
||||
if echo "$TOKEN" | docker login git.infra.mintel.me -u "$U" --password-stdin > /dev/null 2>&1; then
|
||||
echo "✅ Successfully authenticated with a token."
|
||||
echo "::add-mask::$TOKEN"
|
||||
echo "token=$TOKEN" >> $GITHUB_OUTPUT
|
||||
echo "user=$U" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
echo "❌ All available tokens failed to authenticate!"
|
||||
exit 1
|
||||
|
||||
- name: 🏗️ Build & Push ${{ matrix.name }}
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -219,8 +235,8 @@ jobs:
|
||||
provenance: false
|
||||
push: true
|
||||
secrets: |
|
||||
NPM_TOKEN=${{ secrets.NPM_TOKEN }}
|
||||
NPM_TOKEN=${{ steps.discover_token.outputs.token }}
|
||||
tags: |
|
||||
registry.infra.mintel.me/mintel/${{ matrix.image }}:${{ github.ref_name }}
|
||||
registry.infra.mintel.me/mintel/${{ matrix.image }}:latest
|
||||
git.infra.mintel.me/mmintel/${{ matrix.image }}:${{ github.ref_name }}
|
||||
git.infra.mintel.me/mmintel/${{ matrix.image }}:latest
|
||||
|
||||
|
||||
@@ -18,10 +18,55 @@ on:
|
||||
required: true
|
||||
GATEKEEPER_PASSWORD:
|
||||
required: true
|
||||
NPM_TOKEN:
|
||||
required: false
|
||||
MINTEL_PRIVATE_TOKEN:
|
||||
required: false
|
||||
GITEA_PAT:
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
qa_suite:
|
||||
name: 🛡️ Nightly QA Suite
|
||||
prepare:
|
||||
name: 🏗️ Prepare & Install
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
REGISTRY="${{ vars.REGISTRY_HOST || 'npm.infra.mintel.me' }}"
|
||||
echo "@mintel:registry=https://$REGISTRY" > .npmrc
|
||||
echo "//$REGISTRY/:_authToken=${{ secrets.NPM_TOKEN || secrets.GITEA_PAT || secrets.MINTEL_PRIVATE_TOKEN }}" >> .npmrc
|
||||
- name: 🔍 Diagnostics
|
||||
run: |
|
||||
echo "👤 User: $(id)"
|
||||
echo "📂 Directory: $(pwd)"
|
||||
echo "📂 Disk Space:"
|
||||
df -h
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
rm -rf .next .turbo node_modules || true
|
||||
pnpm install --no-frozen-lockfile --reporter=append-only
|
||||
- name: 📦 Archive dependencies
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: node_modules
|
||||
path: node_modules
|
||||
retention-days: 1
|
||||
|
||||
static:
|
||||
name: 🔍 Static Analysis
|
||||
needs: prepare
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
@@ -36,98 +81,126 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 🔐 Registry Auth
|
||||
run: |
|
||||
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm" > .npmrc
|
||||
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${{ secrets.MINTEL_PRIVATE_TOKEN || secrets.GITEA_PAT }}" >> .npmrc
|
||||
- name: Install dependencies
|
||||
id: deps
|
||||
run: |
|
||||
pnpm store prune
|
||||
pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: 📦 Cache APT Packages
|
||||
uses: actions/cache@v4
|
||||
- name: 📥 Restore dependencies
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
key: apt-cache-${{ runner.os }}-${{ runner.arch }}-chromium
|
||||
|
||||
- name: 💾 Cache Chromium
|
||||
id: cache-chromium
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /usr/bin/chromium
|
||||
key: ${{ runner.os }}-chromium-native-${{ hashFiles('package.json') }}
|
||||
|
||||
- name: 🔍 Install Chromium (Native & ARM64)
|
||||
if: steps.cache-chromium.outputs.cache-hit != 'true' && steps.deps.outcome == 'success'
|
||||
run: |
|
||||
rm -f /etc/apt/apt.conf.d/docker-clean
|
||||
apt-get update
|
||||
apt-get install -y gnupg wget ca-certificates
|
||||
OS_ID=$(. /etc/os-release && echo $ID)
|
||||
CODENAME=$(. /etc/os-release && echo $VERSION_CODENAME)
|
||||
if [ "$OS_ID" = "debian" ]; then
|
||||
apt-get install -y chromium
|
||||
else
|
||||
mkdir -p /etc/apt/keyrings
|
||||
KEY_ID="82BB6851C64F6880"
|
||||
wget -qO- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEY_ID" | gpg --dearmor > /etc/apt/keyrings/xtradeb.gpg
|
||||
echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list
|
||||
printf "Package: *\nPin: release o=LP-PPA-xtradeb-apps\nPin-Priority: 1001\n" > /etc/apt/preferences.d/xtradeb
|
||||
apt-get update
|
||||
apt-get install -y --allow-downgrades chromium
|
||||
fi
|
||||
[ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/google-chrome
|
||||
[ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/chromium-browser
|
||||
|
||||
# ── Quality Gates ─────────────────────────────────────────────────────────
|
||||
|
||||
- name: 🌐 Full Sitemap HTML Validation
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
name: node_modules
|
||||
- name: 🌐 HTML Validation
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ inputs.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run check:html
|
||||
|
||||
- name: 🌐 Dynamic Asset Presence & Error Scan
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
- name: 🖼️ Asset Scan
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ inputs.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run check:assets
|
||||
|
||||
- name: ♿ Accessibility Scan (WCAG)
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
accessibility:
|
||||
name: ♿ Accessibility
|
||||
needs: prepare
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 📥 Restore dependencies
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: node_modules
|
||||
- name: 🔍 Install Chromium
|
||||
run: |
|
||||
apt-get update && apt-get install -y gnupg wget ca-certificates
|
||||
CODENAME=$(. /etc/os-release && echo $VERSION_CODENAME)
|
||||
mkdir -p /etc/apt/keyrings
|
||||
wget -qO- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x82BB6851C64F6880" | gpg --dearmor > /etc/apt/keyrings/xtradeb.gpg
|
||||
echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list
|
||||
printf "Package: *\nPin: release o=LP-PPA-xtradeb-apps\nPin-Priority: 1001\n" > /etc/apt/preferences.d/xtradeb
|
||||
apt-get update && apt-get install -y --allow-downgrades chromium
|
||||
ln -sf /usr/bin/chromium /usr/bin/google-chrome
|
||||
- name: ♿ WCAG Scan
|
||||
continue-on-error: true
|
||||
env:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ inputs.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run check:wcag
|
||||
|
||||
- name: 📦 Unused Dependencies Scan (depcheck)
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
analysis:
|
||||
name: 🧪 Maintenance & Links
|
||||
needs: prepare
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: 📥 Restore dependencies
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: node_modules
|
||||
- name: 📦 Depcheck
|
||||
continue-on-error: true
|
||||
run: pnpm dlx depcheck --ignores="*eslint*,*typescript*,*tailwindcss*,*postcss*,*prettier*,*@types/*,*husky*,*lint-staged*,*@next/*,*@lhci/*,*commitlint*,*cspell*,*rimraf*,*@payloadcms/*,*start-server-and-test*,*html-validate*,*critters*,*dotenv*,*turbo*"
|
||||
|
||||
- name: 🔗 Markdown & HTML Link Check (Lychee)
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
- name: 🔗 Lychee Link Check
|
||||
uses: lycheeverse/lychee-action@v2
|
||||
with:
|
||||
args: --accept 200,204,429 --timeout 15 content/ app/ public/
|
||||
fail: true
|
||||
|
||||
- name: 🎭 LHCI Desktop Audit
|
||||
id: lhci_desktop
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
performance:
|
||||
name: 🎭 Lighthouse
|
||||
needs: prepare
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 10
|
||||
- name: 📥 Restore dependencies
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: node_modules
|
||||
- name: 🔍 Install Chromium
|
||||
run: |
|
||||
apt-get update && apt-get install -y gnupg wget ca-certificates
|
||||
CODENAME=$(. /etc/os-release && echo $VERSION_CODENAME)
|
||||
mkdir -p /etc/apt/keyrings
|
||||
wget -qO- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x82BB6851C64F6880" | gpg --dearmor > /etc/apt/keyrings/xtradeb.gpg
|
||||
echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list
|
||||
printf "Package: *\nPin: release o=LP-PPA-xtradeb-apps\nPin-Priority: 1001\n" > /etc/apt/preferences.d/xtradeb
|
||||
apt-get update && apt-get install -y --allow-downgrades chromium
|
||||
ln -sf /usr/bin/chromium /usr/bin/google-chrome
|
||||
- name: 🎭 LHCI Desktop
|
||||
env:
|
||||
LHCI_URL: ${{ inputs.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
run: pnpm run pagespeed:test -- --collect.settings.preset=desktop
|
||||
|
||||
- name: 📱 LHCI Mobile Audit
|
||||
id: lhci_mobile
|
||||
if: always() && steps.deps.outcome == 'success'
|
||||
- name: 📱 LHCI Mobile
|
||||
env:
|
||||
LHCI_URL: ${{ inputs.TARGET_URL }}
|
||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
||||
@@ -135,7 +208,7 @@ jobs:
|
||||
|
||||
notifications:
|
||||
name: 🔔 Notify
|
||||
needs: [qa_suite]
|
||||
needs: [prepare, static, accessibility, analysis, performance]
|
||||
if: always()
|
||||
runs-on: docker
|
||||
container:
|
||||
@@ -144,22 +217,30 @@ jobs:
|
||||
- name: 🔔 Gotify
|
||||
shell: bash
|
||||
run: |
|
||||
SUITE="${{ needs.qa_suite.result }}"
|
||||
PREPARE="${{ needs.prepare.result }}"
|
||||
STATIC="${{ needs.static.result }}"
|
||||
A11Y="${{ needs.accessibility.result }}"
|
||||
ANALYSIS="${{ needs.analysis.result }}"
|
||||
PERF="${{ needs.performance.result }}"
|
||||
|
||||
PROJECT="${{ inputs.PROJECT_NAME }}"
|
||||
URL="${{ inputs.TARGET_URL }}"
|
||||
|
||||
if [[ "$SUITE" != "success" ]]; then
|
||||
if [[ "$PREPARE" != "success" || "$STATIC" != "success" || "$PERF" != "success" ]]; then
|
||||
PRIORITY=8
|
||||
EMOJI="⚠️"
|
||||
EMOJI="🚨"
|
||||
STATUS_LINE="Nightly QA Failed! Action required."
|
||||
else
|
||||
PRIORITY=2
|
||||
EMOJI="✅"
|
||||
STATUS_LINE="Nightly QA Passed perfectly."
|
||||
STATUS_LINE="Nightly QA Passed."
|
||||
fi
|
||||
|
||||
TITLE="$EMOJI $PROJECT Nightly QA"
|
||||
MESSAGE="$STATUS_LINE\n$URL\nPlease check Pipeline output for details."
|
||||
MESSAGE="$STATUS_LINE
|
||||
Prepare: $PREPARE | Static: $STATIC | A11y: $A11Y
|
||||
Analysis: $ANALYSIS | Perf: $PERF
|
||||
$URL"
|
||||
|
||||
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
||||
-F "title=$TITLE" \
|
||||
|
||||
10
.gitignore
vendored
@@ -46,4 +46,12 @@ directus/uploads/directus-health-file
|
||||
# Estimation Engine Data
|
||||
data/crawls/
|
||||
packages/estimation-engine/out/
|
||||
apps/web/out/estimations/
|
||||
apps/web/out/estimations/
|
||||
|
||||
# Memory MCP
|
||||
data/qdrant/
|
||||
packages/memory-mcp/models/
|
||||
|
||||
# Kabelfachmann MCP
|
||||
packages/kabelfachmann-mcp/data/
|
||||
packages/kabelfachmann-mcp/models/
|
||||
@@ -5,46 +5,4 @@ if [ -f "$SCRIPT_DIR/scripts/validate-sdk-imports.sh" ]; then
|
||||
"$SCRIPT_DIR/scripts/validate-sdk-imports.sh" || exit 1
|
||||
fi
|
||||
|
||||
# Check if we are pushing a tag
|
||||
while read local_ref local_sha remote_ref remote_sha
|
||||
do
|
||||
if [[ "$remote_ref" == refs/tags/* ]]; then
|
||||
TAG=${remote_ref#refs/tags/}
|
||||
echo "🏷️ Tag detected: $TAG, ensuring versions are synced..."
|
||||
|
||||
# Run sync script
|
||||
pnpm sync-versions "$TAG"
|
||||
|
||||
# Check for changes in relevant files
|
||||
SYNC_FILES="package.json packages/*/package.json apps/*/package.json .env.example"
|
||||
CHANGES=$(git status --porcelain $SYNC_FILES)
|
||||
|
||||
if [[ -n "$CHANGES" ]]; then
|
||||
echo "📝 Version sync made changes. Integrating into tag..."
|
||||
|
||||
# Stage and commit
|
||||
git add $SYNC_FILES
|
||||
git commit -m "chore: sync versions to $TAG" --no-verify
|
||||
|
||||
# Force update the local tag to point to the new commit
|
||||
git tag -f "$TAG" > /dev/null
|
||||
|
||||
echo "✅ Tag $TAG has been updated locally with synced versions."
|
||||
echo "🚀 Auto-pushing updated tag..."
|
||||
|
||||
# Push the updated branch and tag directly (using --no-verify to avoid recursion)
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
if [ -n "$CURRENT_BRANCH" ]; then
|
||||
git push origin "$CURRENT_BRANCH" --no-verify
|
||||
fi
|
||||
git push origin "$TAG" --force --no-verify
|
||||
|
||||
echo "✨ Success! The hook synchronized the versions, committed to branch, and pushed the updated tag for you."
|
||||
echo "ℹ️ Note: The original push command was aborted in favor of the auto-push. This is normal."
|
||||
exit 1 # We MUST exit 1 here to stop git from proceeding with the original push which would fail
|
||||
else
|
||||
echo "✨ Versions already in sync for $TAG."
|
||||
exit 0 # Allow git to proceed with the original push since we didn't do it ourselves
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
46
ARCHITECTURE.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Mintel Universal Design System - Architecture Manifesto
|
||||
|
||||
Dieses Dokument ist der absolute Nordstern für alle Entwickler und KI-Agenten, die an der Mintel Codebase arbeiten. Es definiert, wie wir UI-Komponenten bauen, isolieren und skalieren, ohne in eine "Lock-in"-Falle zu tappen.
|
||||
|
||||
## 1. Das Problem: Die Design System Falle
|
||||
Wir bauen eine neutrale Basis (`@mintel/ui`), auf der Web, Print (PDF), Präsentationen und Remotion-Videos basieren.
|
||||
Das größte Risiko: Komponenten werden im Laufe der Zeit so komplex (mit dutzenden `if/else`-Switches für Kunden-Sonderfälle), dass niemand sich mehr traut, sie anzufassen, aus Angst, andere Projekte zu zerstören.
|
||||
|
||||
## 2. Die Zwiebel-Architektur (Core vs. Components)
|
||||
Um dieses Problem zu lösen, teilen wir unser UI-Paket logisch in zwei Ebenen:
|
||||
|
||||
### Tier 1: Primitives (Der Kern)
|
||||
- **Was ist das?** Die absoluten Grundbausteine: `<Box>`, `<Text>`, `<Flex>`, `<Grid>`.
|
||||
- **Die Regel:** Diese Komponenten haben *keine* Meinung. Sie dienen ausschließlich als saubere, typisierte Wrapper, um Tailwind-Klassen und Props durchzureichen. Sie dürfen sich architektonisch nie ändern.
|
||||
- **Warum?** Weil Projekte im Notfall immer auf diese Bausteine zurückfallen können, um eigene, hochgradig individuelle Layouts zu bauen.
|
||||
|
||||
### Tier 2: Base Components (Der Mintel Standard)
|
||||
- **Was ist das?** Fertige Komponenten wie `<Button>`, `<Card>`, `<HeroSection>`.
|
||||
- **Die Regel:** Sie haben den "neutralen" Mintel-Standardlook. Sie bauen zu 100% auf Tier 1 Primitives auf.
|
||||
|
||||
## 3. Die 3 Goldenen Regeln gegen den "Lock-in"
|
||||
|
||||
Wenn ein spezifisches Projekt (z.B. ein neuer Kunde) eine Komponente braucht, die radikal vom Mintel-Standard abweicht, gelten folgende Eskalationsstufen:
|
||||
|
||||
### Eskalation 1: Compound Components
|
||||
Komponenten dürfen niemals gigantische Props-Monster (`<Card hideHeader={true} showFooter={false} />`) werden.
|
||||
Wir nutzen **Compound Components**: Das Projekt setzt die Card selbst aus `<Card.Header>`, `<Card.Body>` und `<Card.Footer>` zusammen. Braucht das Projekt den Header nicht, lässt es ihn einfach weg.
|
||||
|
||||
### Eskalation 2: Das `asChild` Pattern (Slot)
|
||||
Wir sperren Projekte niemals in unsere DOM-Struktur ein. Wenn eine Komponente Logik enthält, nutzen wir das `asChild` (oder Slot) Pattern.
|
||||
*Beispiel:* Ein `<Button asChild>` rendert kein `<button>` HTML-Tag, sondern reicht seine Tailwind-Styles und seine Accessibility-Logik transparent an das Kind-Element durch (z.B. einen Next.js `<Link>`).
|
||||
|
||||
### Eskalation 3: Der "Eject" (Der Shadcn-Weg)
|
||||
Wenn eine `@mintel/ui` Komponente absolut nicht mehr für den Kunden X passt, wird sie **nicht** durch `if (kunde === 'X')`-Logik in der Basis "aufgebohrt".
|
||||
Stattdessen **ejecten** (kopieren) wir den Source-Code der Komponente physisch in das Projekt von Kunde X (`apps/kunde-x/components/`). Ab diesem Moment "besitzt" das Projekt diese Komponente und kann sie nach Belieben modifizieren. Die neutrale Basis bleibt sauber!
|
||||
|
||||
## 4. Agenten & LLM Workflow
|
||||
Jeder KI-Agent, der eine neue Komponente für `@mintel/ui` baut, muss folgende Checkliste abarbeiten:
|
||||
|
||||
1. **Ladle-First (TDD):** Keine Komponente wird ohne eine dazugehörige `.stories.tsx` Datei für den Ladle-Katalog akzeptiert. Ladle ist unsere Source of Truth.
|
||||
2. **Keine Business-Logik:** Die Komponente darf keinen State (außer internen UI-State wie `isOpen`), keine API-Fetches und keine projektspezifischen Konstanten enthalten.
|
||||
3. **Universalität prüfen:** Die Komponente darf keine reinen Browser-APIs (`window.location`) ohne Fallbacks nutzen, damit sie auch serverseitig in der Print-Engine (PDF) und in der Remotion-Engine gerendert werden kann.
|
||||
4. **Tailwind pur:** Nutze ausschließlich Tailwind-Klassen. Keine externen CSS-Module oder `style`-Tags, wenn nicht physikalisch zwingend (z.B. bei dynamischer Frame-Interpolation in Videos).
|
||||
|
||||
---
|
||||
*Wer dieses Manifest bricht, sabotiert die Skalierbarkeit des Monorepos. Halte dich an die Zwiebel-Architektur!*
|
||||
@@ -1,5 +1,5 @@
|
||||
# Stage 1: Builder
|
||||
FROM registry.infra.mintel.me/mintel/nextjs:latest AS builder
|
||||
FROM git.infra.mintel.me/mmintel/nextjs:latest AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Clean the workspace in case the base image is dirty
|
||||
@@ -37,7 +37,7 @@ COPY . .
|
||||
RUN pnpm build
|
||||
|
||||
# Stage 2: Runner
|
||||
FROM registry.infra.mintel.me/mintel/runtime:latest AS runner
|
||||
FROM git.infra.mintel.me/mmintel/runtime:latest AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
24
apps/example-presentation/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
73
apps/example-presentation/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
||||
|
||||
## React Compiler
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
// Remove tseslint.configs.recommended and replace with this
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
// Other configs...
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs['recommended-typescript'],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
22
apps/example-presentation/eslint.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
13
apps/example-presentation/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>example-presentation</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
31
apps/example-presentation/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "example-presentation",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mintel/ui": "workspace:*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"spectacle": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
"typescript": "~5.7.2",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vite": "^6.0.5"
|
||||
}
|
||||
}
|
||||
1
apps/example-presentation/public/favicon.svg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
24
apps/example-presentation/public/icons.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||
</symbol>
|
||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||
</symbol>
|
||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||
</symbol>
|
||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||
</symbol>
|
||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
184
apps/example-presentation/src/App.css
Normal file
@@ -0,0 +1,184 @@
|
||||
.counter {
|
||||
font-size: 16px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: var(--accent);
|
||||
background: var(--accent-bg);
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
|
||||
.base,
|
||||
.framework,
|
||||
.vite {
|
||||
inset-inline: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.base {
|
||||
width: 170px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.framework,
|
||||
.vite {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.framework {
|
||||
z-index: 1;
|
||||
top: 34px;
|
||||
height: 28px;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
||||
scale(1.4);
|
||||
}
|
||||
|
||||
.vite {
|
||||
z-index: 0;
|
||||
top: 107px;
|
||||
height: 26px;
|
||||
width: auto;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
||||
scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
#center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
padding: 32px 20px 24px;
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: left;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 0;
|
||||
padding: 32px;
|
||||
@media (max-width: 1024px) {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 16px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
#docs {
|
||||
border-right: 1px solid var(--border);
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 32px 0 0;
|
||||
|
||||
.logo {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-h);
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
background: var(--social-bg);
|
||||
display: flex;
|
||||
padding: 6px 12px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.button-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
flex: 1 1 calc(50% - 8px);
|
||||
}
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#spacer {
|
||||
height: 88px;
|
||||
border-top: 1px solid var(--border);
|
||||
@media (max-width: 1024px) {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4.5px;
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
border-left-color: var(--border);
|
||||
}
|
||||
&::after {
|
||||
right: 0;
|
||||
border-right-color: var(--border);
|
||||
}
|
||||
}
|
||||
26
apps/example-presentation/src/App.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Deck, Slide, Heading, Text as SpectacleText } from 'spectacle';
|
||||
import { Box, Text, EnvironmentProvider } from '@mintel/ui';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<EnvironmentProvider env="presentation">
|
||||
<Deck>
|
||||
<Slide>
|
||||
<Box className="flex flex-col items-center justify-center h-full">
|
||||
<Heading>Mintel Universal Design System</Heading>
|
||||
<SpectacleText>One Codebase. Web, Print, and Presentations.</SpectacleText>
|
||||
|
||||
<Box className="mt-8 p-4 bg-mintel-primary rounded-xl shadow-xl">
|
||||
<Text className="text-white text-xl">
|
||||
This Box and Text comes directly from @mintel/ui!
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Slide>
|
||||
</Deck>
|
||||
</EnvironmentProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
BIN
apps/example-presentation/src/assets/hero.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
apps/example-presentation/src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
1
apps/example-presentation/src/assets/vite.svg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
111
apps/example-presentation/src/index.css
Normal file
@@ -0,0 +1,111 @@
|
||||
:root {
|
||||
--text: #6b6375;
|
||||
--text-h: #08060d;
|
||||
--bg: #fff;
|
||||
--border: #e5e4e7;
|
||||
--code-bg: #f4f3ec;
|
||||
--accent: #aa3bff;
|
||||
--accent-bg: rgba(170, 59, 255, 0.1);
|
||||
--accent-border: rgba(170, 59, 255, 0.5);
|
||||
--social-bg: rgba(244, 243, 236, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
||||
|
||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--mono: ui-monospace, Consolas, monospace;
|
||||
|
||||
font: 18px/145% var(--sans);
|
||||
letter-spacing: 0.18px;
|
||||
color-scheme: light dark;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text: #9ca3af;
|
||||
--text-h: #f3f4f6;
|
||||
--bg: #16171d;
|
||||
--border: #2e303a;
|
||||
--code-bg: #1f2028;
|
||||
--accent: #c084fc;
|
||||
--accent-bg: rgba(192, 132, 252, 0.15);
|
||||
--accent-border: rgba(192, 132, 252, 0.5);
|
||||
--social-bg: rgba(47, 48, 58, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
||||
}
|
||||
|
||||
#social .button-icon {
|
||||
filter: invert(1) brightness(2);
|
||||
}
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 1126px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
border-inline: 1px solid var(--border);
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-family: var(--heading);
|
||||
font-weight: 500;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 56px;
|
||||
letter-spacing: -1.68px;
|
||||
margin: 32px 0;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 36px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 118%;
|
||||
letter-spacing: -0.24px;
|
||||
margin: 0 0 8px;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code,
|
||||
.counter {
|
||||
font-family: var(--mono);
|
||||
display: inline-flex;
|
||||
border-radius: 4px;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 15px;
|
||||
line-height: 135%;
|
||||
padding: 4px 8px;
|
||||
background: var(--code-bg);
|
||||
}
|
||||
10
apps/example-presentation/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
25
apps/example-presentation/tsconfig.app.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023", "DOM"],
|
||||
"module": "esnext",
|
||||
"types": ["vite/client"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
apps/example-presentation/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
24
apps/example-presentation/tsconfig.node.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "esnext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
7
apps/example-presentation/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
19
apps/example-print/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "example-print",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build:pdf": "ts-node src/generator.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mintel/ui": "workspace:*",
|
||||
"@react-pdf/renderer": "^3.4.0",
|
||||
"react": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.0.0",
|
||||
"@types/react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
30
apps/example-print/src/generator.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import ReactPDF, { Document, Page } from '@react-pdf/renderer';
|
||||
import { Box, Text, EnvironmentProvider } from '@mintel/ui';
|
||||
|
||||
const InvoiceDocument = () => (
|
||||
<EnvironmentProvider env="print">
|
||||
<Document>
|
||||
<Page size="A4" style={{ padding: 40, backgroundColor: '#ffffff' }}>
|
||||
<Box pdfStyle={{ marginBottom: 20, borderBottom: '2px solid #1E40AF', paddingBottom: 10 }}>
|
||||
<Text pdfStyle={{ fontSize: 28, color: '#1E40AF', fontWeight: 'bold' }}>
|
||||
Mintel Universal Design
|
||||
</Text>
|
||||
</Box>
|
||||
<Box pdfStyle={{ padding: 20, backgroundColor: '#f3f4f6', borderRadius: 8 }}>
|
||||
<Text pdfStyle={{ fontSize: 14, color: '#374151' }}>
|
||||
This PDF is generated using the exact same React components as the Web application.
|
||||
</Text>
|
||||
</Box>
|
||||
</Page>
|
||||
</Document>
|
||||
</EnvironmentProvider>
|
||||
);
|
||||
|
||||
const generatePDF = async () => {
|
||||
console.log('Generating Universal PDF...');
|
||||
await ReactPDF.render(<InvoiceDocument />, `${__dirname}/output.pdf`);
|
||||
console.log('Successfully saved to output.pdf');
|
||||
};
|
||||
|
||||
generatePDF();
|
||||
22
apps/example-video/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@mintel/example-video",
|
||||
"version": "1.0.0",
|
||||
"description": "Remotion video project using Mintel UI",
|
||||
"scripts": {
|
||||
"start": "remotion studio",
|
||||
"build": "remotion render"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mintel/ui": "workspace:*",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"remotion": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remotion/cli": "^4.0.0",
|
||||
"@remotion/tailwind": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"tailwindcss": "^3.0.0"
|
||||
}
|
||||
}
|
||||
6
apps/example-video/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
4
apps/example-video/remotion.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Config } from "@remotion/cli/config";
|
||||
|
||||
Config.setVideoImageFormat("jpeg");
|
||||
Config.setOverwriteOutput(true);
|
||||
40
apps/example-video/src/Composition.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
import { useCurrentFrame, useVideoConfig, interpolate } from "remotion";
|
||||
import { Box, Text } from "@mintel/ui";
|
||||
import "../../ui/index.css"; // Ensure Tailwind CSS from UI package is loaded
|
||||
|
||||
export const MintelPromo: React.FC = () => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
|
||||
// Fade in over the first 1 second (30 frames)
|
||||
const opacity = interpolate(frame, [0, fps], [0, 1], {
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
// Slide up over the first 1 second
|
||||
const translateY = interpolate(frame, [0, fps], [50, 0], {
|
||||
extrapolateRight: "clamp",
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ flex: 1, backgroundColor: "#f3f4f6" }}>
|
||||
<Box
|
||||
className="w-full h-full flex items-center justify-center bg-gray-100"
|
||||
style={{ opacity }}
|
||||
>
|
||||
<Box
|
||||
className="bg-white p-12 rounded-3xl shadow-2xl flex flex-col items-center"
|
||||
style={{ transform: `translateY(${translateY}px)` }}
|
||||
>
|
||||
<Text variant="h1" className="text-blue-600 mb-4 text-8xl font-bold">
|
||||
Mintel Video
|
||||
</Text>
|
||||
<Text variant="body" className="text-gray-600 text-3xl">
|
||||
Powered by Remotion & @mintel/ui
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
18
apps/example-video/src/Root.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
import { Composition } from "remotion";
|
||||
import { MintelPromo } from "./Composition";
|
||||
|
||||
export const RemotionRoot: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Composition
|
||||
id="MintelPromo"
|
||||
component={MintelPromo}
|
||||
durationInFrames={150}
|
||||
fps={30}
|
||||
width={1920}
|
||||
height={1080}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
4
apps/example-video/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { registerRoot } from "remotion";
|
||||
import { RemotionRoot } from "./Root";
|
||||
|
||||
registerRoot(RemotionRoot);
|
||||
11
apps/example-video/tailwind.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{ts,tsx}",
|
||||
"../../packages/ui/src/**/*.{ts,tsx}"
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
20
apps/example-video/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
41
apps/example-web/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
5
apps/example-web/AGENTS.md
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
# This is NOT the Next.js you know
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
1
apps/example-web/CLAUDE.md
Normal file
@@ -0,0 +1 @@
|
||||
@AGENTS.md
|
||||
36
apps/example-web/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
18
apps/example-web/eslint.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTs from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
...nextTs,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
7
apps/example-web/next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
26
apps/example-web/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "example-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "16.2.7",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.2.7",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
4077
apps/example-web/pnpm-lock.yaml
generated
Normal file
3
apps/example-web/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
ignoredBuiltDependencies:
|
||||
- sharp
|
||||
- unrs-resolver
|
||||
7
apps/example-web/postcss.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
1
apps/example-web/public/file.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 391 B |
1
apps/example-web/public/globe.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
apps/example-web/public/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
apps/example-web/public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
1
apps/example-web/public/window.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
BIN
apps/example-web/src/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
26
apps/example-web/src/app/globals.css
Normal file
@@ -0,0 +1,26 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-geist-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
33
apps/example-web/src/app/layout.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
||||
>
|
||||
<body className="min-h-full flex flex-col">{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
65
apps/example-web/src/app/page.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
apps/example-web/tsconfig.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.mts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sample-website",
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.17",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
177
blender-mv.sh
Executable file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env bash
|
||||
# blender-mv: A smart file/directory mover that automatically updates Blender paths
|
||||
|
||||
set -e
|
||||
|
||||
print_usage() {
|
||||
echo "Usage: blender-mv [OPTIONS] SOURCE DEST"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --search-project <dir> Scan this specific project directory for .blend files"
|
||||
echo " to update (useful if you move an external asset folder,"
|
||||
echo " and old .blend files referencing it are elsewhere)."
|
||||
echo ""
|
||||
echo "Example: blender-mv ./old_folder ./new_folder"
|
||||
}
|
||||
|
||||
SEARCH_PROJECT=""
|
||||
|
||||
# Parse arguments
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
--search-project) SEARCH_PROJECT="$2"; shift 2 ;;
|
||||
-h|--help) print_usage; exit 0 ;;
|
||||
*)
|
||||
if [[ -z "$SOURCE" ]]; then
|
||||
SOURCE="$1"
|
||||
elif [[ -z "$DEST" ]]; then
|
||||
DEST="$1"
|
||||
else
|
||||
echo "Unknown parameter passed: $1"
|
||||
print_usage
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$SOURCE" || -z "$DEST" ]]; then
|
||||
echo "Error: Both SOURCE and DEST must be provided."
|
||||
print_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -e "$SOURCE" ]]; then
|
||||
echo "Error: Source path '$SOURCE' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to get absolute normalized path (handles missing destination gracefully)
|
||||
get_abs_path() {
|
||||
python3 -c 'import os,sys; print(os.path.normpath(os.path.abspath(sys.argv[1])))' "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# Resolve source before moving
|
||||
ABS_SOURCE=$(get_abs_path "$SOURCE")
|
||||
|
||||
# Determine resulting target path
|
||||
if [[ -d "$DEST" ]]; then
|
||||
# If DEST is an existing dir, standard mv puts SOURCE inside it
|
||||
BASENAME_SOURCE=$(basename "$SOURCE")
|
||||
ABS_DEST=$(get_abs_path "$DEST/$BASENAME_SOURCE")
|
||||
else
|
||||
# Placed exactly at DEST (rename or target dir doesn't exist yet)
|
||||
ABS_DEST=$(get_abs_path "$DEST")
|
||||
fi
|
||||
|
||||
echo "Moving: $SOURCE -> $DEST"
|
||||
mv "$SOURCE" "$DEST"
|
||||
|
||||
echo "Replacing paths in Blender's recent-files lists..."
|
||||
# Find all recent-files.txt across Blender versions and replace the string
|
||||
find "$HOME/Library/Application Support/Blender" -type f -name "recent-files.txt" 2>/dev/null | while read -r RECENT_FILE; do
|
||||
# Using sed with alternative delimiter to avoid path interference
|
||||
sed -i '' "s|${ABS_SOURCE}|${ABS_DEST}|g" "$RECENT_FILE"
|
||||
done
|
||||
|
||||
# Prepare the embedded Python payload to update blend paths
|
||||
TMP_PY=$(mktemp /tmp/blender_update_paths.XXXXXX.py)
|
||||
cat << 'EOF' > "$TMP_PY"
|
||||
import bpy
|
||||
import sys
|
||||
|
||||
def replace_in_prop(prop, old_path, new_path):
|
||||
if prop and isinstance(prop, str) and old_path in prop:
|
||||
return prop.replace(old_path, new_path)
|
||||
return prop
|
||||
|
||||
def run():
|
||||
# Args are passed after `--`
|
||||
if '--' not in sys.argv:
|
||||
return
|
||||
idx = sys.argv.index('--')
|
||||
if len(sys.argv) <= idx + 2:
|
||||
return
|
||||
|
||||
old_path = sys.argv[idx + 1]
|
||||
new_path = sys.argv[idx + 2]
|
||||
|
||||
changed = False
|
||||
|
||||
# 1. Libraries (Linked objects/collections)
|
||||
for lib in bpy.data.libraries:
|
||||
new_fp = replace_in_prop(lib.filepath, old_path, new_path)
|
||||
if new_fp != lib.filepath:
|
||||
lib.filepath = new_fp
|
||||
changed = True
|
||||
|
||||
# 2. Images (Textures, Env Maps)
|
||||
for img in bpy.data.images:
|
||||
new_fp = replace_in_prop(img.filepath, old_path, new_path)
|
||||
if new_fp != img.filepath:
|
||||
img.filepath = new_fp
|
||||
changed = True
|
||||
|
||||
# 3. Cache files (Alembic/VDB)
|
||||
for cache in bpy.data.cache_files:
|
||||
new_fp = replace_in_prop(cache.filepath, old_path, new_path)
|
||||
if new_fp != cache.filepath:
|
||||
cache.filepath = new_fp
|
||||
changed = True
|
||||
|
||||
# 4. Fonts
|
||||
for font in bpy.data.fonts:
|
||||
new_fp = replace_in_prop(font.filepath, old_path, new_path)
|
||||
if new_fp != font.filepath:
|
||||
font.filepath = new_fp
|
||||
changed = True
|
||||
|
||||
# 5. Video strips
|
||||
for scene in bpy.data.scenes:
|
||||
if scene.sequence_editor:
|
||||
for strip in scene.sequence_editor.sequences_all:
|
||||
if strip.type in ('MOVIE', 'IMAGE'):
|
||||
new_fp = replace_in_prop(strip.filepath, old_path, new_path)
|
||||
if new_fp != strip.filepath:
|
||||
strip.filepath = new_fp
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
print(f"[*] Updated paths in {bpy.data.filepath}")
|
||||
bpy.ops.wm.save_mainfile()
|
||||
|
||||
try:
|
||||
run()
|
||||
except Exception as e:
|
||||
print(f"[!] Error updating blend file paths: {e}")
|
||||
EOF
|
||||
|
||||
# Determine what to search for blend files
|
||||
if [[ -n "$SEARCH_PROJECT" ]]; then
|
||||
SEARCH_ROOT=$(get_abs_path "$SEARCH_PROJECT")
|
||||
echo "Scanning project root '$SEARCH_ROOT' for .blend files..."
|
||||
elif [[ -d "$ABS_DEST" ]]; then
|
||||
SEARCH_ROOT="$ABS_DEST"
|
||||
echo "Scanning moved directory for .blend files..."
|
||||
elif [[ "$ABS_DEST" == *.blend ]]; then
|
||||
SEARCH_ROOT="$ABS_DEST"
|
||||
echo "Updating moved .blend file..."
|
||||
else
|
||||
echo "No project root defined and moved object is not a directory or .blend file."
|
||||
echo "Finished."
|
||||
rm -f "$TMP_PY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Execute headless blender on all found blend files
|
||||
if [[ -d "$SEARCH_ROOT" ]]; then
|
||||
find "$SEARCH_ROOT" -type f -name "*.blend" -print0 | while IFS= read -r -d '' BLEND_FILE; do
|
||||
blender -b "$BLEND_FILE" -P "$TMP_PY" -- "$ABS_SOURCE" "$ABS_DEST" >/dev/null 2>&1 || echo "Warning: Failed to process $BLEND_FILE"
|
||||
done
|
||||
elif [[ -f "$SEARCH_ROOT" && "$SEARCH_ROOT" == *.blend ]]; then
|
||||
blender -b "$SEARCH_ROOT" -P "$TMP_PY" -- "$ABS_SOURCE" "$ABS_DEST" >/dev/null 2>&1 || echo "Warning: Failed to process $SEARCH_ROOT"
|
||||
fi
|
||||
|
||||
rm -f "$TMP_PY"
|
||||
echo "Success: Move complete and paths updated."
|
||||
39
docker-compose.gatekeeper.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
services:
|
||||
gatekeeper-proxy:
|
||||
image: alpine:latest
|
||||
command: sleep infinity
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- infra
|
||||
labels:
|
||||
- "caddy=http://gatekeeper.localhost"
|
||||
- "caddy.route=/*"
|
||||
- "caddy.route.0_redir=/ /gatekeeper/login 302"
|
||||
- "caddy.route.1_reverse_proxy=gatekeeper-app:3000"
|
||||
|
||||
gatekeeper-app:
|
||||
image: node:20-alpine
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
- gatekeeper_root_node_modules:/app/node_modules
|
||||
- gatekeeper_pkg_node_modules:/app/packages/gatekeeper/node_modules
|
||||
- gatekeeper_next_cache:/app/packages/gatekeeper/.next
|
||||
- gatekeeper_pnpm_store:/pnpm
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- NPM_TOKEN=${NPM_TOKEN:-}
|
||||
networks:
|
||||
- infra
|
||||
command: >
|
||||
sh -c "corepack enable && pnpm config set store-dir /pnpm && pnpm install --no-frozen-lockfile && pnpm --filter @mintel/gatekeeper run dev --hostname 0.0.0.0 --port 3000"
|
||||
|
||||
networks:
|
||||
infra:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
gatekeeper_root_node_modules:
|
||||
gatekeeper_pkg_node_modules:
|
||||
gatekeeper_next_cache:
|
||||
gatekeeper_pnpm_store:
|
||||
90
docker-compose.mcps.yml
Normal file
@@ -0,0 +1,90 @@
|
||||
services:
|
||||
qdrant:
|
||||
image: qdrant/qdrant:latest
|
||||
container_name: qdrant-mcp
|
||||
ports:
|
||||
- "6335:6333"
|
||||
- "6336:6334"
|
||||
volumes:
|
||||
- ./data/qdrant:/qdrant/storage
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mcp-network
|
||||
|
||||
gitea-mcp:
|
||||
build:
|
||||
context: ./packages/gitea-mcp
|
||||
container_name: gitea-mcp
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3011:3001"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mcp-network
|
||||
|
||||
memory-mcp:
|
||||
build:
|
||||
context: ./packages/memory-mcp
|
||||
container_name: memory-mcp
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3002:3002"
|
||||
depends_on:
|
||||
- qdrant
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mcp-network
|
||||
|
||||
umami-mcp:
|
||||
build:
|
||||
context: ./packages/umami-mcp
|
||||
container_name: umami-mcp
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3003:3003"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mcp-network
|
||||
|
||||
serpbear-mcp:
|
||||
build:
|
||||
context: ./packages/serpbear-mcp
|
||||
container_name: serpbear-mcp
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3004:3004"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mcp-network
|
||||
|
||||
glitchtip-mcp:
|
||||
build:
|
||||
context: ./packages/glitchtip-mcp
|
||||
container_name: glitchtip-mcp
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3005:3005"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mcp-network
|
||||
|
||||
klz-payload-mcp:
|
||||
build:
|
||||
context: ./packages/klz-payload-mcp
|
||||
container_name: klz-payload-mcp
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3006:3006"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mcp-network
|
||||
|
||||
networks:
|
||||
mcp-network:
|
||||
driver: bridge
|
||||
@@ -22,6 +22,43 @@ services:
|
||||
- "caddy=http://${TRAEFIK_HOST:-acquisition.localhost}"
|
||||
- "caddy.reverse_proxy={{upstreams 3000}}"
|
||||
|
||||
at-mintel-ui:
|
||||
image: node:22-alpine
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
- ui_pnpm_store:/pnpm
|
||||
environment:
|
||||
- CI=true
|
||||
networks:
|
||||
- infra
|
||||
restart: unless-stopped
|
||||
command: >
|
||||
sh -c "corepack enable && pnpm config set store-dir /pnpm && pnpm install --no-frozen-lockfile --ignore-scripts || true; echo 'Starting ladle...'; pnpm --filter @mintel/ui exec ladle serve --host 0.0.0.0 --port 61000"
|
||||
labels:
|
||||
- "caddy=http://ui.localhost"
|
||||
- "caddy.reverse_proxy={{upstreams 61000}}"
|
||||
|
||||
at-mintel-video:
|
||||
image: node:22-alpine
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
- ui_pnpm_store:/pnpm
|
||||
environment:
|
||||
- CI=true
|
||||
networks:
|
||||
- infra
|
||||
restart: unless-stopped
|
||||
command: >
|
||||
sh -c "corepack enable && pnpm config set store-dir /pnpm && pnpm install --no-frozen-lockfile --ignore-scripts || true; pnpm --filter @mintel/example-video exec remotion studio --host 0.0.0.0 --port 3001"
|
||||
labels:
|
||||
- "caddy=http://video.localhost"
|
||||
- "caddy.reverse_proxy={{upstreams 3001}}"
|
||||
|
||||
networks:
|
||||
infra:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
ui_pnpm_store:
|
||||
|
||||
69
eslint-errors-2.txt
Normal file
@@ -0,0 +1,69 @@
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/gitea-mcp/src/index.ts[24m[0m
|
||||
[0m [2m11:0[22m [31merror[39m Parsing error: Identifier expected[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/glitchtip-mcp/src/index.ts[24m[0m
|
||||
[0m [2m124:19[22m [33mwarning[39m 'res' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/klz-payload-mcp/src/index.ts[24m[0m
|
||||
[0m [2m39:18[22m [33mwarning[39m 'e' is defined but never used. Allowed unused caught errors must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/memory-mcp/src/qdrant.test.ts[24m[0m
|
||||
[0m [2m7:52[22m [33mwarning[39m 'text' is defined but never used. Allowed unused args must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/page-audit/src/report.ts[24m[0m
|
||||
[0m [2m7:47[22m [33mwarning[39m 'PageAuditData' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m7:62[22m [33mwarning[39m 'AuditIssue' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/components/FieldGenerators/AiFieldButton.tsx[24m[0m
|
||||
[0m [2m11:13[22m [33mwarning[39m 'value' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/components/FieldGenerators/GenerateSlugButton.tsx[24m[0m
|
||||
[0m [2m20:21[22m [33mwarning[39m 'replaceState' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m21:13[22m [33mwarning[39m 'value' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/components/FieldGenerators/GenerateThumbnailButton.tsx[24m[0m
|
||||
[0m [2m21:13[22m [33mwarning[39m 'value' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/components/OptimizeButton.tsx[24m[0m
|
||||
[0m [2m5:10[22m [33mwarning[39m 'Button' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/tools/mcpAdapter.ts[24m[0m
|
||||
[0m [2m44:15[22m [33mwarning[39m 'toolSchema' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/tools/memoryDb.ts[24m[0m
|
||||
[0m [2m89:31[22m [33mwarning[39m 'query' is defined but never used. Allowed unused args must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/tools/payloadLocal.ts[24m[0m
|
||||
[0m [2m3:40[22m [33mwarning[39m 'User' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/types.ts[24m[0m
|
||||
[0m [2m1:15[22m [33mwarning[39m 'Plugin' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/ConceptPDF.tsx[24m[0m
|
||||
[0m [2m4:18[22m [33mwarning[39m 'PDFPage' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m5:10[22m [33mwarning[39m 'pdfStyles' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/EstimationPDF.tsx[24m[0m
|
||||
[0m [2m4:18[22m [33mwarning[39m 'PDFPage' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m5:10[22m [33mwarning[39m 'pdfStyles' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m54:11[22m [33mwarning[39m 'getPageNum' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/InfoPDF.tsx[24m[0m
|
||||
[0m [2m5:13[22m [33mwarning[39m 'PDFPage' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m12:5[22m [33mwarning[39m 'pdfStyles' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/pdf/SharedUI.tsx[24m[0m
|
||||
[0m [2m528:5[22m [33mwarning[39m 'bankData' is defined but never used. Allowed unused args must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/pdf/SimpleLayout.tsx[24m[0m
|
||||
[0m [2m4:52[22m [33mwarning[39m 'PDFText' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m5:26[22m [33mwarning[39m 'pdfStyles' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/seo-engine/src/report.ts[24m[0m
|
||||
[0m [2m5:3[22m [33mwarning[39m 'TopicCluster' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m6:3[22m [33mwarning[39m 'ContentGap' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m7:3[22m [33mwarning[39m 'CompetitorRanking' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[31m[1m✖ 28 problems (1 error, 27 warnings)[22m[39m[0m
|
||||
[0m[31m[1m[22m[39m[0m
|
||||
97
eslint-errors.txt
Normal file
@@ -0,0 +1,97 @@
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/gitea-mcp/src/index.ts[24m[0m
|
||||
[0m [2m12:5[22m [33mwarning[39m 'Resource' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m14:10[22m [33mwarning[39m 'z' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m427:30[22m [33mwarning[39m 'e' is defined but never used. Allowed unused caught errors must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m745:50[22m [31merror[39m Unnecessary escape character: \/ [2mno-useless-escape[22m[0m
|
||||
[0m [2m745:60[22m [31merror[39m Unnecessary escape character: \/ [2mno-useless-escape[22m[0m
|
||||
[0m [2m799:54[22m [31merror[39m Unnecessary escape character: \/ [2mno-useless-escape[22m[0m
|
||||
[0m [2m799:64[22m [31merror[39m Unnecessary escape character: \/ [2mno-useless-escape[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/glitchtip-mcp/src/index.ts[24m[0m
|
||||
[0m [2m124:19[22m [33mwarning[39m 'res' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/klz-payload-mcp/src/index.ts[24m[0m
|
||||
[0m [2m39:18[22m [33mwarning[39m 'e' is defined but never used. Allowed unused caught errors must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/memory-mcp/src/qdrant.test.ts[24m[0m
|
||||
[0m [2m7:52[22m [33mwarning[39m 'text' is defined but never used. Allowed unused args must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/page-audit/src/report.ts[24m[0m
|
||||
[0m [2m7:47[22m [33mwarning[39m 'PageAuditData' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m7:62[22m [33mwarning[39m 'AuditIssue' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/chatPlugin.ts[24m[0m
|
||||
[0m [2m1:15[22m [33mwarning[39m 'Config' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m10:17[22m [31merror[39m 'config' is never reassigned. Use 'const' instead [2mprefer-const[22m[0m
|
||||
[0m [2m48:37[22m [33mwarning[39m 'req' is defined but never used. Allowed unused args must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/components/ChatWindow/index.tsx[24m[0m
|
||||
[0m [2m43:5[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m [2m44:63[22m [33mwarning[39m 'setMessages' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/components/FieldGenerators/AiFieldButton.tsx[24m[0m
|
||||
[0m [2m11:13[22m [33mwarning[39m 'value' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/components/FieldGenerators/GenerateSlugButton.tsx[24m[0m
|
||||
[0m [2m20:21[22m [33mwarning[39m 'replaceState' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m21:13[22m [33mwarning[39m 'value' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/components/FieldGenerators/GenerateThumbnailButton.tsx[24m[0m
|
||||
[0m [2m21:13[22m [33mwarning[39m 'value' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/components/OptimizeButton.tsx[24m[0m
|
||||
[0m [2m5:10[22m [33mwarning[39m 'Button' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/endpoints/chatEndpoint.ts[24m[0m
|
||||
[0m [2m96:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m [2m100:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/tools/mcpAdapter.ts[24m[0m
|
||||
[0m [2m44:15[22m [33mwarning[39m 'toolSchema' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m53:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/tools/memoryDb.ts[24m[0m
|
||||
[0m [2m50:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m [2m88:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m [2m89:31[22m [33mwarning[39m 'query' is defined but never used. Allowed unused args must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/tools/payloadLocal.ts[24m[0m
|
||||
[0m [2m3:40[22m [33mwarning[39m 'User' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m25:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m [2m45:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m [2m61:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m [2m78:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m [2m95:13[22m [31merror[39m Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free [2m@typescript-eslint/ban-ts-comment[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/payload-ai/src/types.ts[24m[0m
|
||||
[0m [2m1:15[22m [33mwarning[39m 'Plugin' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/ConceptPDF.tsx[24m[0m
|
||||
[0m [2m4:18[22m [33mwarning[39m 'PDFPage' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m5:10[22m [33mwarning[39m 'pdfStyles' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/EstimationPDF.tsx[24m[0m
|
||||
[0m [2m4:18[22m [33mwarning[39m 'PDFPage' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m5:10[22m [33mwarning[39m 'pdfStyles' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m54:11[22m [33mwarning[39m 'getPageNum' is assigned a value but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/InfoPDF.tsx[24m[0m
|
||||
[0m [2m5:13[22m [33mwarning[39m 'PDFPage' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m12:5[22m [33mwarning[39m 'pdfStyles' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/pdf/SharedUI.tsx[24m[0m
|
||||
[0m [2m528:5[22m [33mwarning[39m 'bankData' is defined but never used. Allowed unused args must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/pdf-library/src/components/pdf/SimpleLayout.tsx[24m[0m
|
||||
[0m [2m4:52[22m [33mwarning[39m 'PDFText' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m5:26[22m [33mwarning[39m 'pdfStyles' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[4m/Users/marcmintel/Projects/at-mintel/packages/seo-engine/src/report.ts[24m[0m
|
||||
[0m [2m5:3[22m [33mwarning[39m 'TopicCluster' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m6:3[22m [33mwarning[39m 'ContentGap' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m [2m7:3[22m [33mwarning[39m 'CompetitorRanking' is defined but never used. Allowed unused vars must match /^_/u [2m@typescript-eslint/no-unused-vars[22m[0m
|
||||
[0m[0m
|
||||
[0m[31m[1m✖ 49 problems (16 errors, 33 warnings)[22m[39m[0m
|
||||
[0m[31m[1m[22m[39m[31m[1m 1 error and 0 warnings potentially fixable with the `--fix` option.[22m[39m[0m
|
||||
[0m[31m[1m[22m[39m[0m
|
||||
12
fix-private.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
import fs from 'fs';
|
||||
import glob from 'glob';
|
||||
|
||||
const files = glob.sync('/Users/marcmintel/Projects/at-mintel/packages/*/package.json');
|
||||
files.forEach(f => {
|
||||
const content = fs.readFileSync(f, 'utf8');
|
||||
if (content.includes('"private": true,')) {
|
||||
console.log(`Fixing ${f}`);
|
||||
const newContent = content.replace(/\s*"private": true,?\n/g, '\n');
|
||||
fs.writeFileSync(f, newContent);
|
||||
}
|
||||
});
|
||||
13
package.json
@@ -5,11 +5,19 @@
|
||||
"scripts": {
|
||||
"build": "pnpm -r build",
|
||||
"dev": "pnpm -r dev",
|
||||
"dev:gatekeeper": "bash -c 'trap \"COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml up --build --remove-orphans'",
|
||||
"dev:mcps:up": "docker-compose -f docker-compose.mcps.yml up -d --build --remove-orphans",
|
||||
"dev:mcps:down": "docker-compose -f docker-compose.mcps.yml down",
|
||||
"dev:mcps:watch": "pnpm -r --filter=\"./packages/*-mcp\" exec tsc -w",
|
||||
"dev:mcps": "npm run dev:mcps:up && npm run dev:mcps:watch",
|
||||
"start:mcps": "npm run dev:mcps:up",
|
||||
"start:mcps:force": "docker-compose -f docker-compose.mcps.yml up -d --build --force-recreate --remove-orphans",
|
||||
"lint": "pnpm -r --filter='./packages/**' --filter='./apps/**' lint",
|
||||
"test": "pnpm -r test",
|
||||
"changeset": "changeset",
|
||||
"version-packages": "changeset version",
|
||||
"sync-versions": "tsx scripts/sync-versions.ts --",
|
||||
"release:version": "bash scripts/release.sh",
|
||||
"release": "pnpm build && changeset publish",
|
||||
"release:tag": "pnpm build && pnpm -r publish --no-git-checks --access public",
|
||||
"prepare": "husky"
|
||||
@@ -34,6 +42,7 @@
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^27.4.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"pm2": "^6.0.14",
|
||||
"prettier": "^3.8.1",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.0.0",
|
||||
@@ -47,7 +56,7 @@
|
||||
"pino-pretty": "^13.1.3",
|
||||
"require-in-the-middle": "^8.0.1"
|
||||
},
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.17",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
@@ -66,4 +75,4 @@
|
||||
"@sentry/nextjs": "10.38.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/cli",
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.17",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://git.infra.mintel.me/api/packages/mmintel/npm"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/cloner",
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.17",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/concept-engine",
|
||||
"version": "1.9.2",
|
||||
"private": true,
|
||||
"version": "1.9.17",
|
||||
"description": "AI-powered web project concept generation and analysis",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/content-engine",
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.17",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/eslint-config",
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.17",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://git.infra.mintel.me/api/packages/mmintel/npm"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/estimation-engine",
|
||||
"version": "1.9.2",
|
||||
"private": true,
|
||||
"version": "1.9.17",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/gatekeeper",
|
||||
"version": "1.9.2",
|
||||
"private": true,
|
||||
"version": "1.9.17",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -12,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@mintel/next-utils": "workspace:*",
|
||||
"framer-motion": "^11.18.2",
|
||||
"lucide-react": "^0.474.0",
|
||||
"next": "16.1.6",
|
||||
"react": "^19.0.0",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-white text-slate-800 font-serif antialiased selection:bg-slate-900 selection:text-white;
|
||||
@apply bg-[#f5f5f7] text-black/80 font-serif antialiased selection:bg-black/10 selection:text-black;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply font-sans font-bold text-slate-900 tracking-tighter;
|
||||
@apply font-sans font-bold text-black tracking-tighter;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply mb-4 text-base leading-relaxed text-slate-700;
|
||||
@apply mb-4 text-base leading-relaxed text-black/50;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-slate-900 hover:text-slate-700 transition-colors no-underline;
|
||||
@apply text-black/50 hover:text-black transition-colors no-underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,34 +36,58 @@
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply inline-flex items-center justify-center px-6 py-3 border border-slate-200 bg-white text-slate-600 font-sans font-bold text-sm uppercase tracking-widest rounded-full transition-all duration-500 ease-industrial hover:border-slate-400 hover:text-slate-900 hover:bg-slate-50 hover:-translate-y-0.5 hover:shadow-xl hover:shadow-slate-100 active:translate-y-0 active:shadow-sm;
|
||||
@apply inline-flex items-center justify-center px-6 py-3 border border-black/10 bg-white text-black/60 font-sans font-bold text-sm uppercase tracking-widest rounded-full transition-all duration-500 ease-industrial hover:border-black/20 hover:text-black hover:bg-white hover:-translate-y-0.5 hover:shadow-xl hover:shadow-black/5 active:translate-y-0 active:shadow-sm;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply border-slate-900 text-slate-900 hover:bg-slate-900 hover:text-white;
|
||||
@apply border-black bg-black text-white hover:bg-black/85 hover:text-white;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
/* Custom scrollbar - light theme */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
background: #f5f5f7;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
background: #d1d1d6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
background: #b0b0b8;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%,
|
||||
100% {
|
||||
@@ -79,6 +103,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-slide-up {
|
||||
animation: slide-up 0.8s ease-out 0.2s forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.animate-shake {
|
||||
animation: shake 0.2s ease-in-out 0s 2;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,17 @@ const newsreader = Newsreader({
|
||||
export const metadata: Metadata = {
|
||||
title: "Gatekeeper | Access Control",
|
||||
description: "Mintel Infrastructure Protection",
|
||||
openGraph: {
|
||||
title: "Gatekeeper | Access Control",
|
||||
description: "Mintel Infrastructure Protection",
|
||||
siteName: "Mintel Gatekeeper",
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Gatekeeper | Access Control",
|
||||
description: "Mintel Infrastructure Protection",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ArrowRight, ShieldCheck } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { ShieldCheck } from "lucide-react";
|
||||
import { GateScene } from "../../components/gate-scene";
|
||||
import { AnimatedLoginForm } from "../../components/animated-login-form";
|
||||
|
||||
interface LoginPageProps {
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||
@@ -17,8 +18,8 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
|
||||
async function login(formData: FormData) {
|
||||
"use server";
|
||||
|
||||
const email = (formData.get("email") as string || "").trim();
|
||||
const password = (formData.get("password") as string || "").trim();
|
||||
const email = ((formData.get("email") as string) || "").trim();
|
||||
const password = ((formData.get("password") as string) || "").trim();
|
||||
|
||||
const expectedCode = process.env.GATEKEEPER_PASSWORD || "mintel";
|
||||
const adminEmail = process.env.DIRECTUS_ADMIN_EMAIL;
|
||||
@@ -116,7 +117,9 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
|
||||
}
|
||||
|
||||
if (userIdentity) {
|
||||
console.log(`[Login] Success: ${userIdentity} | Redirect: ${targetRedirect}`);
|
||||
console.log(
|
||||
`[Login] Success: ${userIdentity} | Redirect: ${targetRedirect}`,
|
||||
);
|
||||
const cookieStore = await cookies();
|
||||
// Store identity in the cookie (simplified for now, ideally signed)
|
||||
const sessionValue = JSON.stringify({
|
||||
@@ -127,7 +130,9 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
console.log(`[Login] Setting Cookie: ${authCookieName} | Domain: ${cookieDomain || "Default"}`);
|
||||
console.log(
|
||||
`[Login] Setting Cookie: ${authCookieName} | Domain: ${cookieDomain || "Default"}`,
|
||||
);
|
||||
|
||||
cookieStore.set(authCookieName, sessionValue, {
|
||||
httpOnly: true,
|
||||
@@ -145,101 +150,79 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center relative bg-white font-serif antialiased overflow-hidden">
|
||||
{/* Background Decor - Signature mintel.me style */}
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none opacity-[0.03] scale-[1.01]"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(to right, #000 1px, transparent 1px), linear-gradient(to bottom, #000 1px, transparent 1px)`,
|
||||
backgroundSize: "clamp(30px, 8vw, 40px) clamp(30px, 8vw, 40px)",
|
||||
}}
|
||||
/>
|
||||
<div className="min-h-screen flex items-center justify-center relative bg-[#f5f5f7] font-serif antialiased overflow-hidden selection:bg-black/10 selection:text-black">
|
||||
{/* 3D Digital Gate Background */}
|
||||
<GateScene />
|
||||
|
||||
<main className="relative z-10 w-full max-w-sm px-8 sm:px-6">
|
||||
<div className="space-y-12 sm:space-y-16 animate-fade-in">
|
||||
{/* Top Icon Box - Signature mintel.me Black Square */}
|
||||
<main className="relative z-10 w-full max-w-[380px] px-6 sm:px-4 pb-24 sm:pb-32 pointer-events-auto">
|
||||
<div className="space-y-10 animate-fade-in">
|
||||
{/* Top Icon Box */}
|
||||
<div className="flex justify-center">
|
||||
<div className="w-16 h-16 bg-black rounded-xl flex items-center justify-center shadow-xl shadow-slate-100 hover:scale-105 transition-all duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] rotate-2 hover:rotate-0">
|
||||
<Image
|
||||
<a
|
||||
href="https://mintel.me"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-14 h-14 bg-white rounded-2xl flex items-center justify-center shadow-lg shadow-black/[0.06] hover:scale-105 hover:shadow-black/10 transition-all duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] border border-black/[0.06] hover:border-black/10"
|
||||
>
|
||||
<img
|
||||
src="/gatekeeper/icon-white.svg"
|
||||
alt="Mintel"
|
||||
width={32}
|
||||
height={32}
|
||||
className="w-8 h-8"
|
||||
unoptimized
|
||||
style={{ filter: "invert(1)" }}
|
||||
width={28}
|
||||
height={28}
|
||||
className="w-7 h-7 opacity-80"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="space-y-12 animate-slide-up">
|
||||
<div className="text-center space-y-4">
|
||||
<h1 className="text-xs font-sans font-bold uppercase tracking-[0.4em] text-slate-900 border-b border-slate-50 pb-4 inline-block mx-auto min-w-[200px]">
|
||||
{projectName} <span className="text-slate-300">Gatekeeper</span>
|
||||
<div className="space-y-8 animate-slide-up">
|
||||
<div className="text-center space-y-3">
|
||||
<h1 className="text-[11px] font-sans font-bold uppercase tracking-[0.5em] text-black/80 pb-3 inline-block mx-auto min-w-[220px]">
|
||||
{projectName} <span className="text-black/30">Gatekeeper</span>
|
||||
</h1>
|
||||
<p className="text-[10px] text-slate-400 font-sans uppercase tracking-widest italic flex items-center justify-center gap-2">
|
||||
<span className="w-1 h-1 bg-slate-200 rounded-full" />
|
||||
Infrastructure Protection
|
||||
<span className="w-1 h-1 bg-slate-200 rounded-full" />
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="h-px w-8 bg-gradient-to-r from-transparent to-black/10" />
|
||||
<p className="text-[8px] text-black/30 font-sans uppercase tracking-[0.35em] font-semibold">
|
||||
Infrastructure Protection
|
||||
</p>
|
||||
<div className="h-px w-8 bg-gradient-to-l from-transparent to-black/10" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 text-red-600 px-5 py-3 rounded-2xl text-[9px] font-sans font-bold uppercase tracking-widest flex items-center gap-3 border border-red-100 animate-shake">
|
||||
<ShieldCheck className="w-4 h-4" />
|
||||
<div className="bg-red-50 backdrop-blur-md text-red-600 px-5 py-4 rounded-2xl text-[9px] font-sans font-bold uppercase tracking-widest flex items-center gap-3 border border-red-200 animate-shake">
|
||||
<ShieldCheck className="w-4 h-4 text-red-500/70" />
|
||||
<span>Access Denied. Try Again.</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form action={login} className="space-y-4">
|
||||
<input type="hidden" name="redirect" value={redirectUrl} />
|
||||
{/* The Animated Framer Motion Form */}
|
||||
<AnimatedLoginForm
|
||||
redirectUrl={redirectUrl}
|
||||
loginAction={login}
|
||||
projectName={projectName}
|
||||
/>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="relative group">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="EMAIL (OPTIONAL)"
|
||||
className="w-full bg-slate-50/50 border border-slate-200 rounded-2xl px-6 py-4 focus:outline-none focus:border-slate-900 focus:bg-white transition-all text-[10px] font-sans font-bold tracking-[0.2em] uppercase placeholder:text-slate-300 shadow-sm shadow-slate-50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative group">
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
required
|
||||
autoFocus
|
||||
autoComplete="current-password"
|
||||
placeholder="ACCESS CODE"
|
||||
className="w-full bg-slate-50/50 border border-slate-200 rounded-2xl px-6 py-4 focus:outline-none focus:border-slate-900 focus:bg-white transition-all text-sm font-sans font-bold tracking-[0.3em] uppercase placeholder:text-slate-300 placeholder:tracking-widest shadow-sm shadow-slate-50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary w-full py-5 rounded-2xl text-[10px] shadow-lg shadow-slate-100 flex items-center justify-center"
|
||||
{/* Bottom Section */}
|
||||
<div className="pt-4 sm:pt-6 flex flex-col items-center gap-5">
|
||||
<div className="h-px w-16 bg-gradient-to-r from-transparent via-black/10 to-transparent" />
|
||||
<a
|
||||
href="https://mintel.me"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="opacity-30 transition-opacity hover:opacity-60"
|
||||
>
|
||||
Unlock Access
|
||||
<ArrowRight className="ml-3 w-3 h-3 group-hover:translate-x-1 transition-transform" />
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Bottom Section - Full Branding Parity */}
|
||||
<div className="pt-12 sm:pt-20 flex flex-col items-center gap-6 sm:gap-8">
|
||||
<div className="h-px w-8 bg-slate-100" />
|
||||
<div className="opacity-80 transition-opacity hover:opacity-100">
|
||||
<Image
|
||||
src="/gatekeeper/logo-black.svg"
|
||||
<img
|
||||
src="/gatekeeper/logo-white.svg"
|
||||
alt={projectName}
|
||||
width={140}
|
||||
height={40}
|
||||
className="h-7 sm:h-auto grayscale contrast-125 w-auto"
|
||||
unoptimized
|
||||
width={120}
|
||||
height={36}
|
||||
className="h-5 sm:h-6 w-auto"
|
||||
style={{ filter: "invert(1)" }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[8px] font-sans font-bold text-slate-300 uppercase tracking-[0.4em] sm:tracking-[0.5em] text-center">
|
||||
© 2026 MINTEL
|
||||
</a>
|
||||
<p className="text-[7px] font-sans font-semibold text-black/25 uppercase tracking-[0.5em] text-center">
|
||||
© {new Date().getFullYear()} MINTEL
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
126
packages/gatekeeper/src/app/opengraph-image.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { ImageResponse } from "next/og";
|
||||
|
||||
export const runtime = "edge";
|
||||
|
||||
// Image metadata
|
||||
export const alt = "Gatekeeper Infrastructure Protection";
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export const contentType = "image/png";
|
||||
|
||||
export default async function Image() {
|
||||
const projectName = process.env.PROJECT_NAME || "MINTEL";
|
||||
|
||||
return new ImageResponse(
|
||||
<div
|
||||
style={{
|
||||
background: "linear-gradient(to bottom, #020617, #0f172a)",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
position: "relative",
|
||||
fontFamily: "Inter, sans-serif",
|
||||
}}
|
||||
>
|
||||
{/* Subtle Background Pattern matching the industrial look */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundImage:
|
||||
"radial-gradient(circle at 50% 50%, #334155 1px, transparent 1px)",
|
||||
backgroundSize: "40px 40px",
|
||||
opacity: 0.1,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Central Card Element */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "rgba(15, 23, 42, 0.6)",
|
||||
border: "1px solid rgba(51, 65, 85, 0.4)",
|
||||
borderRadius: "32px",
|
||||
padding: "80px",
|
||||
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
|
||||
}}
|
||||
>
|
||||
{/* Top Icon Box */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100px",
|
||||
height: "100px",
|
||||
background: "#000",
|
||||
borderRadius: "24px",
|
||||
border: "2px solid #334155",
|
||||
boxShadow: "0 20px 40px rgba(0,0,0,0.8)",
|
||||
marginBottom: "40px",
|
||||
transform: "rotate(2deg)",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="white"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Project Name & Typography */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
fontSize: "64px",
|
||||
fontWeight: 900,
|
||||
letterSpacing: "0.2em",
|
||||
color: "white",
|
||||
textTransform: "uppercase",
|
||||
marginBottom: "16px",
|
||||
}}
|
||||
>
|
||||
{projectName}{" "}
|
||||
<span style={{ color: "#64748b", marginLeft: "10px" }}>
|
||||
GATEKEEPER
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
fontSize: "24px",
|
||||
fontWeight: 600,
|
||||
letterSpacing: "0.4em",
|
||||
color: "#94a3b8",
|
||||
textTransform: "uppercase",
|
||||
}}
|
||||
>
|
||||
Infrastructure Protection
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
{
|
||||
...size,
|
||||
},
|
||||
);
|
||||
}
|
||||
283
packages/gatekeeper/src/components/animated-login-form.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { ArrowRight, Lock, Shield, Fingerprint } from "lucide-react";
|
||||
|
||||
interface AnimatedLoginFormProps {
|
||||
redirectUrl: string;
|
||||
loginAction: (formData: FormData) => Promise<void>;
|
||||
projectName: string;
|
||||
}
|
||||
|
||||
export function AnimatedLoginForm({
|
||||
redirectUrl,
|
||||
loginAction,
|
||||
}: AnimatedLoginFormProps) {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const beamRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Mouse tracking refs (no re-render)
|
||||
const mouse = useRef({ x: 0, y: 0 });
|
||||
const angle = useRef(0);
|
||||
const tilt = useRef({ x: 0, y: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
// Single rAF loop: iridescent border + perspective tilt
|
||||
useEffect(() => {
|
||||
let animId: number;
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
mouse.current = { x: e.clientX, y: e.clientY };
|
||||
};
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
|
||||
const animate = () => {
|
||||
if (!wrapperRef.current || !beamRef.current) {
|
||||
animId = requestAnimationFrame(animate);
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = wrapperRef.current.getBoundingClientRect();
|
||||
const cx = rect.left + rect.width / 2;
|
||||
const cy = rect.top + rect.height / 2;
|
||||
const dx = mouse.current.x - cx;
|
||||
const dy = mouse.current.y - cy;
|
||||
|
||||
// Angle from form center to mouse → positions the bright highlight
|
||||
const targetAngle = (Math.atan2(dy, dx) * 180) / Math.PI;
|
||||
|
||||
// Lerp angle smoothly (shortest path)
|
||||
let diff = targetAngle - angle.current;
|
||||
while (diff > 180) diff -= 360;
|
||||
while (diff < -180) diff += 360;
|
||||
angle.current += diff * 0.06;
|
||||
|
||||
// Intensity: slightly stronger on focus
|
||||
const intensity = isFocused ? 1 : 0.7;
|
||||
|
||||
// Mouse-aligned iridescent conic gradient
|
||||
// The "hotspot" (brightest white) faces the mouse
|
||||
beamRef.current.style.background = `conic-gradient(from ${angle.current}deg at 50% 50%,
|
||||
rgba(255,255,255,${1.0 * intensity}) 0deg,
|
||||
rgba(200,210,255,${0.8 * intensity}) 20deg,
|
||||
rgba(255,200,230,${0.7 * intensity}) 45deg,
|
||||
rgba(150,160,180,${0.6 * intensity}) 80deg,
|
||||
rgba(40,40,50,${0.5 * intensity}) 160deg,
|
||||
rgba(20,20,30,${0.4 * intensity}) 200deg,
|
||||
rgba(140,150,170,${0.5 * intensity}) 280deg,
|
||||
rgba(210,225,255,${0.7 * intensity}) 320deg,
|
||||
rgba(255,255,255,${1.0 * intensity}) 360deg)`;
|
||||
|
||||
// Subtle perspective tilt — max ±4deg
|
||||
const maxTilt = 4;
|
||||
const normX = dx / (rect.width * 2);
|
||||
const normY = dy / (rect.height * 2);
|
||||
const targetTiltY = normX * maxTilt;
|
||||
const targetTiltX = -normY * maxTilt;
|
||||
tilt.current.x += (targetTiltX - tilt.current.x) * 0.08;
|
||||
tilt.current.y += (targetTiltY - tilt.current.y) * 0.08;
|
||||
|
||||
wrapperRef.current.style.transform = `perspective(800px) rotateX(${tilt.current.x}deg) rotateY(${tilt.current.y}deg)`;
|
||||
|
||||
animId = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animId = requestAnimationFrame(animate);
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
cancelAnimationFrame(animId);
|
||||
};
|
||||
}, [isFocused]);
|
||||
|
||||
const handleSubmit = async (formData: FormData) => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await loginAction(formData);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30, scale: 0.95 }}
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
transition={{ duration: 0.8, ease: [0.23, 1, 0.32, 1], delay: 0.3 }}
|
||||
className="relative"
|
||||
style={{ willChange: "transform" }}
|
||||
>
|
||||
{/* Outer wrapper for tilt (refs need non-motion div) */}
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
style={{ willChange: "transform", transformStyle: "preserve-3d" }}
|
||||
className="relative"
|
||||
>
|
||||
{/* ── Always-on iridescent beam border ── */}
|
||||
<div className="absolute -inset-[1.5px] rounded-[28px] z-0 overflow-hidden">
|
||||
{/* Sharp edge layer */}
|
||||
<div
|
||||
ref={beamRef}
|
||||
className="absolute inset-0 rounded-[28px]"
|
||||
style={{ filter: "blur(0.4px)" }}
|
||||
/>
|
||||
{/* Soft glow bloom */}
|
||||
<div
|
||||
className="absolute inset-0 rounded-[28px]"
|
||||
style={{
|
||||
background: "inherit",
|
||||
filter: "blur(15px)",
|
||||
opacity: 0.5,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── Glassmorphism card — high-fidelity glossy light ── */}
|
||||
<div
|
||||
className="relative z-10 bg-white/70 backdrop-blur-3xl rounded-3xl p-7 sm:p-9"
|
||||
style={{
|
||||
boxShadow:
|
||||
/* Razor-sharp inner border highlight */ "inset 0 0 0 1px rgba(255,255,255,0.7), " +
|
||||
/* Top gloss edge */ "inset 0 1.5px 0.5px rgba(255,255,255,1), " +
|
||||
/* Secondary soft top gloss */ "inset 0 4px 10px rgba(255,255,255,0.4), " +
|
||||
/* Bottom inner shadow */ "inset 0 -1px 1px rgba(0,0,0,0.05), " +
|
||||
/* Outer drop shadows for depth */ "0 25px 50px -12px rgba(0,0,0,0.08), 0 4px 8px rgba(0,0,0,0.02)",
|
||||
}}
|
||||
>
|
||||
{/* Subtle surface "sheen" gradient */}
|
||||
<div className="absolute inset-0 rounded-3xl bg-gradient-to-br from-white/40 via-transparent to-black/[0.02] pointer-events-none" />
|
||||
|
||||
{/* Shield icon header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.5, duration: 0.6 }}
|
||||
className="flex justify-center mb-6"
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-black/[0.04] to-black/[0.08] border border-black/[0.06] flex items-center justify-center backdrop-blur-sm">
|
||||
<Shield className="w-5 h-5 text-black/40" />
|
||||
</div>
|
||||
{mounted && (
|
||||
<div className="absolute -inset-2 rounded-2xl border border-black/[0.04] animate-ping opacity-30" />
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<form
|
||||
action={handleSubmit}
|
||||
className="space-y-5 relative z-10"
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={(e) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
|
||||
setIsFocused(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="redirect" value={redirectUrl} />
|
||||
|
||||
<div className="space-y-3">
|
||||
{/* Email Input */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.6, duration: 0.5 }}
|
||||
className="relative group"
|
||||
>
|
||||
<Fingerprint className="absolute left-4 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-black/20 group-focus-within:text-black/50 transition-colors duration-300 pointer-events-none" />
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Identity (optional)"
|
||||
onFocus={() => setIsFocused(true)}
|
||||
className="w-full bg-black/[0.02] border border-black/[0.06] rounded-2xl pl-11 pr-4 py-3.5 focus:outline-none focus:border-black/15 focus:bg-white/80 transition-all duration-300 text-[11px] font-sans font-medium tracking-[0.08em] placeholder:text-black/25 placeholder:normal-case placeholder:tracking-normal text-black/70"
|
||||
/>
|
||||
<div className="absolute bottom-0 left-4 right-4 h-px bg-gradient-to-r from-transparent via-black/0 to-transparent group-focus-within:via-black/10 transition-all duration-500" />
|
||||
</motion.div>
|
||||
|
||||
{/* Password Input */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 0.7, duration: 0.5 }}
|
||||
className="relative group"
|
||||
>
|
||||
<Lock className="absolute left-4 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-black/20 group-focus-within:text-black/50 transition-colors duration-300 pointer-events-none" />
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
required
|
||||
autoFocus
|
||||
autoComplete="current-password"
|
||||
placeholder="Access code"
|
||||
onFocus={() => setIsFocused(true)}
|
||||
className="w-full bg-black/[0.02] border border-black/[0.06] rounded-2xl pl-11 pr-4 py-3.5 focus:outline-none focus:border-black/15 focus:bg-white/80 transition-all duration-300 text-[13px] font-sans font-medium tracking-[0.15em] placeholder:text-black/25 placeholder:tracking-normal placeholder:text-[11px] placeholder:font-normal text-black/80"
|
||||
/>
|
||||
<div className="absolute bottom-0 left-4 right-4 h-px bg-gradient-to-r from-transparent via-black/0 to-transparent group-focus-within:via-black/10 transition-all duration-500" />
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.8, duration: 0.5 }}
|
||||
>
|
||||
<motion.button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
whileHover={{ scale: 1.01 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
className={`relative w-full py-4 rounded-2xl text-[10px] font-bold tracking-[0.3em] uppercase flex items-center justify-center overflow-hidden transition-all duration-300 ${
|
||||
isSubmitting
|
||||
? "bg-black/5 text-black/25 cursor-not-allowed"
|
||||
: "bg-black text-white hover:bg-black/85 shadow-lg shadow-black/10"
|
||||
}`}
|
||||
>
|
||||
{!isSubmitting && (
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent -translate-x-full hover:translate-x-full transition-transform duration-700" />
|
||||
)}
|
||||
{isSubmitting ? (
|
||||
<span className="flex items-center gap-3 relative z-10">
|
||||
<motion.div
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{
|
||||
repeat: Infinity,
|
||||
duration: 1,
|
||||
ease: "linear",
|
||||
}}
|
||||
className="w-4 h-4 border-2 border-white/20 border-t-white/70 rounded-full"
|
||||
/>
|
||||
Authenticating...
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-3 relative z-10">
|
||||
Unlock Access
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</span>
|
||||
)}
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
</form>
|
||||
|
||||
{/* Security badge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.0, duration: 0.5 }}
|
||||
className="mt-5 flex items-center justify-center gap-2 text-[8px] font-sans font-semibold text-black/25 uppercase tracking-[0.3em]"
|
||||
>
|
||||
<div className="w-1 h-1 rounded-full bg-black/20 animate-pulse" />
|
||||
Encrypted Connection
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
127
packages/gatekeeper/src/components/gate-scene.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export function GateScene() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const mouse = useRef({ x: -1000, y: -1000 });
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const FONT_SIZE = 13;
|
||||
const COL_GAP = 18;
|
||||
const ROW_GAP = 20;
|
||||
|
||||
interface Cell {
|
||||
char: string;
|
||||
alpha: number;
|
||||
targetAlpha: number;
|
||||
speed: number;
|
||||
nextChange: number;
|
||||
}
|
||||
|
||||
let cells: Cell[][] = [];
|
||||
let cols = 0;
|
||||
let rows = 0;
|
||||
let animId: number;
|
||||
|
||||
const init = () => {
|
||||
cols = Math.ceil(canvas.width / COL_GAP);
|
||||
rows = Math.ceil(canvas.height / ROW_GAP);
|
||||
cells = Array.from({ length: cols }, () =>
|
||||
Array.from({ length: rows }, () => ({
|
||||
char: Math.random() > 0.5 ? "1" : "0",
|
||||
alpha: Math.random() * 0.08,
|
||||
targetAlpha: Math.random() * 0.15,
|
||||
speed: 0.008 + Math.random() * 0.02,
|
||||
nextChange: Math.floor(Math.random() * 80),
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
mouse.current = { x: e.clientX, y: e.clientY };
|
||||
};
|
||||
|
||||
const resize = () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
init();
|
||||
};
|
||||
|
||||
let frame = 0;
|
||||
|
||||
const draw = () => {
|
||||
frame++;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.font = `${FONT_SIZE}px 'Courier New', monospace`;
|
||||
|
||||
for (let c = 0; c < cols; c++) {
|
||||
for (let r = 0; r < rows; r++) {
|
||||
const cell = cells[c][r];
|
||||
const x = c * COL_GAP;
|
||||
const y = r * ROW_GAP + FONT_SIZE;
|
||||
|
||||
// Mouse proximity influence
|
||||
const dx = mouse.current.x - x;
|
||||
const dy = mouse.current.y - y;
|
||||
const distSq = dx * dx + dy * dy;
|
||||
const proximity = Math.max(0, 1 - Math.sqrt(distSq) / 250);
|
||||
|
||||
// Nudge alpha toward target
|
||||
cell.alpha += (cell.targetAlpha - cell.alpha) * cell.speed;
|
||||
|
||||
// More aggressive random behavior
|
||||
if (frame >= cell.nextChange) {
|
||||
cell.targetAlpha = Math.random() * 0.25; // Higher max alpha
|
||||
cell.speed = 0.01 + Math.random() * 0.03; // Faster transitions
|
||||
cell.nextChange = frame + 20 + Math.floor(Math.random() * 80); // More frequent changes
|
||||
|
||||
// Higher flip probability near mouse
|
||||
const flipProb = 0.3 + proximity * 0.5;
|
||||
if (Math.random() < flipProb) {
|
||||
cell.char = cell.char === "0" ? "1" : "0";
|
||||
}
|
||||
}
|
||||
|
||||
const a = Math.min(0.4, cell.alpha + proximity * 0.35);
|
||||
if (a < 0.01) continue;
|
||||
|
||||
// Dark chars on light background
|
||||
ctx.fillStyle = `rgba(0, 0, 0, ${a})`;
|
||||
ctx.fillText(cell.char, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
animId = requestAnimationFrame(draw);
|
||||
};
|
||||
|
||||
resize();
|
||||
window.addEventListener("resize", resize);
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
animId = requestAnimationFrame(draw);
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animId);
|
||||
window.removeEventListener("resize", resize);
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 pointer-events-none z-0 bg-[#f5f5f7]">
|
||||
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
|
||||
<div
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(ellipse 55% 65% at 50% 50%, rgba(245,245,247,0.7) 0%, transparent 100%)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
19
packages/gitea-mcp/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
RUN npm install -g pnpm@10.18.3 && pnpm install
|
||||
|
||||
COPY tsconfig.json ./
|
||||
COPY src ./src
|
||||
RUN pnpm build
|
||||
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/dist ./dist
|
||||
|
||||
# Use node to run the compiled start.js
|
||||
ENTRYPOINT ["node", "dist/start.js"]
|
||||
22
packages/gitea-mcp/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@mintel/gitea-mcp",
|
||||
"version": "1.9.17",
|
||||
"description": "Native Gitea MCP server for 100% Antigravity compatibility",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/start.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.5.0",
|
||||
"axios": "^1.7.2",
|
||||
"dotenv": "^17.3.1",
|
||||
"express": "^4.19.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.14.10",
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
}
|
||||
1347
packages/gitea-mcp/src/index.ts
Normal file
16
packages/gitea-mcp/src/start.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { config } from 'dotenv';
|
||||
import { resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
// Try to load .env.local first (contains credentials usually)
|
||||
config({ quiet: true, path: resolve(__dirname, '../../../.env.local') });
|
||||
// Fallback to .env (contains defaults)
|
||||
config({ quiet: true, path: resolve(__dirname, '../../../.env') });
|
||||
|
||||
// Now boot the compiled MCP index
|
||||
import('./index.js').catch(err => {
|
||||
console.error('Failed to start MCP Server:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
16
packages/gitea-mcp/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
15
packages/glitchtip-mcp/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:20-bookworm-slim AS builder
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
RUN npm install -g pnpm@10.18.3 && pnpm install --ignore-workspace
|
||||
COPY tsconfig.json ./
|
||||
COPY src ./src
|
||||
RUN pnpm build
|
||||
|
||||
FROM node:20-bookworm-slim
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/dist ./dist
|
||||
|
||||
ENTRYPOINT ["node", "dist/start.js"]
|
||||
24
packages/glitchtip-mcp/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@mintel/glitchtip-mcp",
|
||||
"version": "1.9.17",
|
||||
"description": "GlitchTip Error Tracking MCP server for Mintel infrastructure",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/start.js",
|
||||
"dev": "tsx watch src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.5.0",
|
||||
"axios": "^1.7.2",
|
||||
"dotenv": "^17.3.1",
|
||||
"express": "^4.19.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.14.10",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.5.3"
|
||||
}
|
||||
}
|
||||
219
packages/glitchtip-mcp/src/index.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import express, { Request, Response } from 'express';
|
||||
import crypto from 'crypto';
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
Tool,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import axios from "axios";
|
||||
import https from "https";
|
||||
|
||||
const GLITCHTIP_BASE_URL = process.env.GLITCHTIP_BASE_URL || "https://glitchtip.infra.mintel.me";
|
||||
const GLITCHTIP_API_KEY = process.env.GLITCHTIP_API_KEY;
|
||||
|
||||
if (!GLITCHTIP_API_KEY) {
|
||||
console.error("Warning: GLITCHTIP_API_KEY is not set. API calls will fail.");
|
||||
}
|
||||
|
||||
const httpsAgent = new https.Agent({
|
||||
rejectUnauthorized: false, // For internal infra
|
||||
});
|
||||
|
||||
const glitchtipClient = axios.create({
|
||||
baseURL: `${GLITCHTIP_BASE_URL}/api/0`,
|
||||
headers: { Authorization: `Bearer ${GLITCHTIP_API_KEY}` },
|
||||
httpsAgent,
|
||||
timeout: 10000 // Add timeout to prevent hangs
|
||||
});
|
||||
|
||||
// Logging middleware for GlitchTip client
|
||||
glitchtipClient.interceptors.request.use((config) => {
|
||||
console.error(`GlitchTip Request: ${config.method?.toUpperCase()} ${config.url}`);
|
||||
return config;
|
||||
}, (error) => {
|
||||
console.error(`GlitchTip Request Error: ${error.message}`);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
glitchtipClient.interceptors.response.use((response) => {
|
||||
return response;
|
||||
}, (error) => {
|
||||
const msg = error.response?.data?.detail || error.response?.data?.message || error.message;
|
||||
console.error(`GlitchTip API Error (${error.config?.url}): ${msg}`);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
const LIST_PROJECTS_TOOL: Tool = {
|
||||
name: "glitchtip_list_projects",
|
||||
description: "List all projects and organizations in GlitchTip",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
};
|
||||
|
||||
const LIST_ISSUES_TOOL: Tool = {
|
||||
name: "glitchtip_list_issues",
|
||||
description: "List issues (errors) for a specific project",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
organization_slug: { type: "string", description: "The organization slug" },
|
||||
project_slug: { type: "string", description: "The project slug" },
|
||||
query: { type: "string", description: "Optional query filter (e.g., 'is:unresolved')" },
|
||||
limit: { type: "number", description: "Maximum number of issues to return (default: 20)" },
|
||||
},
|
||||
required: ["organization_slug", "project_slug"],
|
||||
},
|
||||
};
|
||||
|
||||
const GET_ISSUE_DETAILS_TOOL: Tool = {
|
||||
name: "glitchtip_get_issue_details",
|
||||
description: "Get detailed information about a specific issue, including stack trace",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
issue_id: { type: "string", description: "The ID of the issue" },
|
||||
},
|
||||
required: ["issue_id"],
|
||||
},
|
||||
};
|
||||
|
||||
const UPDATE_ISSUE_TOOL: Tool = {
|
||||
name: "glitchtip_update_issue",
|
||||
description: "Update the status of an issue (e.g., resolve it)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
issue_id: { type: "string", description: "The ID of the issue" },
|
||||
status: { type: "string", enum: ["resolved", "unresolved", "ignored"], description: "The new status" },
|
||||
},
|
||||
required: ["issue_id", "status"],
|
||||
},
|
||||
};
|
||||
|
||||
const server = new Server(
|
||||
{ name: "glitchtip-mcp", version: "1.0.0" },
|
||||
{ capabilities: { tools: {} } }
|
||||
);
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: [
|
||||
LIST_PROJECTS_TOOL,
|
||||
LIST_ISSUES_TOOL,
|
||||
GET_ISSUE_DETAILS_TOOL,
|
||||
UPDATE_ISSUE_TOOL,
|
||||
],
|
||||
}));
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
try {
|
||||
if (request.params.name === "glitchtip_list_projects") {
|
||||
const res = await glitchtipClient.get('/projects/');
|
||||
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
||||
}
|
||||
|
||||
if (request.params.name === "glitchtip_list_issues") {
|
||||
const { organization_slug, project_slug, query, limit = 20 } = request.params.arguments as any;
|
||||
const res = await glitchtipClient.get(`/projects/${organization_slug}/${project_slug}/issues/`, {
|
||||
params: { query, limit }
|
||||
});
|
||||
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
||||
}
|
||||
|
||||
if (request.params.name === "glitchtip_get_issue_details") {
|
||||
const { issue_id } = request.params.arguments as any;
|
||||
const res = await glitchtipClient.get(`/issues/${issue_id}/`);
|
||||
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
||||
}
|
||||
|
||||
if (request.params.name === "glitchtip_update_issue") {
|
||||
const { issue_id, status } = request.params.arguments as any;
|
||||
const res = await glitchtipClient.put(`/issues/${issue_id}/`, { status });
|
||||
return { content: [{ type: "text", text: `Issue ${issue_id} status updated to ${status}.` }] };
|
||||
}
|
||||
|
||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||
} catch (e: any) {
|
||||
const msg = e.response?.data?.detail || e.response?.data?.message || e.message;
|
||||
return { isError: true, content: [{ type: "text", text: `GlitchTip Service Error: ${msg}` }] };
|
||||
}
|
||||
});
|
||||
|
||||
async function run() {
|
||||
const isStdio = process.argv.includes('--stdio');
|
||||
|
||||
if (isStdio) {
|
||||
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error('GlitchTip MCP server is running on stdio');
|
||||
} else {
|
||||
const app = express();
|
||||
const transports = new Map<string, SSEServerTransport>();
|
||||
|
||||
// Manual CORS middleware
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-mcp-protocol-version');
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(204).end();
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use((req, _res, next) => {
|
||||
console.error(`${req.method} ${req.url}`);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/sse', async (req, res) => {
|
||||
const transport = new SSEServerTransport('/message', res as any);
|
||||
const sessionId = transport.sessionId;
|
||||
console.error(`New SSE connection: ${sessionId}`);
|
||||
transports.set(sessionId, transport);
|
||||
|
||||
const heartbeatInterval = setInterval(() => {
|
||||
res.write(": heartbeat\n\n");
|
||||
}, 15000);
|
||||
|
||||
req.on('close', () => {
|
||||
console.error(`SSE connection closed: ${sessionId}`);
|
||||
clearInterval(heartbeatInterval);
|
||||
transports.delete(sessionId);
|
||||
});
|
||||
|
||||
await server.connect(transport);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
req.on('close', resolve);
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/message', express.json(), async (req, res) => {
|
||||
const sessionId = req.query.sessionId as string;
|
||||
const transport = transports.get(sessionId);
|
||||
|
||||
console.error(`POST /message/${sessionId} - Active transports: ${transports.size}`);
|
||||
|
||||
if (!transport) {
|
||||
console.error(`No transport found for session: ${sessionId}`);
|
||||
res.status(400).send('No active SSE connection for this session');
|
||||
return;
|
||||
}
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
|
||||
const PORT = Number(process.env.GLITCHTIP_MCP_PORT) || 3005;
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.error(`GlitchTip MCP server running on http://0.0.0.0:${PORT}/sse`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
run().catch((err) => {
|
||||
console.error("Fatal error:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
13
packages/glitchtip-mcp/src/start.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { config } from 'dotenv';
|
||||
import { resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
config({ quiet: true, path: resolve(__dirname, '../../../.env.local') });
|
||||
config({ quiet: true, path: resolve(__dirname, '../../../.env') });
|
||||
|
||||
import('./index.js').catch(err => {
|
||||
console.error('Failed to start GlitchTip MCP Server:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
16
packages/glitchtip-mcp/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/husky-config",
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.17",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://git.infra.mintel.me/api/packages/mmintel/npm"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Start from the pre-built Nextjs Base image
|
||||
FROM registry.infra.mintel.me/mintel/nextjs:latest AS builder
|
||||
FROM git.infra.mintel.me/mmintel/nextjs:latest AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -20,7 +20,7 @@ ENV DIRECTUS_URL=$DIRECTUS_URL
|
||||
RUN pnpm --filter ${APP_NAME:-app} build
|
||||
|
||||
# Production runner image
|
||||
FROM registry.infra.mintel.me/mintel/runtime:latest AS runner
|
||||
FROM git.infra.mintel.me/mmintel/runtime:latest AS runner
|
||||
WORKDIR /app
|
||||
|
||||
# Copy standalone output and static files
|
||||
|
||||
@@ -13,7 +13,7 @@ COPY packages/eslint-config/package.json ./packages/eslint-config/package.json
|
||||
COPY packages/next-config/package.json ./packages/next-config/package.json
|
||||
COPY packages/tsconfig/package.json ./packages/tsconfig/package.json
|
||||
COPY packages/infra/package.json ./packages/infra/package.json
|
||||
COPY packages/cms-infra/package.json ./packages/cms-infra/package.json
|
||||
|
||||
COPY packages/mail/package.json ./packages/mail/package.json
|
||||
COPY packages/cli/package.json ./packages/cli/package.json
|
||||
COPY packages/observability/package.json ./packages/observability/package.json
|
||||
|
||||
@@ -38,7 +38,7 @@ services:
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-app}-auth.forwardauth.authResponseHeaders=X-Auth-User"
|
||||
|
||||
gatekeeper:
|
||||
image: registry.infra.mintel.me/mintel/gatekeeper:${IMAGE_TAG:-latest}
|
||||
image: git.infra.mintel.me/mmintel/gatekeeper:${IMAGE_TAG:-latest}
|
||||
restart: always
|
||||
networks:
|
||||
- infra
|
||||
@@ -53,7 +53,7 @@ services:
|
||||
- "traefik.http.services.${PROJECT_NAME}-gatekeeper.loadbalancer.server.port=3000"
|
||||
|
||||
directus:
|
||||
image: registry.infra.mintel.me/mintel/directus:${IMAGE_TAG:-latest}
|
||||
image: git.infra.mintel.me/mmintel/directus:${IMAGE_TAG:-latest}
|
||||
restart: always
|
||||
networks:
|
||||
- infra
|
||||
|
||||
@@ -177,12 +177,31 @@ jobs:
|
||||
- name: 🐳 Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 🔐 Registry Login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: registry.infra.mintel.me
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASS }}
|
||||
- name: 🔐 Discover Valid Registry Token
|
||||
id: discover_token
|
||||
run: |
|
||||
echo "Testing available secrets against git.infra.mintel.me Docker registry..."
|
||||
TOKENS="${{ secrets.GITEA_PAT }} ${{ secrets.MINTEL_PRIVATE_TOKEN }} ${{ secrets.NPM_TOKEN }}"
|
||||
USERS="${{ github.repository_owner }} ${{ github.actor }} marcmintel mintel mmintel"
|
||||
|
||||
for TOKEN in $TOKENS; do
|
||||
if [ -n "$TOKEN" ]; then
|
||||
for U in $USERS; do
|
||||
if [ -n "$U" ]; then
|
||||
echo "Attempting docker login for a token with user $U..."
|
||||
if echo "$TOKEN" | docker login git.infra.mintel.me -u "$U" --password-stdin > /dev/null 2>&1; then
|
||||
echo "✅ Successfully authenticated with a token."
|
||||
echo "::add-mask::$TOKEN"
|
||||
echo "token=$TOKEN" >> $GITHUB_OUTPUT
|
||||
echo "user=$U" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
echo "❌ All available tokens failed to authenticate!"
|
||||
exit 1
|
||||
|
||||
- name: 🏗️ Docker Build & Push
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -197,8 +216,8 @@ jobs:
|
||||
NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }}
|
||||
push: true
|
||||
secrets: |
|
||||
NPM_TOKEN=${{ secrets.NPM_TOKEN }}
|
||||
tags: registry.infra.mintel.me/mintel/${{ github.event.repository.name }}:${{ needs.prepare.outputs.image_tag }}
|
||||
NPM_TOKEN=${{ steps.discover_token.outputs.token }}
|
||||
tags: git.infra.mintel.me/mmintel/${{ github.event.repository.name }}:${{ needs.prepare.outputs.image_tag }}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# JOB 4: Deploy
|
||||
@@ -262,7 +281,7 @@ jobs:
|
||||
set -e
|
||||
cd "/home/deploy/sites/${{ github.event.repository.name }}"
|
||||
chmod 600 "$ENV_FILE"
|
||||
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
echo "${{ steps.discover_token.outputs.token }}" | docker login git.infra.mintel.me -u "${{ steps.discover_token.outputs.user }}" --password-stdin
|
||||
docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" pull
|
||||
docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" up -d --remove-orphans
|
||||
docker system prune -f --filter "until=24h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/infra",
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.17",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://git.infra.mintel.me/api/packages/mmintel/npm"
|
||||
|
||||
@@ -2,12 +2,24 @@
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
REGISTRY_DATA="/mnt/HC_Volume_104575103/registry-data/docker/registry/v2"
|
||||
REGISTRY_DATA="/mnt/HC_Volume_104796416/registry-data/docker/registry/v2"
|
||||
KEEP_TAGS=3
|
||||
|
||||
echo "🏥 Starting Aggressive Mintel Infrastructure Optimization..."
|
||||
|
||||
# 1. Prune Registry Tags (Filesystem level)
|
||||
# 1. Gitea Maintenance
|
||||
echo "🍵 Running Gitea Maintenance..."
|
||||
GITEA_CONTAINER=$(docker ps --format "{{.Names}}" | grep gitea | head -1 || true)
|
||||
if [ -n "$GITEA_CONTAINER" ]; then
|
||||
# Run common Gitea cleanup tasks
|
||||
docker exec -u git "$GITEA_CONTAINER" gitea admin cron run cleanup_old_repository_archives || true
|
||||
docker exec -u git "$GITEA_CONTAINER" gitea admin cron run cleanup_upload_directory || true
|
||||
docker exec -u git "$GITEA_CONTAINER" gitea admin cron run cleanup_packages || true
|
||||
docker exec -u git "$GITEA_CONTAINER" gitea admin cron run garbage_collect_attachment || true
|
||||
docker exec -u git "$GITEA_CONTAINER" gitea admin cron run garbage_collect_lfs || true
|
||||
fi
|
||||
|
||||
# 2. Prune Registry Tags (Filesystem level)
|
||||
if [ -d "$REGISTRY_DATA" ]; then
|
||||
echo "🔍 Processing Registry tags..."
|
||||
for repo_dir in "$REGISTRY_DATA/repositories/mintel/"*; do
|
||||
@@ -47,4 +59,4 @@ docker system prune -af --filter "until=24h"
|
||||
docker volume prune -f
|
||||
|
||||
echo "✅ Optimization complete!"
|
||||
df -h /mnt/HC_Volume_104575103
|
||||
df -h /mnt/HC_Volume_104796416
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
REGISTRY_DATA="/mnt/HC_Volume_104575103/registry-data/docker/registry/v2"
|
||||
# Find registry data dynamically (volume ID might change)
|
||||
REGISTRY_DATA=$(find /mnt -maxdepth 5 -type d -path '*/registry-data/docker/registry/v2' | head -n 1)
|
||||
|
||||
if [ -z "$REGISTRY_DATA" ]; then
|
||||
echo "❌ Registry data directory not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KEEP_TAGS=3
|
||||
|
||||
echo "🏥 Starting Aggressive Registry & Docker Maintenance..."
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "@mintel/journaling",
|
||||
"version": "1.9.2",
|
||||
"private": true,
|
||||
"version": "1.9.17",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
|
||||
15
packages/klz-payload-mcp/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:20-bookworm-slim AS builder
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
RUN npm install -g pnpm@10.18.3 && pnpm install --ignore-workspace
|
||||
COPY tsconfig.json ./
|
||||
COPY src ./src
|
||||
RUN pnpm build
|
||||
|
||||
FROM node:20-bookworm-slim
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/dist ./dist
|
||||
|
||||
ENTRYPOINT ["node", "dist/start.js"]
|
||||