From 98bce1b0da675715b2e5482c0ad61ba21135f62f Mon Sep 17 00:00:00 2001
From: Server-Bibliothek <server-bibliothek@lokal>
Date: Di, 04 Aug 2026 07:00:04 +0200
Subject: [PATCH] Add a desktop client that knows what is installed locally

---
 bauen-exe.cjs |   73 ++++++++++++++++++++++++++++++++++++
 1 files changed, 73 insertions(+), 0 deletions(-)

diff --git a/bauen-exe.cjs b/bauen-exe.cjs
new file mode 100644
index 0000000..3bcfa6d
--- /dev/null
+++ b/bauen-exe.cjs
@@ -0,0 +1,73 @@
+/**
+ * Baut aus dem gebündelten Server und der gebauten Oberfläche eine einzelne
+ * ausführbare Datei (Node SEA). Die Dateien der Oberfläche werden als Assets
+ * eingebettet und zur Laufzeit binärsicher aus der exe ausgeliefert.
+ *
+ * Voraussetzung: `npm run build` und `npm run bundle -w packages/bibliothek-server`.
+ */
+const { execFileSync } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+
+const EXE_NAME = 'Software-Bibliothek.exe';
+const SENTINEL = 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2';
+
+const wurzel = __dirname;
+const oberflaechenOrdner = path.join(wurzel, 'packages', 'bibliothek-client', 'dist');
+const bundleOrdner = path.join(wurzel, 'packages', 'bibliothek-server', 'build-exe');
+const konfigPfad = path.join(bundleOrdner, 'sea-config.json');
+const blobPfad = path.join(bundleOrdner, 'sea-prep.blob');
+const ausgabeOrdner = path.join(wurzel, 'exe');
+const exePfad = path.join(ausgabeOrdner, EXE_NAME);
+
+function schritt(beschreibung, aktion) {
+  process.stdout.write(`${beschreibung} … `);
+  const ergebnis = aktion();
+  console.log(ergebnis ?? 'ok');
+}
+
+schritt('Oberflaechen-Dateien einsammeln', () => {
+  const dateien = fs.readdirSync(oberflaechenOrdner).filter((name) => fs.statSync(path.join(oberflaechenOrdner, name)).isFile());
+  if (!dateien.includes('index.html')) {
+    throw new Error(`In ${oberflaechenOrdner} fehlt index.html — wurde der Client gebaut?`);
+  }
+  const assets = {};
+  for (const name of dateien) {
+    assets[name] = path.relative(wurzel, path.join(oberflaechenOrdner, name)).replace(/\\/g, '/');
+  }
+  fs.writeFileSync(
+    konfigPfad,
+    JSON.stringify(
+      {
+        main: path.relative(wurzel, path.join(bundleOrdner, 'server.cjs')).replace(/\\/g, '/'),
+        output: path.relative(wurzel, blobPfad).replace(/\\/g, '/'),
+        disableExperimentalSEAWarning: true,
+        assets,
+      },
+      null,
+      2
+    )
+  );
+  return `${dateien.length} Datei(en)`;
+});
+
+schritt('Blob erzeugen (Server + eingebettete Oberflaeche)', () => {
+  execFileSync(process.execPath, ['--experimental-sea-config', konfigPfad], { stdio: 'pipe', cwd: wurzel });
+});
+
+schritt('Node-Binary kopieren', () => {
+  fs.mkdirSync(ausgabeOrdner, { recursive: true });
+  fs.copyFileSync(process.execPath, exePfad);
+});
+
+schritt('Code in die exe injizieren', () => {
+  execFileSync('npx', ['--yes', 'postject', exePfad, 'NODE_SEA_BLOB', blobPfad, '--sentinel-fuse', SENTINEL], {
+    stdio: 'pipe',
+    shell: process.platform === 'win32',
+  });
+});
+
+const groesseMb = (fs.statSync(exePfad).size / 1024 / 1024).toFixed(1);
+console.log(`\nFertig: ${exePfad} (${groesseMb} MB)`);
+console.log('Zum Betrieb: exe starten, dann http://localhost:3000 aufrufen.');
+console.log('Die Nutzdaten (Datenbank + geholte Installer) landen im Ordner "data" neben der exe.');

--
Gitblit v1.9.3