fix: replace invalid useRouter import from @directus/extensions-sdk with vue-router
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 26s
Monorepo Pipeline / 🚀 Release (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been cancelled
Monorepo Pipeline / 🧪 Test (push) Has been cancelled
Monorepo Pipeline / 🧹 Lint (push) Has been cancelled
Monorepo Pipeline / 🏗️ Build (push) Has been cancelled
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 26s
Monorepo Pipeline / 🚀 Release (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been cancelled
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been cancelled
Monorepo Pipeline / 🧪 Test (push) Has been cancelled
Monorepo Pipeline / 🧹 Lint (push) Has been cancelled
Monorepo Pipeline / 🏗️ Build (push) Has been cancelled
The Directus 11.x SDK does not export useRouter. Importing it caused a SyntaxError that crashed the entire extensions bundle, preventing ALL modules from appearing in the Data Studio sidebar. Changes: - Replace useRouter import from @directus/extensions-sdk → vue-router - Add scripts/validate-sdk-imports.sh to catch invalid SDK imports - Integrate SDK import validation into pre-push hook - Add EXTENSIONS_AUTO_RELOAD to docker-compose.yml - Remove debug NODE_ENV=development
This commit is contained in:
@@ -1,3 +1,10 @@
|
||||
# Validate Directus SDK imports before push
|
||||
# This prevents runtime crashes caused by importing non-existent exports
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
infra-cms:
|
||||
image: directus/directus:11
|
||||
image: directus/directus:11.15.2
|
||||
ports:
|
||||
- "8059:8055"
|
||||
networks:
|
||||
@@ -14,6 +14,7 @@ services:
|
||||
DB_CLIENT: "sqlite3"
|
||||
DB_FILENAME: "/directus/database/data.db"
|
||||
WEBSOCKETS_ENABLED: "true"
|
||||
PUBLIC_URL: "http://cms.localhost"
|
||||
EMAIL_TRANSPORT: "smtp"
|
||||
EMAIL_SMTP_HOST: "smtp.eu.mailgun.org"
|
||||
EMAIL_SMTP_PORT: "587"
|
||||
@@ -21,19 +22,29 @@ services:
|
||||
EMAIL_SMTP_PASSWORD: "4592fcb94599ee1a45b4ac2386fd0a64-102c75d8-ca2870e6"
|
||||
EMAIL_SMTP_SECURE: "false"
|
||||
EMAIL_FROM: "postmaster@mg.mintel.me"
|
||||
LOG_LEVEL: "debug"
|
||||
SERVE_APP: "true"
|
||||
EXTENSIONS_AUTO_RELOAD: "true"
|
||||
volumes:
|
||||
- ./database:/directus/database
|
||||
- ./uploads:/directus/uploads
|
||||
- ./schema:/directus/schema
|
||||
- ./extensions:/directus/extensions
|
||||
healthcheck:
|
||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8055/server/health" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.infra-cms.rule=Host(`cms.localhost`)"
|
||||
- "traefik.http.services.infra-cms.loadbalancer.server.port=8055"
|
||||
- "traefik.http.routers.at-mintel-infra-cms.rule=Host(`cms.localhost`)"
|
||||
- "traefik.http.services.at-mintel-infra-cms.loadbalancer.server.port=8055"
|
||||
- "traefik.http.services.at-mintel-infra-cms.loadbalancer.healthcheck.path=/server/health"
|
||||
- "traefik.docker.network=infra"
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: mintel-infra-cms-internal
|
||||
name: at-mintel-cms-network
|
||||
infra:
|
||||
external: true
|
||||
|
||||
141
packages/cms-infra/extensions/unified-dashboard/src/module.vue
Normal file
141
packages/cms-infra/extensions/unified-dashboard/src/module.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<private-view title="Overview">
|
||||
<div class="dashboard">
|
||||
<header class="dashboard-header">
|
||||
<h1 class="title">Infrastructure Stack</h1>
|
||||
<p class="subtitle">Zentrale Schnittstelle für Firmen, Personen und Leads.</p>
|
||||
</header>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" @click="navigateTo('/company-manager')">
|
||||
<div class="stat-icon"><v-icon name="business" large /></div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">Firmen</span>
|
||||
<span class="stat-value">{{ stats.companies }}</span>
|
||||
</div>
|
||||
<v-icon name="chevron_right" class="arrow" />
|
||||
</div>
|
||||
|
||||
<div class="stat-card" @click="navigateTo('/people-manager')">
|
||||
<div class="stat-icon"><v-icon name="person" large /></div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">Personen</span>
|
||||
<span class="stat-value">{{ stats.people }}</span>
|
||||
</div>
|
||||
<v-icon name="chevron_right" class="arrow" />
|
||||
</div>
|
||||
|
||||
<div class="stat-card" @click="navigateTo('/acquisition-manager')">
|
||||
<div class="stat-icon"><v-icon name="auto_awesome" large /></div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">Leads</span>
|
||||
<span class="stat-value">{{ stats.leads }}</span>
|
||||
</div>
|
||||
<v-icon name="chevron_right" class="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recent-activity">
|
||||
<h2 class="section-title">Schnellzugriff</h2>
|
||||
<div class="action-grid">
|
||||
<v-button secondary block @click="navigateTo('/people-manager?create=true')">
|
||||
<v-icon name="person_add" left />
|
||||
Neue Person anlegen
|
||||
</v-button>
|
||||
<v-button secondary block @click="navigateTo('/acquisition-manager?create=true')">
|
||||
<v-icon name="add_link" left />
|
||||
Neuen Lead registrieren
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useApi } from '@directus/extensions-sdk';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const api = useApi();
|
||||
const router = useRouter();
|
||||
|
||||
const stats = ref({
|
||||
companies: 0,
|
||||
people: 0,
|
||||
leads: 0
|
||||
});
|
||||
|
||||
async function fetchStats() {
|
||||
try {
|
||||
const [comp, peop, lead] = await Promise.all([
|
||||
api.get('/items/companies?aggregate[count]=*'),
|
||||
api.get('/items/people?aggregate[count]=*'),
|
||||
api.get('/items/leads?aggregate[count]=*')
|
||||
]);
|
||||
stats.value = {
|
||||
companies: comp.data.data[0].count,
|
||||
people: peop.data.data[0].count,
|
||||
leads: lead.data.data[0].count
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
|
||||
onMounted(fetchStats);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard { padding: 40px; }
|
||||
.dashboard-header { margin-bottom: 48px; }
|
||||
.title { font-size: 32px; font-weight: 800; letter-spacing: -0.5px; margin-bottom: 8px; }
|
||||
.subtitle { color: var(--theme--foreground-subdued); font-size: 16px; }
|
||||
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-bottom: 48px; }
|
||||
|
||||
.stat-card {
|
||||
background: var(--theme--background-normal);
|
||||
border: 1px solid var(--theme--border);
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
border-color: var(--theme--primary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: var(--theme--background-subdued);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--theme--primary);
|
||||
}
|
||||
|
||||
.stat-content { display: flex; flex-direction: column; }
|
||||
.stat-label { font-size: 12px; font-weight: 700; text-transform: uppercase; color: var(--theme--foreground-subdued); letter-spacing: 0.5px; }
|
||||
.stat-value { font-size: 28px; font-weight: 800; color: var(--theme--foreground); }
|
||||
|
||||
.arrow { position: absolute; right: 24px; opacity: 0.2; }
|
||||
.stat-card:hover .arrow { opacity: 1; color: var(--theme--primary); }
|
||||
|
||||
.recent-activity { max-width: 600px; }
|
||||
.section-title { font-size: 18px; font-weight: 700; margin-bottom: 24px; }
|
||||
.action-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
</style>
|
||||
141
packages/unified-dashboard/src/module.vue
Normal file
141
packages/unified-dashboard/src/module.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<private-view title="Overview">
|
||||
<div class="dashboard">
|
||||
<header class="dashboard-header">
|
||||
<h1 class="title">Infrastructure Stack</h1>
|
||||
<p class="subtitle">Zentrale Schnittstelle für Firmen, Personen und Leads.</p>
|
||||
</header>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card" @click="navigateTo('/company-manager')">
|
||||
<div class="stat-icon"><v-icon name="business" large /></div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">Firmen</span>
|
||||
<span class="stat-value">{{ stats.companies }}</span>
|
||||
</div>
|
||||
<v-icon name="chevron_right" class="arrow" />
|
||||
</div>
|
||||
|
||||
<div class="stat-card" @click="navigateTo('/people-manager')">
|
||||
<div class="stat-icon"><v-icon name="person" large /></div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">Personen</span>
|
||||
<span class="stat-value">{{ stats.people }}</span>
|
||||
</div>
|
||||
<v-icon name="chevron_right" class="arrow" />
|
||||
</div>
|
||||
|
||||
<div class="stat-card" @click="navigateTo('/acquisition-manager')">
|
||||
<div class="stat-icon"><v-icon name="auto_awesome" large /></div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-label">Leads</span>
|
||||
<span class="stat-value">{{ stats.leads }}</span>
|
||||
</div>
|
||||
<v-icon name="chevron_right" class="arrow" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recent-activity">
|
||||
<h2 class="section-title">Schnellzugriff</h2>
|
||||
<div class="action-grid">
|
||||
<v-button secondary block @click="navigateTo('/people-manager?create=true')">
|
||||
<v-icon name="person_add" left />
|
||||
Neue Person anlegen
|
||||
</v-button>
|
||||
<v-button secondary block @click="navigateTo('/acquisition-manager?create=true')">
|
||||
<v-icon name="add_link" left />
|
||||
Neuen Lead registrieren
|
||||
</v-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useApi } from '@directus/extensions-sdk';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const api = useApi();
|
||||
const router = useRouter();
|
||||
|
||||
const stats = ref({
|
||||
companies: 0,
|
||||
people: 0,
|
||||
leads: 0
|
||||
});
|
||||
|
||||
async function fetchStats() {
|
||||
try {
|
||||
const [comp, peop, lead] = await Promise.all([
|
||||
api.get('/items/companies?aggregate[count]=*'),
|
||||
api.get('/items/people?aggregate[count]=*'),
|
||||
api.get('/items/leads?aggregate[count]=*')
|
||||
]);
|
||||
stats.value = {
|
||||
companies: comp.data.data[0].count,
|
||||
people: peop.data.data[0].count,
|
||||
leads: lead.data.data[0].count
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function navigateTo(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
|
||||
onMounted(fetchStats);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard { padding: 40px; }
|
||||
.dashboard-header { margin-bottom: 48px; }
|
||||
.title { font-size: 32px; font-weight: 800; letter-spacing: -0.5px; margin-bottom: 8px; }
|
||||
.subtitle { color: var(--theme--foreground-subdued); font-size: 16px; }
|
||||
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-bottom: 48px; }
|
||||
|
||||
.stat-card {
|
||||
background: var(--theme--background-normal);
|
||||
border: 1px solid var(--theme--border);
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
border-color: var(--theme--primary);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: var(--theme--background-subdued);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--theme--primary);
|
||||
}
|
||||
|
||||
.stat-content { display: flex; flex-direction: column; }
|
||||
.stat-label { font-size: 12px; font-weight: 700; text-transform: uppercase; color: var(--theme--foreground-subdued); letter-spacing: 0.5px; }
|
||||
.stat-value { font-size: 28px; font-weight: 800; color: var(--theme--foreground); }
|
||||
|
||||
.arrow { position: absolute; right: 24px; opacity: 0.2; }
|
||||
.stat-card:hover .arrow { opacity: 1; color: var(--theme--primary); }
|
||||
|
||||
.recent-activity { max-width: 600px; }
|
||||
.section-title { font-size: 18px; font-weight: 700; margin-bottom: 24px; }
|
||||
.action-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
</style>
|
||||
100
scripts/validate-sdk-imports.sh
Executable file
100
scripts/validate-sdk-imports.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# validate-sdk-imports.sh
|
||||
# Validates that Directus extensions only use exports that exist in @directus/extensions-sdk.
|
||||
# Prevents the "SyntaxError: doesn't provide an export named" runtime crash
|
||||
# that silently breaks ALL extensions in the browser.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Valid exports from @directus/extensions-sdk in Directus 11.x
|
||||
# If Directus is upgraded, update this list by running:
|
||||
# curl -s http://cms.localhost/admin/assets/@directus_extensions-sdk.*.entry.js | grep -oE 'export\{[^}]+\}'
|
||||
VALID_EXPORTS=(
|
||||
"defineDisplay"
|
||||
"defineEndpoint"
|
||||
"defineHook"
|
||||
"defineInterface"
|
||||
"defineLayout"
|
||||
"defineModule"
|
||||
"defineOperationApi"
|
||||
"defineOperationApp"
|
||||
"definePanel"
|
||||
"defineTheme"
|
||||
"getFieldsFromTemplate"
|
||||
"getRelationType"
|
||||
"useApi"
|
||||
"useCollection"
|
||||
"useExtensions"
|
||||
"useFilterFields"
|
||||
"useItems"
|
||||
"useLayout"
|
||||
"useSdk"
|
||||
"useStores"
|
||||
"useSync"
|
||||
)
|
||||
|
||||
ERRORS=0
|
||||
|
||||
echo "🔍 Validating @directus/extensions-sdk imports..."
|
||||
echo ""
|
||||
|
||||
# Search all .ts and .vue files in extension directories
|
||||
SEARCH_DIRS=(
|
||||
"$REPO_ROOT/packages/cms-infra/extensions"
|
||||
"$REPO_ROOT/packages/unified-dashboard"
|
||||
"$REPO_ROOT/packages/customer-manager"
|
||||
"$REPO_ROOT/packages/company-manager"
|
||||
"$REPO_ROOT/packages/people-manager"
|
||||
"$REPO_ROOT/packages/acquisition-manager"
|
||||
"$REPO_ROOT/packages/feedback-commander"
|
||||
)
|
||||
|
||||
for DIR in "${SEARCH_DIRS[@]}"; do
|
||||
[ -d "$DIR" ] || continue
|
||||
|
||||
# Find all imports from @directus/extensions-sdk
|
||||
while IFS= read -r line; do
|
||||
FILE=$(echo "$line" | cut -d: -f1)
|
||||
LINENUM=$(echo "$line" | cut -d: -f2)
|
||||
CONTENT=$(echo "$line" | cut -d: -f3-)
|
||||
|
||||
# Extract imported names from the import statement
|
||||
IMPORTS=$(echo "$CONTENT" | grep -oE '\{[^}]+\}' | tr -d '{}' | tr ',' '\n' | sed 's/^ *//;s/ *$//' | sed 's/ as .*//')
|
||||
|
||||
for IMPORT in $IMPORTS; do
|
||||
[ -z "$IMPORT" ] && continue
|
||||
FOUND=false
|
||||
for VALID in "${VALID_EXPORTS[@]}"; do
|
||||
if [ "$IMPORT" = "$VALID" ]; then
|
||||
FOUND=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$FOUND" = false ]; then
|
||||
echo "❌ INVALID IMPORT: '$IMPORT' in $FILE:$LINENUM"
|
||||
echo " '$IMPORT' is NOT exported by @directus/extensions-sdk in Directus 11.x"
|
||||
echo " This WILL crash ALL extensions at runtime!"
|
||||
echo ""
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done
|
||||
done < <(grep -rn "from ['\"]@directus/extensions-sdk['\"]" "$DIR" --include="*.ts" --include="*.vue" 2>/dev/null || true)
|
||||
done
|
||||
|
||||
if [ "$ERRORS" -gt 0 ]; then
|
||||
echo "💥 Found $ERRORS invalid import(s)!"
|
||||
echo ""
|
||||
echo "Valid exports from @directus/extensions-sdk:"
|
||||
printf " %s\n" "${VALID_EXPORTS[@]}"
|
||||
echo ""
|
||||
echo "Common fixes:"
|
||||
echo " useRouter → import { useRouter } from 'vue-router'"
|
||||
echo " useRoute → import { useRoute } from 'vue-router'"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ All @directus/extensions-sdk imports are valid."
|
||||
fi
|
||||
Reference in New Issue
Block a user