v0.0.2: pre-productie (Bootstrap 5.3.3, LightPdf klant-PDF, server-setup en release-script)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Machiel
2026-10-15 11:58:52 +02:00
co-authored by Claude Opus 5.5
parent bffa053c05
commit 7d302dc405
48 changed files with 2769 additions and 698 deletions
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
# Kopieert de 3D-engine van de Tectum-configurator (../configurator) naar static/configurator/, voor de
# viewer van gekozen modellen (templates/viewer.html, static/viewer.js). Zelfde werkwijze als
# tools/configurator-sync.sh van de website; alleen wat de engine nodig heeft (js, assets, kosten).
# Gebruik: tools/configurator-sync.sh [pad-naar-configurator] [doelmap]
cd "$(dirname "$0")/.." || exit 1
SRC="${1:-../configurator}"
DEST="${2:-static/configurator}"
[ -f "$SRC/js/house/house-model.js" ] || { echo "Geen configurator gevonden in $SRC" >&2; exit 1; }
mkdir -p "$DEST"
rsync -a --delete \
--include='/LICENTIE.md' --include='/assets/***' --include='/js/***' --include='/kosten/***' \
--exclude='*' \
"$SRC/" "$DEST/"
echo "Configurator-engine gekopieerd naar $DEST/ ($(du -sh "$DEST" | cut -f1))"
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env node
// Exporteert de standaardmodellen van de configurator (HouseModel.MODELS) als house-design-JSON,
// precies zoals Bestand › Opslaan in de configurator ze wegschrijft.
// Gebruik: node tools/export-models.js [pad-naar-configurator] [uitvoermap]
// Standaard: ../configurator → demo/modellen/
'use strict';
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const root = path.dirname(__dirname);
const SRC = path.resolve(process.argv[2] || path.join(root, '..', 'configurator'));
const OUT = path.resolve(process.argv[3] || path.join(root, 'demo', 'modellen'));
// De rekenmodules in de volgorde van light.html; alleen wat nodig is om een model op te bouwen.
const SCRIPTS = [
'js/vendor/three.min.js', 'js/core/geometry.js', 'js/core/view-layers.js', 'js/wall/kozijn-model.js',
'js/core/ui-kit.js', 'js/wall/wall-model.js', 'js/floor/floor-model.js', 'js/roof/roof-model.js', 'js/roof/post-model.js',
'js/lighting/lighting-model.js', 'js/elektra/elektra-model.js', 'js/sandwich/sandwich-model.js', 'js/wall/wall.js', 'js/floor/floor.js',
'js/unit/unit-model.js', 'js/room/room-model.js', 'js/interior/fixture-model.js',
'js/house/chimney-model.js', 'js/house/house-model.js'
];
// Minimale browseromgeving: de modules raken het DOM alleen bij het tekenen, niet bij het opbouwen.
const noop = () => {};
const el = () => new Proxy(function () {}, { get: (t, k) => (k === Symbol.toPrimitive ? () => '' : el()), apply: () => el(), construct: () => el() });
const ctx = { console, Math, JSON, Date, setTimeout, clearTimeout, URL, Blob: function () {}, Image: function () { return el(); } };
ctx.window = ctx; ctx.self = ctx; ctx.globalThis = ctx;
ctx.document = { createElement: () => el(), getElementById: () => null, querySelector: () => null, querySelectorAll: () => [], addEventListener: noop, head: el(), body: el(), currentScript: null };
ctx.navigator = { userAgent: 'node', platform: 'node', maxTouchPoints: 0 };
ctx.location = { href: 'http://localhost/', search: '' };
ctx.fetch = () => Promise.reject(new Error('geen fetch'));
ctx.addEventListener = noop;
vm.createContext(ctx);
for (const s of SCRIPTS) {
const file = path.join(SRC, s);
// const/let op het hoogste niveau blijven in het script; zet de modulenamen expliciet op het globale object.
let code = fs.readFileSync(file, 'utf8');
const names = [...code.matchAll(/^(?:const|let|var|class)\s+([A-Z][A-Za-z0-9_]*)\s*=?/gm)].map(m => m[1]);
code += '\n' + names.map(n => `try { globalThis.${n} = ${n}; } catch (e) {}`).join('\n');
vm.runInContext(code, ctx, { filename: file });
}
const version = (fs.readFileSync(path.join(SRC, 'js/app.js'), 'utf8').match(/APP_VERSION\s*=\s*['"]([^'"]+)/) || [])[1] || '';
fs.mkdirSync(OUT, { recursive: true });
const index = [];
for (const [key, def] of Object.entries(ctx.HouseModel.MODELS)) {
const state = ctx.HouseModel.normalize(ctx.HouseModel.clone(def.create()));
state.project = Object.assign({}, state.project, { naam: state.project.naam || def.label });
const design = { type: 'house-design', version: 1, exportedAt: new Date().toISOString(), model: key, configurator: version, ...state };
fs.writeFileSync(path.join(OUT, `${key}.json`), JSON.stringify(design));
index.push({ key, label: def.label });
console.log(`${key}.json ${def.label} (${state.units.length} units)`);
}
fs.writeFileSync(path.join(OUT, 'index.json'), JSON.stringify({ configurator: version, models: index }, null, 2) + '\n');
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Een getagde versie van de CRM naar de server brengen (eerst eenmalig server/setup.sh op de server).
#
# tools/release.sh crm@crm.voorbeeld.nl nieuwste tag
# tools/release.sh crm@crm.voorbeeld.nl v0.0.2 een bepaalde versie (ook om terug te rollen)
#
# De code komt uit `git archive <tag>` (precies wat getagd is, niet de werkkopie); de 3D-engine van de
# configurator ($CONFIGURATOR, standaard ../configurator) gaat er met rsync bij, met hardlinks naar de vorige
# versie zodat alleen wijzigingen over de lijn gaan. Op de server maakt server/activate.sh hem actief.
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
HOST="${1:-}"
CONFIGURATOR="${CONFIGURATOR:-$REPO/../configurator}"
ROOT=/srv/vanemous-crm
say() { printf '\033[38;2;185;89;19m▸\033[0m %s\n' "$*"; }
fail() { printf 'Fout: %s\n' "$*" >&2; exit 1; }
[ -n "$HOST" ] || fail "gebruik: tools/release.sh crm@server [vX.Y.Z]"
TAG="${2:-$(git -C "$REPO" tag --list 'v*' --sort=-v:refname | head -n1)}"
[[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "onbekende versie '$TAG' (verwacht vX.Y.Z)"
git -C "$REPO" rev-parse -q --verify "refs/tags/$TAG" >/dev/null || fail "tag $TAG bestaat niet"
code_version="$(git -C "$REPO" show "$TAG:app.py" | sed -nE "s/^__version__ = '([^']+)'.*/\1/p")"
[ "v$code_version" = "$TAG" ] || fail "tag $TAG bevat __version__ = '$code_version' in app.py"
git -C "$REPO" cat-file -e "$TAG:server/activate.sh" 2>/dev/null || fail "$TAG heeft nog geen server/activate.sh; tag een nieuwere versie"
[ -f "$CONFIGURATOR/js/house/house-model.js" ] || fail "geen configurator in $CONFIGURATOR (zet CONFIGURATOR=pad)"
[ -z "$(git -C "$REPO" status --porcelain)" ] || say "let op: niet-gecommitte wijzigingen zitten niet in $TAG"
target="$ROOT/releases/$TAG"
if ssh "$HOST" "test -f $target/.installed"; then
say "$TAG staat al op de server"
else
say "$TAG uploaden naar $HOST"
git -C "$REPO" archive "$TAG" | ssh "$HOST" "rm -rf $target && mkdir -p $target && tar -x -C $target"
fi
say "3D-engine bijwerken uit $CONFIGURATOR"
ssh "$HOST" "mkdir -p $target/static/configurator"
rsync -az --delete --link-dest="$ROOT/current/static/configurator/" \
--include='/LICENTIE.md' --include='/assets/***' --include='/js/***' --include='/kosten/***' --exclude='*' \
"$CONFIGURATOR/" "$HOST:$target/static/configurator/"
ssh "$HOST" "bash $target/server/activate.sh $TAG"