Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
294b6b3bfc | ||
|
|
7f9742bcaa | ||
|
|
5aad24522c |
@@ -344,7 +344,7 @@ jobs:
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.ALPHA_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan -H alpha.mintel.me >> ~/.ssh/known_hosts 2>/dev/null
|
||||
ssh-keyscan -H infra.mintel.me >> ~/.ssh/known_hosts 2>/dev/null
|
||||
|
||||
# Transfer and Restart
|
||||
if [[ "$TARGET" == "production" ]]; then
|
||||
@@ -356,17 +356,17 @@ jobs:
|
||||
else
|
||||
SITE_DIR="/home/deploy/sites/branch.klz-cables.com/${SLUG:-unknown}"
|
||||
fi
|
||||
ssh root@alpha.mintel.me "mkdir -p $SITE_DIR"
|
||||
ssh root@infra.mintel.me "mkdir -p $SITE_DIR"
|
||||
|
||||
scp .env.deploy root@alpha.mintel.me:$SITE_DIR/$ENV_FILE
|
||||
scp docker-compose.yml root@alpha.mintel.me:$SITE_DIR/docker-compose.yml
|
||||
scp .env.deploy root@infra.mintel.me:$SITE_DIR/$ENV_FILE
|
||||
scp docker-compose.yml root@infra.mintel.me:$SITE_DIR/docker-compose.yml
|
||||
|
||||
ssh root@alpha.mintel.me "cd $SITE_DIR && echo '${{ secrets.REGISTRY_PASS }}' | docker login registry.infra.mintel.me -u '${{ secrets.REGISTRY_USER }}' --password-stdin"
|
||||
ssh root@alpha.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' pull"
|
||||
ssh root@alpha.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' up -d --remove-orphans"
|
||||
ssh root@infra.mintel.me "cd $SITE_DIR && echo '${{ secrets.REGISTRY_PASS }}' | docker login registry.infra.mintel.me -u '${{ secrets.REGISTRY_USER }}' --password-stdin"
|
||||
ssh root@infra.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' pull"
|
||||
ssh root@infra.mintel.me "cd $SITE_DIR && docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' up -d --remove-orphans"
|
||||
|
||||
ssh root@alpha.mintel.me "docker network prune -f"
|
||||
ssh root@alpha.mintel.me "docker system prune -f --filter 'until=24h'"
|
||||
ssh root@infra.mintel.me "docker network prune -f"
|
||||
ssh root@infra.mintel.me "docker system prune -f --filter 'until=24h'"
|
||||
|
||||
- name: 🧹 Post-Deploy Cleanup (Runner)
|
||||
if: always()
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Incident Ledger
|
||||
|
||||
One appended line per real incident. Never rewritten.
|
||||
|
||||
| Date | Area | Mechanism | Root cause | Remedy | Tier |
|
||||
| :--------- | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--- |
|
||||
| 2026-10-09 | Products / Niederspannungskabel | Five aluminium low-voltage products (NAYY, NAYCWY, NAY2Y, NA2XY, NA2X2Y) silently invisible on the site: dropped from category listing, 404 on detail pages, absent from sitemap | Commit 76cbdaa3 (2026-08-07, release 2.3.33 "NA Kabel Leiterklassen Fix") flattened frontmatter indentation of exactly these five files while removing RE/SE conductor shapes; broken YAML made the product loader return null and the site dropped them without any error | Restored valid frontmatter from 76cbdaa3~1 (content verified identical modulo indentation, whitespace-only diff), kept current markdown body incl. the RE/SE fix; regression spec tests/products-content.test.ts guards parseability and catalog visibility | M |
|
||||
+1
-1
@@ -116,7 +116,7 @@
|
||||
"prepare": "husky",
|
||||
"preinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"version": "2.3.37",
|
||||
"version": "2.3.38",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
|
||||
@@ -13,7 +13,7 @@ fi
|
||||
# ── Configuration ──────────────────────────────────────────────────────────
|
||||
SOURCE_ENV="${1:-}" # local | testing | staging | prod
|
||||
TARGET_ENV="${2:-}" # testing | staging | prod
|
||||
SSH_HOST="root@alpha.mintel.me"
|
||||
SSH_HOST="root@infra.mintel.me"
|
||||
LOCAL_MEDIA_DIR="./public/media"
|
||||
|
||||
DRY_RUN=""
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import { getAllProducts } from '../lib/products';
|
||||
|
||||
const productsDir = path.join(process.cwd(), 'content', 'products');
|
||||
const productFiles = fs.readdirSync(productsDir).filter((f) => f.endsWith('.mdx'));
|
||||
|
||||
interface CatalogEntry {
|
||||
slug: string;
|
||||
parseable: boolean;
|
||||
error?: string;
|
||||
declaresImages: boolean;
|
||||
}
|
||||
|
||||
// Single parse pass at module load, mirroring the production loader's first
|
||||
// touch of each file (gray-matter caches per input string, so re-parsing the
|
||||
// same file inside individual tests would not re-throw parse errors).
|
||||
const catalog: CatalogEntry[] = productFiles.map((file) => {
|
||||
const raw = fs.readFileSync(path.join(productsDir, file), 'utf-8');
|
||||
try {
|
||||
const { data } = matter(raw);
|
||||
return {
|
||||
slug: file.replace('.mdx', ''),
|
||||
parseable: true,
|
||||
declaresImages: Array.isArray(data.images) && data.images.length > 0,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
slug: file.replace('.mdx', ''),
|
||||
parseable: false,
|
||||
error: `${file}: ${(error as Error).message.split('\n')[0]}`,
|
||||
// An unparseable file cannot declare anything and must never silently
|
||||
// shrink the expected catalog — require it to be listed.
|
||||
declaresImages: true,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const ALUMINIUM_LOW_VOLTAGE_SLUGS = ['nayy', 'naycwy', 'nay2y', 'na2xy', 'na2x2y'];
|
||||
|
||||
describe('Product catalog content contract', () => {
|
||||
it('every product file has parseable frontmatter', () => {
|
||||
expect(catalog.filter((entry) => !entry.parseable).map((e) => e.error)).toEqual([]);
|
||||
});
|
||||
|
||||
it('exposes every product that declares images in the catalog listing', async () => {
|
||||
const expected = catalog
|
||||
.filter((entry) => entry.declaresImages)
|
||||
.map((e) => e.slug)
|
||||
.sort();
|
||||
const actual = (await getAllProducts('de')).map((p) => p.slug).sort();
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('lists all aluminium low-voltage cable types (NAYY, NAYCWY, NAY2Y, NA2XY, NA2X2Y)', async () => {
|
||||
const slugs = (await getAllProducts('de')).map((p) => p.slug);
|
||||
for (const slug of ALUMINIUM_LOW_VOLTAGE_SLUGS) {
|
||||
expect(slugs).toContain(slug);
|
||||
}
|
||||
});
|
||||
|
||||
it('categorizes the aluminium low-voltage types as Niederspannungskabel', async () => {
|
||||
const products = await getAllProducts('de');
|
||||
for (const slug of ALUMINIUM_LOW_VOLTAGE_SLUGS) {
|
||||
const product = products.find((p) => p.slug === slug);
|
||||
expect(product?.frontmatter.categories[0]).toBe('Niederspannungskabel');
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user