waffensachkunde

Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.

/ app electron.vite.config.ts

5,3 KB Rohdatei
app/electron.vite.config.ts — 147 Zeilen
1 import { execFileSync } from 'node:child_process';
2 import { readFileSync } from 'node:fs';
3 import { resolve } from 'node:path';
4
5 import react from '@vitejs/plugin-react';
6 import { defineConfig } from 'electron-vite';
7 import type { Plugin } from 'vite';
8
9 import { CSP_PLATZHALTER, cspRichtlinie } from './src/shared/csp';
10
11 /**
12 * Version aus der package.json, zur Bauzeit fest eingesetzt.
13 *
14 * `app.getVersion()` liefert nur im gepackten Zustand die Anwendungsversion.
15 * Wird der gebaute Einstiegspunkt direkt gestartet – etwa in der Entwicklung
16 * oder in den Oberflächentests – findet Electron keine package.json und gibt
17 * ersatzweise die Electron-Version zurück. Das stand dann fälschlich als
18 * „Programmversion" auf dem Startbildschirm.
19 */
20 const paket = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8')) as {
21 version: string;
22 };
23
24 /** Ein Git-Aufruf, dessen Fehlschlag den Bau nicht aufhält. */
25 function git(...argumente: readonly string[]): string | null {
26 try {
27 return execFileSync('git', argumente, {
28 cwd: __dirname,
29 encoding: 'utf-8',
30 stdio: ['ignore', 'pipe', 'ignore'],
31 }).trim();
32 } catch {
33 /* Kein Git, kein Arbeitsbaum, kein Commit – etwa beim Bau aus einem
34 Quellarchiv. Die Anwendung läuft ohne Baukennung genauso. */
35 return null;
36 }
37 }
38
39 /**
40 * Woraus dieser Bau entstanden ist.
41 *
42 * Die Versionsnummer allein reicht nicht: Zwischen zwei Veröffentlichungen
43 * entstehen viele Bauten mit derselben Nummer, und wer eine Rückmeldung gibt
44 * („bei mir tut X nicht“), kann sonst nicht sagen, welchen Stand er vor sich
45 * hat.
46 *
47 * Genommen wird das **Commit-Datum**, nicht das Bau-Datum: Dasselbe Commit
48 * ergibt so immer dieselbe Kennung, und der Bau bleibt wiederholbar. Ein `+`
49 * verrät einen Arbeitsbaum mit ungesicherten Änderungen – dann stimmt der
50 * Quellstand eben nicht mit dem Commit überein, und das muss man sehen.
51 */
52 function baukennung(): { kennung: string; stand: string } {
53 const kurz = git('rev-parse', '--short', 'HEAD');
54 if (kurz === null) {
55 return { kennung: '', stand: '' };
56 }
57 /* `--untracked-files=no`, und das mit Absicht: electron-vite legt beim
58 Bauen eine transpilierte Kopie dieser Datei daneben
59 (`electron.vite.config.<zeitstempel>.mjs`) und räumt sie danach weg.
60 Ohne den Schalter meldete `status` genau diese Datei – und jeder
61 Bau truegänge ein `+`, auch aus einem tadellos sauberen Arbeitsbaum.
62 Eine Marke, die immer leuchtet, sagt nichts mehr.
63
64 Der Preis: Eine völlig neue, noch nicht aufgenommene Quelldatei fällt
65 nicht auf. Der Fall ist selten und fällt anderswo laut auf – wer das
66 Commit auscheckt, bekommt einen Bau, der gar nicht erst durchläuft. */
67 const schmutzig = (git('status', '--porcelain', '--untracked-files=no') ?? '').length > 0;
68 const datum = git('log', '-1', '--format=%cI') ?? '';
69 return { kennung: schmutzig ? `${kurz}+` : kurz, stand: datum.slice(0, 10) };
70 }
71
72 const bau = baukennung();
73
74 /**
75 * Ersetzt den Platzhalter im `<meta http-equiv="Content-Security-Policy">`
76 * durch die Richtlinie aus `src/shared/csp.ts`.
77 *
78 * Im Dev-Modus braucht Vite `'unsafe-inline'` (HMR-Präambel, injizierte
79 * Styles) – im Produktionsbuild gilt die strenge Variante.
80 *
81 * `'meta'` als Ziel, und das ist hier der ganze Punkt: Direktiven, die ein
82 * Browser im `<meta>`-Element ignoriert (`frame-ancestors`), bleiben draußen.
83 * Sonst schreibt Chromium bei jedem Start einen Konsolenfehler über eine
84 * Direktive, die ohnehin nicht gilt. Den Kopf-Weg bedient
85 * `src/main/sicherheit.ts`.
86 */
87 function cspPlugin(): Plugin {
88 return {
89 name: 'lernapp-csp',
90 transformIndexHtml: {
91 order: 'pre',
92 handler(html, ctx) {
93 const modus = ctx.server ? 'development' : 'production';
94 return html.replaceAll(CSP_PLATZHALTER, cspRichtlinie(modus, 'meta'));
95 },
96 },
97 };
98 }
99
100 export default defineConfig({
101 main: {
102 define: {
103 __ANWENDUNGS_VERSION__: JSON.stringify(paket.version),
104 __BAUKENNUNG__: JSON.stringify(bau.kennung),
105 __BAUSTAND__: JSON.stringify(bau.stand),
106 },
107 build: {
108 // Native und CommonJS-Abhängigkeiten (better-sqlite3) dürfen nicht
109 // gebündelt werden – sie bleiben in node_modules und landen über
110 // electron-builder im Paket. `true` ist zwar der Standard, steht hier
111 // aber bewusst explizit, weil das Gegenstück im Preload abweicht.
112 externalizeDeps: true,
113 rollupOptions: {
114 input: { index: resolve(__dirname, 'src/main/index.ts') },
115 },
116 },
117 },
118
119 preload: {
120 build: {
121 // MUSS false sein: mit `sandbox: true` kann das Preload-Skript zur
122 // Laufzeit nichts nachladen, also muss alles gebündelt sein.
123 // Ausgabeformat bleibt CommonJS (package.json hat kein
124 // "type": "module") – sandboxed Preloads unterstützen kein ESM.
125 externalizeDeps: false,
126 rollupOptions: {
127 input: { index: resolve(__dirname, 'src/preload/index.ts') },
128 },
129 },
130 },
131
132 renderer: {
133 root: resolve(__dirname, 'src/renderer'),
134 resolve: {
135 alias: {
136 '@renderer': resolve(__dirname, 'src/renderer/src'),
137 '@shared': resolve(__dirname, 'src/shared'),
138 },
139 },
140 plugins: [react(), cspPlugin()],
141 build: {
142 rollupOptions: {
143 input: { index: resolve(__dirname, 'src/renderer/index.html') },
144 },
145 },
146 },
147 });