Compare commits

..
3 Commits
Author SHA1 Message Date
mmintel 294b6b3bfc chore(release): 2.3.38
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Successful in 1m14s
Build & Deploy / 🏗️ Build (push) Successful in 2m19s
Build & Deploy / 🚀 Deploy (push) Successful in 18s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m31s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-10-09 13:00:29 +02:00
mmintel 7f9742bcaa fix(products): restore NA low-voltage cables broken by flattened frontmatter
Commit 76cbdaa3 destroyed the YAML indentation of NAYY, NAYCWY, NAY2Y,
NA2XY and NA2X2Y, silently dropping them from the catalog (404 detail
pages, missing from category listing and sitemap). Restored the valid
frontmatter from 76cbdaa3~1 (whitespace-only diff, content unchanged)
and added a content contract spec guarding parseability and catalog
visibility.
2026-10-09 13:00:29 +02:00
mmintel 5aad24522c chore(ci): retarget deployments to infra.mintel.me (server consolidation)
Build & Deploy / 🔍 Prepare (push) Successful in 2m54s
Build & Deploy / 🧪 QA (push) Successful in 1m19s
Build & Deploy / 🏗️ Build (push) Failing after 1s
Build & Deploy / 🚀 Deploy (push) Skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Skipped
Build & Deploy / 🔔 Notify (push) Failing after 1s
2026-10-01 22:45:28 +02:00
10 changed files with 614 additions and 536 deletions
+9 -9
View File
@@ -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()
+7
View File
@@ -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
View File
@@ -116,7 +116,7 @@
"prepare": "husky",
"preinstall": "npx only-allow pnpm"
},
"version": "2.3.37",
"version": "2.3.38",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
+1 -1
View File
@@ -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=""
+71
View File
@@ -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');
}
});
});