feat: Introduce NEXT_PUBLIC_TARGET build argument and abstract server-side error reporting to a dedicated service.
Some checks failed
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 7s
Build & Deploy KLZ Cables / 🏗️ Build App (push) Successful in 4m2s
Build & Deploy KLZ Cables / 🏗️ Build Gatekeeper (push) Successful in 21s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Successful in 11m56s
Build & Deploy KLZ Cables / 🚀 Deploy (push) Successful in 43s
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Failing after 2m7s
Build & Deploy KLZ Cables / 🔔 Notifications (push) Successful in 1s
Some checks failed
Build & Deploy KLZ Cables / 🔍 Prepare Environment (push) Successful in 7s
Build & Deploy KLZ Cables / 🏗️ Build App (push) Successful in 4m2s
Build & Deploy KLZ Cables / 🏗️ Build Gatekeeper (push) Successful in 21s
Build & Deploy KLZ Cables / 🧪 Quality Assurance (push) Successful in 11m56s
Build & Deploy KLZ Cables / 🚀 Deploy (push) Successful in 43s
Build & Deploy KLZ Cables / ⚡ PageSpeed (push) Failing after 2m7s
Build & Deploy KLZ Cables / 🔔 Notifications (push) Successful in 1s
This commit is contained in:
@@ -10,6 +10,11 @@
|
|||||||
# ────────────────────────────────────────────────────────────────────────────
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
||||||
|
# TARGET is used to differentiate between environments (testing, staging, production)
|
||||||
|
# NEXT_PUBLIC_TARGET makes this information available to the frontend
|
||||||
|
NEXT_PUBLIC_TARGET=development
|
||||||
|
# TARGET is used server-side
|
||||||
|
TARGET=development
|
||||||
|
|
||||||
# ────────────────────────────────────────────────────────────────────────────
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
# Analytics (Umami)
|
# Analytics (Umami)
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ jobs:
|
|||||||
--build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
--build-arg NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
|
||||||
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
|
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
|
||||||
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="$NEXT_PUBLIC_UMAMI_SCRIPT_URL" \
|
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="$NEXT_PUBLIC_UMAMI_SCRIPT_URL" \
|
||||||
|
--build-arg NEXT_PUBLIC_TARGET="$TARGET" \
|
||||||
--build-arg DIRECTUS_URL="$DIRECTUS_URL" \
|
--build-arg DIRECTUS_URL="$DIRECTUS_URL" \
|
||||||
-t registry.infra.mintel.me/mintel/klz-cables.com:$IMAGE_TAG \
|
-t registry.infra.mintel.me/mintel/klz-cables.com:$IMAGE_TAG \
|
||||||
--cache-from type=registry,ref=registry.infra.mintel.me/mintel/klz-cables.com:buildcache \
|
--cache-from type=registry,ref=registry.infra.mintel.me/mintel/klz-cables.com:buildcache \
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
|||||||
ARG NEXT_PUBLIC_BASE_URL
|
ARG NEXT_PUBLIC_BASE_URL
|
||||||
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||||
ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
||||||
|
ARG NEXT_PUBLIC_TARGET
|
||||||
ARG DIRECTUS_URL
|
ARG DIRECTUS_URL
|
||||||
|
|
||||||
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
|
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
|
||||||
ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||||
ENV NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
ENV NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
||||||
|
ENV NEXT_PUBLIC_TARGET=$NEXT_PUBLIC_TARGET
|
||||||
ENV DIRECTUS_URL=$DIRECTUS_URL
|
ENV DIRECTUS_URL=$DIRECTUS_URL
|
||||||
|
|
||||||
# Validate environment variables during build
|
# Validate environment variables during build
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ function createConfig() {
|
|||||||
return {
|
return {
|
||||||
env: env.NODE_ENV,
|
env: env.NODE_ENV,
|
||||||
target,
|
target,
|
||||||
isProduction: target === 'production',
|
isProduction: target === 'production' || !target,
|
||||||
isStaging: target === 'staging',
|
isStaging: target === 'staging',
|
||||||
isTesting: target === 'testing',
|
isTesting: target === 'testing',
|
||||||
isDevelopment: target === 'development' || env.NODE_ENV === 'development',
|
isDevelopment: target === 'development',
|
||||||
|
|
||||||
baseUrl: env.NEXT_PUBLIC_BASE_URL,
|
baseUrl: env.NEXT_PUBLIC_BASE_URL,
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createDirectus, rest, authentication, readItems, readCollections } from '@directus/sdk';
|
import { createDirectus, rest, authentication, readItems, readCollections } from '@directus/sdk';
|
||||||
import { config } from './config';
|
import { config } from './config';
|
||||||
import * as Sentry from '@sentry/nextjs';
|
import { getServerAppServices } from './services/create-services.server';
|
||||||
|
|
||||||
const { url, adminEmail, password, token, proxyPath, internalUrl } = config.directus;
|
const { url, adminEmail, password, token, proxyPath, internalUrl } = config.directus;
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ const shouldShowDevErrors = config.isTesting || config.isDevelopment;
|
|||||||
*/
|
*/
|
||||||
function formatError(error: any) {
|
function formatError(error: any) {
|
||||||
if (shouldShowDevErrors) {
|
if (shouldShowDevErrors) {
|
||||||
return error.message || 'An unexpected error occurred.';
|
return error.errors?.[0]?.message || error.message || 'An unexpected error occurred.';
|
||||||
}
|
}
|
||||||
return 'A system error occurred. Our team has been notified.';
|
return 'A system error occurred. Our team has been notified.';
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,9 @@ export async function ensureAuthenticated() {
|
|||||||
try {
|
try {
|
||||||
await client.login(adminEmail, password);
|
await client.login(adminEmail, password);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Sentry.captureException(e);
|
if (typeof window === 'undefined') {
|
||||||
|
getServerAppServices().errors.captureException(e, { part: 'directus_auth' });
|
||||||
|
}
|
||||||
console.error('Failed to authenticate with Directus:', e);
|
console.error('Failed to authenticate with Directus:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +80,9 @@ export async function getProducts(locale: string = 'de') {
|
|||||||
);
|
);
|
||||||
return items.map((item) => mapDirectusProduct(item, locale));
|
return items.map((item) => mapDirectusProduct(item, locale));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Sentry.captureException(error);
|
if (typeof window === 'undefined') {
|
||||||
|
getServerAppServices().errors.captureException(error, { part: 'directus_get_products' });
|
||||||
|
}
|
||||||
console.error('Error fetching products:', error);
|
console.error('Error fetching products:', error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -104,7 +108,12 @@ export async function getProductBySlug(slug: string, locale: string = 'de') {
|
|||||||
if (!items || items.length === 0) return null;
|
if (!items || items.length === 0) return null;
|
||||||
return mapDirectusProduct(items[0], locale);
|
return mapDirectusProduct(items[0], locale);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Sentry.captureException(error);
|
if (typeof window === 'undefined') {
|
||||||
|
getServerAppServices().errors.captureException(error, {
|
||||||
|
part: 'directus_get_product_by_slug',
|
||||||
|
slug,
|
||||||
|
});
|
||||||
|
}
|
||||||
console.error(`Error fetching product ${slug}:`, error);
|
console.error(`Error fetching product ${slug}:`, error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -117,7 +126,9 @@ export async function checkHealth() {
|
|||||||
await ensureAuthenticated();
|
await ensureAuthenticated();
|
||||||
await client.request(readCollections());
|
await client.request(readCollections());
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Sentry.captureException(e);
|
if (typeof window === 'undefined') {
|
||||||
|
getServerAppServices().errors.captureException(e, { part: 'directus_health_auth' });
|
||||||
|
}
|
||||||
console.error('Directus authentication failed during health check:', e);
|
console.error('Directus authentication failed during health check:', e);
|
||||||
return {
|
return {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
@@ -133,7 +144,9 @@ export async function checkHealth() {
|
|||||||
try {
|
try {
|
||||||
await client.request(readItems('products', { limit: 1 }));
|
await client.request(readItems('products', { limit: 1 }));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Sentry.captureException(e);
|
if (typeof window === 'undefined') {
|
||||||
|
getServerAppServices().errors.captureException(e, { part: 'directus_health_schema' });
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
e.message?.includes('does not exist') ||
|
e.message?.includes('does not exist') ||
|
||||||
e.code === 'INVALID_PAYLOAD' ||
|
e.code === 'INVALID_PAYLOAD' ||
|
||||||
@@ -142,7 +155,7 @@ export async function checkHealth() {
|
|||||||
return {
|
return {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: shouldShowDevErrors
|
message: shouldShowDevErrors
|
||||||
? 'The "products" collection is missing or inaccessible. Please sync your data.'
|
? `The "products" collection is missing or inaccessible. Error: ${e.message || 'Unknown'}`
|
||||||
: 'Required data structures are currently unavailable.',
|
: 'Required data structures are currently unavailable.',
|
||||||
code: 'SCHEMA_MISSING',
|
code: 'SCHEMA_MISSING',
|
||||||
};
|
};
|
||||||
@@ -150,7 +163,7 @@ export async function checkHealth() {
|
|||||||
return {
|
return {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: shouldShowDevErrors
|
message: shouldShowDevErrors
|
||||||
? `Schema error: ${e.message}`
|
? `Schema error: ${e.errors?.[0]?.message || e.message || 'Unknown error'}`
|
||||||
: 'The data schema is currently misconfigured.',
|
: 'The data schema is currently misconfigured.',
|
||||||
code: 'SCHEMA_ERROR',
|
code: 'SCHEMA_ERROR',
|
||||||
};
|
};
|
||||||
@@ -158,7 +171,9 @@ export async function checkHealth() {
|
|||||||
|
|
||||||
return { status: 'ok', message: 'Directus is reachable and responding.' };
|
return { status: 'ok', message: 'Directus is reachable and responding.' };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
Sentry.captureException(error);
|
if (typeof window === 'undefined') {
|
||||||
|
getServerAppServices().errors.captureException(error, { part: 'directus_health_critical' });
|
||||||
|
}
|
||||||
console.error('Directus health check failed with unexpected error:', error);
|
console.error('Directus health check failed with unexpected error:', error);
|
||||||
return {
|
return {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ const preprocessEmptyString = (val: unknown) => (val === '' ? undefined : val);
|
|||||||
export const envSchema = z.object({
|
export const envSchema = z.object({
|
||||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||||
NEXT_PUBLIC_BASE_URL: z.preprocess(preprocessEmptyString, z.string().url()),
|
NEXT_PUBLIC_BASE_URL: z.preprocess(preprocessEmptyString, z.string().url()),
|
||||||
NEXT_PUBLIC_TARGET: z
|
NEXT_PUBLIC_TARGET: z.enum(['development', 'testing', 'staging', 'production']).optional(),
|
||||||
.enum(['development', 'testing', 'staging', 'production'])
|
|
||||||
.default('development'),
|
|
||||||
|
|
||||||
// Analytics
|
// Analytics
|
||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.preprocess(preprocessEmptyString, z.string().optional()),
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.preprocess(preprocessEmptyString, z.string().optional()),
|
||||||
@@ -50,7 +48,7 @@ export const envSchema = z.object({
|
|||||||
INTERNAL_DIRECTUS_URL: z.preprocess(preprocessEmptyString, z.string().url().optional()),
|
INTERNAL_DIRECTUS_URL: z.preprocess(preprocessEmptyString, z.string().url().optional()),
|
||||||
|
|
||||||
// Deploy Target
|
// Deploy Target
|
||||||
TARGET: z.enum(['development', 'testing', 'staging', 'production']).default('development'),
|
TARGET: z.enum(['development', 'testing', 'staging', 'production']).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Env = z.infer<typeof envSchema>;
|
export type Env = z.infer<typeof envSchema>;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function getServerAppServices(): AppServices {
|
|||||||
|
|
||||||
// Create logger first to log initialization
|
// Create logger first to log initialization
|
||||||
const logger = new PinoLoggerService('server');
|
const logger = new PinoLoggerService('server');
|
||||||
|
|
||||||
logger.info('Initializing server application services', {
|
logger.info('Initializing server application services', {
|
||||||
environment: getMaskedConfig(),
|
environment: getMaskedConfig(),
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -40,7 +40,9 @@ export function getServerAppServices(): AppServices {
|
|||||||
: new NoopErrorReportingService();
|
: new NoopErrorReportingService();
|
||||||
|
|
||||||
if (config.errors.glitchtip.enabled) {
|
if (config.errors.glitchtip.enabled) {
|
||||||
logger.info('GlitchTip error reporting service initialized');
|
logger.info('GlitchTip error reporting service initialized', {
|
||||||
|
dsnPresent: Boolean(config.errors.glitchtip.dsn),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.info('Noop error reporting service initialized (error reporting disabled)');
|
logger.info('Noop error reporting service initialized (error reporting disabled)');
|
||||||
}
|
}
|
||||||
@@ -54,9 +56,8 @@ export function getServerAppServices(): AppServices {
|
|||||||
});
|
});
|
||||||
|
|
||||||
singleton = new AppServices(analytics, errors, cache, logger);
|
singleton = new AppServices(analytics, errors, cache, logger);
|
||||||
|
|
||||||
logger.info('All application services initialized successfully');
|
logger.info('All application services initialized successfully');
|
||||||
|
|
||||||
return singleton;
|
return singleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user