hmgr 0.0.6 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/agents.js +14 -5
- package/dist/adapter/bundle-yaml.js +87 -0
- package/dist/adapter/claude-code.js +53 -12
- package/dist/adapter/dateisystem.js +85 -43
- package/dist/adapter/git.js +148 -0
- package/dist/adapter/heime.js +76 -0
- package/dist/adapter/hermes.js +16 -7
- package/dist/adapter/npm-registry.js +61 -0
- package/dist/adapter/paket-version.js +25 -0
- package/dist/adapter/umgebung.js +6 -1
- package/dist/adapter/verlauf-json.js +39 -0
- package/dist/adapter/wsl.js +212 -0
- package/dist/adapter/zustand-json.js +40 -12
- package/dist/cli/hilfe.js +197 -11
- package/dist/cli/main.js +376 -21
- package/dist/gui/hmgr-cli.js +44 -0
- package/dist/gui/main.js +445 -4
- package/dist/gui/preload.cjs +30 -2
- package/dist/gui/renderer/index.html +79 -16
- package/dist/gui/renderer/renderer.js +807 -286
- package/dist/gui/renderer/styles.css +89 -16
- package/dist/kern/anwendungsfaelle.js +687 -18
- package/dist/kern/modell.js +192 -1
- package/dist/kern/version.js +13 -0
- package/dist/ports/dateizugriff.js +1 -0
- package/dist/ports/harness.js +4 -0
- package/dist/ports/heime.js +1 -0
- package/dist/ports/verlauf.js +4 -0
- package/dist/ports/zustand.js +16 -1
- package/package.json +26 -5
package/dist/gui/main.js
CHANGED
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
// Fenster. Kein zweiter Prozess: der Kern läuft direkt im Main-Prozess, die GUI ist ein zweiter
|
|
4
4
|
// Kompositions-Einstieg neben der CLI (ARCHITEKTUR.md „Eingang").
|
|
5
5
|
import { app, BrowserWindow, dialog, ipcMain, Menu, shell } from 'electron';
|
|
6
|
+
// electron-updater ist CommonJS: in ESM kommt nur der Default-Export an, `autoUpdater` wird aus ihm
|
|
7
|
+
// herausgegriffen. Ein benannter Import (`import { autoUpdater } from …`) scheitert zur Laufzeit.
|
|
8
|
+
import electronUpdater from 'electron-updater';
|
|
6
9
|
import path from 'node:path';
|
|
7
10
|
import { randomUUID } from 'node:crypto';
|
|
8
11
|
import { stat } from 'node:fs/promises';
|
|
9
12
|
import { fileURLToPath } from 'node:url';
|
|
10
13
|
import { bauen, umgebungLesen } from "../cli/main.js";
|
|
11
14
|
import { quelleAlsText, quelleAusText } from "../adapter/quelle-text.js";
|
|
15
|
+
import { paketVersion } from "../adapter/paket-version.js";
|
|
12
16
|
import { quelleName, quelleSchluessel } from "../kern/modell.js";
|
|
13
17
|
import { kannProfile } from "../ports/harness.js";
|
|
14
18
|
import { iconFuerQuelle } from "./icons.js";
|
|
@@ -16,6 +20,8 @@ import { entscheideOeffnen } from "./oeffnen.js";
|
|
|
16
20
|
import { mcpUebernahmeSerialisieren, mcpUebersichtSerialisieren, mcpWirkungSerialisieren } from "./mcp-ansicht.js";
|
|
17
21
|
import { beobachten } from "./beobachter.js";
|
|
18
22
|
import { loginPathUebernehmen } from "./login-pfad.js";
|
|
23
|
+
import { hmgrCliVersion } from "./hmgr-cli.js";
|
|
24
|
+
import { cliUpdateHinweis } from "../adapter/npm-registry.js";
|
|
19
25
|
const hierVerzeichnis = path.dirname(fileURLToPath(import.meta.url));
|
|
20
26
|
const smoketest = process.env.HMGR_GUI_SMOKETEST === '1';
|
|
21
27
|
/**
|
|
@@ -117,6 +123,18 @@ const { anwendungsfaelle, ports } = bauen(umgebung, {
|
|
|
117
123
|
rueckfrage,
|
|
118
124
|
...(smoketest ? { claudeAusfuehrer: smoketestClaudeAusfuehrer } : {}),
|
|
119
125
|
});
|
|
126
|
+
/**
|
|
127
|
+
* Wer den laufenden Aktualisieren-Lauf ausgelöst hat (E80): der Renderer reicht es bei jedem Aufruf
|
|
128
|
+
* mit, hier gemerkt für die beiden hmgr-Selbstprüfungen (`update-downloaded`, `cliUpdatePruefen`),
|
|
129
|
+
* die selbst keinen eigenen Auslöser kennen. Default 'start', bevor der erste Lauf gemeldet hat.
|
|
130
|
+
*/
|
|
131
|
+
let letzterAusloeser = 'start';
|
|
132
|
+
// Merkt die GUI-Version beim Start im Verlauf (E80): ein Versionssprung gegen den letzten Start
|
|
133
|
+
// bekommt einen Eintrag „hmgr-GUI X installiert". Läuft auch im Smoke-Test — hmgrHome zeigt dort auf
|
|
134
|
+
// ein Wegwerf-Verzeichnis (fixtureAufbauen in scripts/gui-smoke-test.ts), nie auf das echte ~/.hmgr.
|
|
135
|
+
void anwendungsfaelle.versionsSprungVermerken('gui', paketVersion(), 'start').catch((fehler) => {
|
|
136
|
+
console.log(`[verlauf] ${fehler instanceof Error ? fehler.message : String(fehler)}`);
|
|
137
|
+
});
|
|
120
138
|
async function auffangen(fn) {
|
|
121
139
|
await pathBereit; // erst der Login-PATH (F47=O2), dann der Adapter — sonst findet er die CLI nicht
|
|
122
140
|
try {
|
|
@@ -136,6 +154,14 @@ function zielZustandSerialisieren(z) {
|
|
|
136
154
|
gruende: Object.fromEntries(z.gruende),
|
|
137
155
|
ignoriert: z.ignoriert,
|
|
138
156
|
uebernahme: Object.fromEntries(z.uebernahme),
|
|
157
|
+
// Bundles des Ziels (E69): Adresse und Anzeigename der Quelle kommen mit, damit der Renderer
|
|
158
|
+
// die Gruppe beschriften kann, ohne die Quelle selbst nachzuschlagen.
|
|
159
|
+
bundles: z.bundles.map((b) => ({
|
|
160
|
+
name: b.name,
|
|
161
|
+
skills: b.skills,
|
|
162
|
+
quelleText: quelleAlsText(b.quelle),
|
|
163
|
+
quelleName: quelleName(b.quelle),
|
|
164
|
+
})),
|
|
139
165
|
istRegistriert: z.istRegistriert,
|
|
140
166
|
kannSkillOrdner: z.kannSkillOrdner,
|
|
141
167
|
...(z.plugins === undefined ? {} : { plugins: z.plugins }),
|
|
@@ -181,6 +207,7 @@ function registriereIpc() {
|
|
|
181
207
|
...(skill.modell === undefined ? {} : { modell: skill.modell }),
|
|
182
208
|
...(skill.eigenerKontext === undefined ? {} : { eigenerKontext: skill.eigenerKontext }),
|
|
183
209
|
...(skill.argumentHinweis === undefined ? {} : { argumentHinweis: skill.argumentHinweis }),
|
|
210
|
+
...(skill.geaendert === undefined ? {} : { geaendert: skill.geaendert }),
|
|
184
211
|
quelleText: quelleAlsText(eintrag.quelle),
|
|
185
212
|
quelleName: quelleName(eintrag.quelle),
|
|
186
213
|
quelleSchluessel: quelleSchluessel(eintrag.quelle),
|
|
@@ -189,6 +216,45 @@ function registriereIpc() {
|
|
|
189
216
|
}
|
|
190
217
|
return ergebnis;
|
|
191
218
|
}));
|
|
219
|
+
// Bundles aller bekannten Quellen (E69): flach wie der Skill-Katalog, je Bundle seine Quelle und
|
|
220
|
+
// die Zahl seiner Mitglieder. Kaputte Definitionen kommen als eigene Liste mit, damit die GUI sie
|
|
221
|
+
// nennen kann, statt sie zu verschlucken.
|
|
222
|
+
ipcMain.handle('hmgr:bundles', async () => auffangen(async () => {
|
|
223
|
+
const bundles = [];
|
|
224
|
+
const kaputte = [];
|
|
225
|
+
for (const eintrag of await anwendungsfaelle.bundleKatalog()) {
|
|
226
|
+
for (const bundle of eintrag.bundles)
|
|
227
|
+
bundles.push({
|
|
228
|
+
name: bundle.name,
|
|
229
|
+
beschreibung: bundle.beschreibung,
|
|
230
|
+
mitglieder: bundle.mitglieder.length,
|
|
231
|
+
// Anzeige im Dialog: aus welchen Quellen das Bundle Skills holt, ohne Dubletten.
|
|
232
|
+
quellen: [...new Set(bundle.mitglieder.map((m) => quelleAlsText(m.quelle)))],
|
|
233
|
+
quelleText: quelleAlsText(bundle.quelle),
|
|
234
|
+
quelleName: quelleName(bundle.quelle),
|
|
235
|
+
});
|
|
236
|
+
for (const kaputt of eintrag.kaputte)
|
|
237
|
+
kaputte.push({ quelleName: quelleName(eintrag.quelle), datei: kaputt.datei, fehler: kaputt.fehler });
|
|
238
|
+
}
|
|
239
|
+
return { bundles, kaputte };
|
|
240
|
+
}));
|
|
241
|
+
// Installieren und Entfernen wirken auf das Bundle als Ganzes (E69). Der Zeiger ist nicht im
|
|
242
|
+
// Spiel, die Zugehörigkeit steht im Zustand — anders als bei linkLegen braucht es kein `lokal`.
|
|
243
|
+
ipcMain.handle('hmgr:bundleInstallieren', async (_event, args) => auffangen(async () => {
|
|
244
|
+
const ergebnis = await anwendungsfaelle.bundleInstallieren({
|
|
245
|
+
bundle: args.bundle,
|
|
246
|
+
ziel: zielAusIpc(args.harness, args.ort),
|
|
247
|
+
...(args.quelle === undefined ? {} : { quelle: args.quelle }),
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
name: ergebnis.bundle.name,
|
|
251
|
+
gelegt: ergebnis.gelegt,
|
|
252
|
+
uebernommen: ergebnis.uebernommen,
|
|
253
|
+
neueQuellen: ergebnis.neueQuellen.map((q) => quelleName(q)),
|
|
254
|
+
...(ergebnis.hinweis === undefined ? {} : { hinweis: ergebnis.hinweis }),
|
|
255
|
+
};
|
|
256
|
+
}));
|
|
257
|
+
ipcMain.handle('hmgr:bundleEntfernen', async (_event, args) => auffangen(async () => anwendungsfaelle.bundleEntfernen({ bundle: args.bundle, ziel: zielAusIpc(args.harness, args.ort) })));
|
|
192
258
|
// Nur die Herkunft, ohne die Quellen zu lesen: der Katalog daneben tut das schon.
|
|
193
259
|
ipcMain.handle('hmgr:quellen', async () => auffangen(async () => (await anwendungsfaelle.quellen()).map((quelle) => ({
|
|
194
260
|
art: quelle.art,
|
|
@@ -247,7 +313,25 @@ function registriereIpc() {
|
|
|
247
313
|
lokal: args.ort.art === 'arbeitsverzeichnis',
|
|
248
314
|
})));
|
|
249
315
|
ipcMain.handle('hmgr:eintragLoeschen', async (_event, args) => auffangen(async () => anwendungsfaelle.eintragLoeschen({ ziel: zielAusIpc(args.harness, args.ort), name: args.name })));
|
|
250
|
-
ipcMain.handle('hmgr:aktualisieren', async (_event, args) => auffangen(async () =>
|
|
316
|
+
ipcMain.handle('hmgr:aktualisieren', async (_event, args) => auffangen(async () => {
|
|
317
|
+
if (args?.ausloeser !== undefined)
|
|
318
|
+
letzterAusloeser = args.ausloeser;
|
|
319
|
+
return anwendungsfaelle.aktualisieren(args);
|
|
320
|
+
}));
|
|
321
|
+
// Changelog der letzten Läufe (E80), neueste zuerst.
|
|
322
|
+
ipcMain.handle('hmgr:verlauf', async () => auffangen(async () => anwendungsfaelle.verlauf()));
|
|
323
|
+
// E77: der Prüfen-Knopf stößt zusätzlich zum Quellen-Lauf die Prüfung auf hmgr selbst an. Kein
|
|
324
|
+
// `handle`, sondern `send`/`on`: der Renderer wartet auf nichts, das Ergebnis kommt später von
|
|
325
|
+
// sich aus über 'hmgr:selbstUpdate'.
|
|
326
|
+
ipcMain.on('hmgr:selbstUpdatePruefen', () => selbstUpdatePruefen());
|
|
327
|
+
// Neustart-Knopf an der GUI-Update-Zeile (E80): installiert das geladene Paket sofort, statt auf
|
|
328
|
+
// das nächste Beenden zu warten. Im Smoke-Test ein No-op — dort läuft `updatePruefen` ohnehin nie,
|
|
329
|
+
// der Knopf bleibt verborgen, aber ein Absturz durch ein fehlendes Update darf trotzdem nie sein.
|
|
330
|
+
ipcMain.on('hmgr:neuStarten', () => {
|
|
331
|
+
if (smoketest)
|
|
332
|
+
return;
|
|
333
|
+
autoUpdater.quitAndInstall();
|
|
334
|
+
});
|
|
251
335
|
ipcMain.handle('hmgr:arbeitsverzeichnisHinzufuegen', async (_event, pfad) => auffangen(async () => anwendungsfaelle.arbeitsverzeichnisHinzufuegen(pfad)));
|
|
252
336
|
ipcMain.handle('hmgr:arbeitsverzeichnisVergessen', async (_event, pfad) => auffangen(async () => anwendungsfaelle.arbeitsverzeichnisVergessen(pfad)));
|
|
253
337
|
// Nativer Verzeichnis-Dialog fürs Pfadfeld in „Verzeichnis hinzufügen" (A1). Im Smoke-Test darf
|
|
@@ -270,6 +354,14 @@ function registriereIpc() {
|
|
|
270
354
|
ipcMain.handle('hmgr:harnessAktivieren', async (_event, name) => auffangen(async () => anwendungsfaelle.harnessAktivieren(name)));
|
|
271
355
|
ipcMain.handle('hmgr:harnessDeaktivieren', async (_event, name) => auffangen(async () => anwendungsfaelle.harnessDeaktivieren(name)));
|
|
272
356
|
ipcMain.handle('hmgr:saeubern', async (_event, name) => auffangen(async () => anwendungsfaelle.saeubern(name)));
|
|
357
|
+
// Homedirs je Harness (E78): zeigen, das primäre umstellen (F89), zusätzliche in einer
|
|
358
|
+
// WSL-Distro eintragen und vergessen (F87). Die Distro-Liste kommt aus `wsl -l -q`.
|
|
359
|
+
ipcMain.handle('hmgr:harnessHeime', async () => auffangen(async () => anwendungsfaelle.harnessHeime()));
|
|
360
|
+
ipcMain.handle('hmgr:wslDistros', async () => auffangen(async () => anwendungsfaelle.wslDistros()));
|
|
361
|
+
ipcMain.handle('hmgr:harnessHeimSetzen', async (_event, args) => auffangen(async () => anwendungsfaelle.harnessHeimSetzen(args.harness, args.pfad)));
|
|
362
|
+
ipcMain.handle('hmgr:harnessHeimZuruecksetzen', async (_event, harness) => auffangen(async () => anwendungsfaelle.harnessHeimZuruecksetzen(harness)));
|
|
363
|
+
ipcMain.handle('hmgr:harnessHeimHinzufuegen', async (_event, args) => auffangen(async () => anwendungsfaelle.harnessHeimHinzufuegen(args.harness, args.adresse)));
|
|
364
|
+
ipcMain.handle('hmgr:harnessHeimEntfernen', async (_event, args) => auffangen(async () => anwendungsfaelle.harnessHeimEntfernen(args.harness, args.adresse)));
|
|
273
365
|
// Verwalteter Hermes-Skill-Ordner (E56): Zeigen kommt schon über hmgr:istZustand, hier nur Ändern.
|
|
274
366
|
ipcMain.handle('hmgr:skillOrdnerFestlegen', async (_event, args) => auffangen(async () => anwendungsfaelle.skillOrdnerFestlegen(zielAusIpc(args.harness, args.ort), args.pfad)));
|
|
275
367
|
ipcMain.handle('hmgr:skillOrdnerEntfernen', async (_event, args) => auffangen(async () => anwendungsfaelle.skillOrdnerEntfernen(zielAusIpc(args.harness, args.ort))));
|
|
@@ -386,6 +478,11 @@ function fensterOeffnen() {
|
|
|
386
478
|
minWidth: 860,
|
|
387
479
|
minHeight: 560,
|
|
388
480
|
backgroundColor: '#E3E7E0', // --platte, verhindert einen weißen Blitz beim Start
|
|
481
|
+
// E72: der Fenstertitel nennt die Version, damit im Feld erkennbar ist, welcher Stand läuft.
|
|
482
|
+
// Nicht `app.getVersion()`: das liefert nur im gepackten Bundle die Paketversion, beim Start
|
|
483
|
+
// über `electron dist/gui/main.js` (npm run gui, Smoke-Test) dagegen die Electron-Version —
|
|
484
|
+
// dort liegt neben dem Hauptskript keine package.json (gemessen: 44.0.0 statt 0.0.6).
|
|
485
|
+
title: `hmgr ${paketVersion()}`,
|
|
389
486
|
// Im Smoke-Test bleibt das Fenster unsichtbar: der Renderer lädt trotzdem vollständig, der
|
|
390
487
|
// DOM-Auszug kommt per executeJavaScript. So klaut ein automatisierter Lauf niemandem den
|
|
391
488
|
// Eingabefokus und öffnet kein sichtbares Fenster.
|
|
@@ -400,6 +497,9 @@ function fensterOeffnen() {
|
|
|
400
497
|
hauptfenster.on('closed', () => {
|
|
401
498
|
hauptfenster = null;
|
|
402
499
|
});
|
|
500
|
+
// Der <title> der index.html setzt den Fenstertitel beim Laden sonst zurück und überschriebe die
|
|
501
|
+
// Option oben (E72). Der DOM-Titel bleibt dabei, wie er ist, nur das Fenster folgt ihm nicht mehr.
|
|
502
|
+
hauptfenster.on('page-title-updated', (ereignis) => ereignis.preventDefault());
|
|
403
503
|
// Renderer-Konsole im Main-Log, für Fehlersuche und den Smoke-Test.
|
|
404
504
|
hauptfenster.webContents.on('console-message', (event) => {
|
|
405
505
|
console.log(`[renderer] ${event.message}`);
|
|
@@ -417,6 +517,25 @@ function fensterOeffnen() {
|
|
|
417
517
|
var harnessSchalter = Array.from(document.querySelectorAll('#harnessliste input[type=checkbox]'))
|
|
418
518
|
.map((i) => i.dataset.harness + ':' + i.checked)
|
|
419
519
|
.join(',');
|
|
520
|
+
// Homedir-Zeilen unter den Schaltern (E78): je Harness eine Zeile mit dem Pfad, dazu
|
|
521
|
+
// der Ändern-Knopf. Der Weg zu einem WSL-Homedir steht auf dem Mac bewusst nicht da.
|
|
522
|
+
var heimZeilen = Array.from(document.querySelectorAll('#harnessliste .heimzeile'))
|
|
523
|
+
.map((z) => z.textContent.replace(/\\s+/g, ' ').trim())
|
|
524
|
+
.join(' | ');
|
|
525
|
+
var heimWslKnopfVorhanden = Array.from(document.querySelectorAll('#harnessliste .heime > button'))
|
|
526
|
+
.some((b) => b.textContent.indexOf('WSL') !== -1);
|
|
527
|
+
// Der Pfad-Dialog lässt sich öffnen und per Abbrechen wieder schließen, ohne IPC-Lauf.
|
|
528
|
+
var heimPfadFehler = null;
|
|
529
|
+
var dHeimPfadOffenNachOeffnen = false;
|
|
530
|
+
var dHeimPfadOffenNachAbbrechen = true;
|
|
531
|
+
try {
|
|
532
|
+
document.querySelector('#harnessliste .heimknoepfe button')?.click();
|
|
533
|
+
dHeimPfadOffenNachOeffnen = document.getElementById('d-heim-pfad')?.open === true;
|
|
534
|
+
document.querySelector('#d-heim-pfad button[value="abbruch"]')?.click();
|
|
535
|
+
dHeimPfadOffenNachAbbrechen = document.getElementById('d-heim-pfad')?.open === true;
|
|
536
|
+
} catch (fehler) {
|
|
537
|
+
heimPfadFehler = String(fehler);
|
|
538
|
+
}
|
|
420
539
|
// Quellen-Abschnitt der Einstellungen (E54): Knopf und Dialoge vorhanden, der
|
|
421
540
|
// Hinzufügen-Dialog lässt sich öffnen und per Abbrechen ohne Fehler wieder schließen.
|
|
422
541
|
// Kein echter Quellen-Lauf: der Abbrechen-Knopf löst keinen IPC-Aufruf aus.
|
|
@@ -477,6 +596,17 @@ function fensterOeffnen() {
|
|
|
477
596
|
} catch (fehler) {
|
|
478
597
|
quelleUmbenennenFehler = String(fehler);
|
|
479
598
|
}
|
|
599
|
+
// Leertext der Quellenliste (E75): der Smoke-Lauf hat immer eine Quelle, den leeren
|
|
600
|
+
// Zustand erreicht er nie. Geprüft wird darum die Regel, an der er zerbrach — ein
|
|
601
|
+
// <li class="leerhinweis"> in genau dieser Liste ist vom Grid ausgenommen und nimmt
|
|
602
|
+
// die ganze Breite, statt in die 20px-Icon-Spalte zu fallen und Wort für Wort
|
|
603
|
+
// umzubrechen. Die Probe wird sofort wieder entfernt, die Liste bleibt unberührt.
|
|
604
|
+
var quellenLeerProbe = document.createElement('li');
|
|
605
|
+
quellenLeerProbe.className = 'leerhinweis';
|
|
606
|
+
quellenLeerProbe.textContent = 'Probe';
|
|
607
|
+
document.getElementById('quellenliste')?.appendChild(quellenLeerProbe);
|
|
608
|
+
var quellenLeerDisplay = getComputedStyle(quellenLeerProbe).display;
|
|
609
|
+
quellenLeerProbe.remove();
|
|
480
610
|
document.getElementById('d-einstellungen')?.close();
|
|
481
611
|
// Konfigzeile am Hermes-Ziel (E56): der verwaltete Pfad steht in der Karte, auch
|
|
482
612
|
// ungeklappt (hidden ändert textContent nicht). Dazu der Festlegen-Weg am unmanaged
|
|
@@ -496,6 +626,52 @@ function fensterOeffnen() {
|
|
|
496
626
|
} catch (fehler) {
|
|
497
627
|
skillOrdnerFehler = String(fehler);
|
|
498
628
|
}
|
|
629
|
+
// E77: der Ort für die Update-Hinweise steht in der Seitenleiste, direkt vor dem
|
|
630
|
+
// Einstellungen-Knopf, und ist im Ruhezustand leer und verborgen. Der Smoke-Lauf
|
|
631
|
+
// prüft nie nach — er stößt weder Updater noch CLI-Prüfung an, es gibt nichts zu
|
|
632
|
+
// melden. Geprüft wird darum nur, dass der Ort da ist und stumm bleibt.
|
|
633
|
+
var selbstupdateEl = document.getElementById('selbstupdate');
|
|
634
|
+
var selbstupdateVorhanden = selbstupdateEl !== null;
|
|
635
|
+
// Nicht nur das Attribut, sondern das tatsächliche Rendern: die Klasse setzt
|
|
636
|
+
// display:flex, und eine Autoren-Regel überstimmt das hidden-Attribut des Browsers —
|
|
637
|
+
// ohne die eigene hidden-Regel in styles.css stünde hier ein leerer Kasten samt
|
|
638
|
+
// seinem auto-Rand. Das Attribut allein würde den Fehler nicht zeigen.
|
|
639
|
+
var selbstupdateVerborgen =
|
|
640
|
+
selbstupdateEl?.hidden === true && getComputedStyle(selbstupdateEl).display === 'none';
|
|
641
|
+
// Seit E80 steckt im Kasten auch der Neustart-Knopf, sein Text bleibt aber im DOM
|
|
642
|
+
// stehen, auch verborgen (anders als bei display:none zählt hidden nicht für
|
|
643
|
+
// textContent) — „leer" meint deshalb: kein sichtbarer (nicht verborgener) Kindknoten
|
|
644
|
+
// trägt Text, nicht der rohe textContent des ganzen Kastens.
|
|
645
|
+
var selbstupdateLeer = Array.from(selbstupdateEl?.children ?? [])
|
|
646
|
+
.filter((kind) => !kind.hidden)
|
|
647
|
+
.every((kind) => (kind.textContent ?? '').trim() === '');
|
|
648
|
+
// Er sitzt zwischen Stand-Zeile und Einstellungen-Knopf: der Knopf ist sein nächster
|
|
649
|
+
// Geschwister-Knoten, sonst stünde der Hinweis irgendwo anders in der Leiste.
|
|
650
|
+
var selbstupdateVorEinstellungen = selbstupdateEl?.nextElementSibling?.id === 'einstellungen';
|
|
651
|
+
// E80: Neustart-Knopf an der GUI-Update-Zeile, vorhanden und im Ruhezustand verborgen
|
|
652
|
+
// (der Smoke-Lauf löst nie ein update-downloaded aus, das ihn einblenden würde).
|
|
653
|
+
var selbstupdateNeustartVorhanden = document.getElementById('selbstupdate-neustart') !== null;
|
|
654
|
+
var selbstupdateNeustartVerborgen = document.getElementById('selbstupdate-neustart')?.hidden === true;
|
|
655
|
+
// E80: die Seitenleiste zeigt nur noch die Stand-Zeile und ein „›" zum Öffnen der
|
|
656
|
+
// Ansicht, kein „Prüfen"-Wort mehr und kein eigener Lauf am Standknopf-Klick. In der
|
|
657
|
+
// Ansicht selbst ersetzt ein runder Kringel den früheren großen Knopf, und der alte
|
|
658
|
+
// Sitzungsbericht (#bericht) ist ganz weg, ersetzt durch das Changelog (#verlauf).
|
|
659
|
+
var standknopfText = document.getElementById('standknopf')?.textContent ?? null;
|
|
660
|
+
var updatestartKringel = document.getElementById('updatestart')?.classList.contains('kringel') === true;
|
|
661
|
+
var updatestartLaeuft = document.getElementById('updatestart')?.classList.contains('laeuft') === true;
|
|
662
|
+
var berichtVorhanden = document.getElementById('bericht') !== null;
|
|
663
|
+
var verlaufVorhanden = document.getElementById('verlauf') !== null;
|
|
664
|
+
// Der Standknopf öffnet nur noch die Ansicht (kein Lauf mehr an diesem Klick), die
|
|
665
|
+
// Ansicht lädt ihren Verlauf dabei aus der Datei — das ist ein IPC-Aufruf, also kurz
|
|
666
|
+
// nachfassen, bis das Ergebnis im DOM steht (Muster wie bei mcpWahlOeffnen oben).
|
|
667
|
+
document.getElementById('standknopf')?.click();
|
|
668
|
+
var verlaufBloecke = 0;
|
|
669
|
+
for (var wartezeit = 0; wartezeit < 2000; wartezeit += 100) {
|
|
670
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
671
|
+
verlaufBloecke = document.querySelectorAll('#verlauf .block').length;
|
|
672
|
+
if (verlaufBloecke > 0) break;
|
|
673
|
+
}
|
|
674
|
+
var verlaufText = document.getElementById('verlauf')?.textContent?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
499
675
|
// E60: Beschreibung in der Ziel-Tabelle (nicht nur im Katalog), „manuell" entfällt,
|
|
500
676
|
// Zahnrad statt Sonne am Einstellungen-Knopf.
|
|
501
677
|
var zielBeschreibung = document.querySelector('#zielliste .linkliste .was')?.textContent ?? null;
|
|
@@ -507,6 +683,60 @@ function fensterOeffnen() {
|
|
|
507
683
|
// Katalog (E40 R3, R5): Beschreibung aus dem Fixture muss in der Skill-Zeile stehen.
|
|
508
684
|
document.querySelector('[data-ansicht="katalog"]')?.click();
|
|
509
685
|
var katalogBeschreibung = document.querySelector('#katalog .zeile .beschreibung')?.textContent ?? null;
|
|
686
|
+
// E70/F70=O1: die Herkunftszeile trägt hinter dem Ref das Änderungsdatum des Skills.
|
|
687
|
+
var katalogHerkunft = document.querySelector('#katalog .zeile .herkunft')?.textContent
|
|
688
|
+
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
689
|
+
// E79: Sortier-Gruppe der Skills-Rubrik. Erst „Zuletzt geändert" klicken und die
|
|
690
|
+
// sichtbaren Änderungsdaten der ersten fünf Zeilen samt Knopfstand festhalten, dann
|
|
691
|
+
// zurück auf „Name", damit die obigen Prüfungen (erste Zeile) unverändert gelten.
|
|
692
|
+
document.getElementById('sortnach-datum')?.click();
|
|
693
|
+
var katalogSortierungGeaendert = Array.from(document.querySelectorAll('#katalog .zeile:not([hidden])'))
|
|
694
|
+
.slice(0, 5)
|
|
695
|
+
.map((li) => li.dataset.geaendert ?? '')
|
|
696
|
+
.join('|');
|
|
697
|
+
var katalogSortierungDatumGedrueckt = document.getElementById('sortnach-datum')?.getAttribute('aria-pressed') ?? null;
|
|
698
|
+
var katalogSortierungNameGedrueckt = document.getElementById('sortnach-name')?.getAttribute('aria-pressed') ?? null;
|
|
699
|
+
// E81: Richtung und Pfeil nach dem ersten Klick. Standardrichtung von „datum" ist
|
|
700
|
+
// 'ab' (jüngste zuerst, wie E79), der Name-Chip ist dabei inaktiv und trägt keinen
|
|
701
|
+
// Pfeil.
|
|
702
|
+
var katalogSortierungDatumRichtung = document.getElementById('sortnach-datum')?.getAttribute('data-richtung') ?? null;
|
|
703
|
+
var katalogSortierungDatumPfeil = document.querySelector('#sortnach-datum .sortpfeil')?.textContent ?? null;
|
|
704
|
+
var katalogSortierungNamePfeil = document.querySelector('#sortnach-name .sortpfeil')?.textContent ?? null;
|
|
705
|
+
// E81: zweiter Klick auf denselben Chip kehrt nur die Richtung um (Schlüssel bleibt
|
|
706
|
+
// „datum", jetzt älteste zuerst).
|
|
707
|
+
document.getElementById('sortnach-datum')?.click();
|
|
708
|
+
var katalogSortierungGeaendertAuf = Array.from(document.querySelectorAll('#katalog .zeile:not([hidden])'))
|
|
709
|
+
.slice(0, 5)
|
|
710
|
+
.map((li) => li.dataset.geaendert ?? '')
|
|
711
|
+
.join('|');
|
|
712
|
+
var katalogSortierungDatumRichtungAuf = document.getElementById('sortnach-datum')?.getAttribute('data-richtung') ?? null;
|
|
713
|
+
var katalogSortierungDatumPfeilAuf = document.querySelector('#sortnach-datum .sortpfeil')?.textContent ?? null;
|
|
714
|
+
document.getElementById('sortnach-name')?.click();
|
|
715
|
+
// Reiner Textknoten des Namens (erstes Kind ist das Quellen-Icon, ein etwaiges
|
|
716
|
+
// Markenfeld käme als weiteres Element danach) statt h3.textContent, sonst würde
|
|
717
|
+
// eine Marke direkt an den Namen geklebt.
|
|
718
|
+
var ersterKatalogH3 = document.querySelector('#katalog .zeile:not([hidden]) h3');
|
|
719
|
+
var katalogErsterNachName = ersterKatalogH3
|
|
720
|
+
? Array.from(ersterKatalogH3.childNodes).find((n) => n.nodeType === 3)?.textContent ?? null
|
|
721
|
+
: null;
|
|
722
|
+
// E81: Chipwechsel (anderer Schlüssel als zuvor „datum") setzt dessen Standardrichtung,
|
|
723
|
+
// hier 'auf' für Name — die bisherige, unveränderte Reihenfolge (A→Z).
|
|
724
|
+
var katalogNamenAuf = Array.from(document.querySelectorAll('#katalog .zeile:not([hidden]) h3'))
|
|
725
|
+
.map((h3) => Array.from(h3.childNodes).find((n) => n.nodeType === 3)?.textContent ?? '')
|
|
726
|
+
.join('|');
|
|
727
|
+
var katalogSortierungNameRichtungAuf = document.getElementById('sortnach-name')?.getAttribute('data-richtung') ?? null;
|
|
728
|
+
var katalogSortierungNamePfeilAuf = document.querySelector('#sortnach-name .sortpfeil')?.textContent ?? null;
|
|
729
|
+
// E81: zweiter Klick auf „Name" kehrt die Richtung um (Z→A).
|
|
730
|
+
document.getElementById('sortnach-name')?.click();
|
|
731
|
+
var katalogNamenAb = Array.from(document.querySelectorAll('#katalog .zeile:not([hidden]) h3'))
|
|
732
|
+
.map((h3) => Array.from(h3.childNodes).find((n) => n.nodeType === 3)?.textContent ?? '')
|
|
733
|
+
.join('|');
|
|
734
|
+
var katalogSortierungNameRichtungAb = document.getElementById('sortnach-name')?.getAttribute('data-richtung') ?? null;
|
|
735
|
+
var katalogSortierungNamePfeilAb = document.querySelector('#sortnach-name .sortpfeil')?.textContent ?? null;
|
|
736
|
+
// E81: dritter Klick stellt name/auf wieder her, damit die restliche Sonde (unten)
|
|
737
|
+
// wie gewohnt gegen die Standardsortierung prüft.
|
|
738
|
+
document.getElementById('sortnach-name')?.click();
|
|
739
|
+
var katalogSortierungNameRichtungNachDrittemKlick = document.getElementById('sortnach-name')?.getAttribute('data-richtung') ?? null;
|
|
510
740
|
// Besonderheiten aus dem Frontmatter (E55): Marken an der Zeile, Details erst nach
|
|
511
741
|
// einem Klick auf den Aufklapp-Knopf.
|
|
512
742
|
var katalogMarken = document.querySelector('#katalog .zeile .marken')?.textContent
|
|
@@ -524,12 +754,45 @@ function fensterOeffnen() {
|
|
|
524
754
|
var dskKnopf = Array.from(document.querySelectorAll('button'))
|
|
525
755
|
.find((b) => b.textContent === 'Skills hinzufügen');
|
|
526
756
|
var dskMarken = null;
|
|
757
|
+
// Bundle-Sektion im selben Dialog (E69, F68): sie steht dort, wo die Entscheidung
|
|
758
|
+
// fällt, was ins Ziel kommt, und zeigt je Bundle Beschreibung und Herkunft.
|
|
759
|
+
var dskBundleBoxSichtbar = false;
|
|
760
|
+
var dskBundleText = null;
|
|
527
761
|
if (dskKnopf) {
|
|
528
762
|
dskKnopf.click();
|
|
529
763
|
dskMarken = document.querySelector('#dsk-liste .wahlzeile .marken')?.textContent
|
|
530
764
|
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
765
|
+
dskBundleBoxSichtbar = document.getElementById('dsk-bundlebox')?.hidden === false;
|
|
766
|
+
dskBundleText = document.querySelector('#dsk-bundles .wahlzeile')?.textContent
|
|
767
|
+
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
531
768
|
document.getElementById('d-skills')?.close();
|
|
532
769
|
}
|
|
770
|
+
// Verlinken-Dialog „Wohin?" (E76): er bietet nur Ziele aktiver Harnesses an. Der Text
|
|
771
|
+
// der Wahlliste sagt, welche das sind; ist gar keines aktiv, steht dort der Leerhinweis.
|
|
772
|
+
var dzZieleText = null;
|
|
773
|
+
var dzKnopf = Array.from(document.querySelectorAll('#katalog .zeile button'))
|
|
774
|
+
.find((b) => b.textContent === 'Verlinken');
|
|
775
|
+
if (dzKnopf) {
|
|
776
|
+
dzKnopf.click();
|
|
777
|
+
dzZieleText = document.getElementById('dz-ziele')?.textContent
|
|
778
|
+
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
779
|
+
document.getElementById('d-ziel')?.close();
|
|
780
|
+
}
|
|
781
|
+
// Bundle-Gruppe an der Ziel-Karte (E69): Kopf mit Name, Quelle und Entfernen, darunter
|
|
782
|
+
// die Zeilen der Mitglieder — sie stehen in der Gruppe, nicht in der allgemeinen Liste.
|
|
783
|
+
var bundleGruppeText = document.querySelector('#zielliste .bundlegruppe .bundlekopf')?.textContent
|
|
784
|
+
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
785
|
+
var bundleGruppeZeilen = Array.from(document.querySelectorAll('#zielliste .bundlegruppe .linkliste .name'))
|
|
786
|
+
.map((n) => n.textContent.trim()).join(',');
|
|
787
|
+
// Entfernen an der Einzelzeile innerhalb der Gruppe sagt, dass das Bundle zerbricht.
|
|
788
|
+
var bundleZerbrichtText = null;
|
|
789
|
+
try {
|
|
790
|
+
document.querySelector('#zielliste .bundlegruppe .linkliste .knopf')?.click();
|
|
791
|
+
bundleZerbrichtText = document.getElementById('dx-text')?.textContent ?? null;
|
|
792
|
+
document.querySelector('#d-entfernen button[value="nein"]')?.click();
|
|
793
|
+
} catch (fehler) {
|
|
794
|
+
bundleZerbrichtText = String(fehler);
|
|
795
|
+
}
|
|
533
796
|
// MCP-Ansicht (E61-Folgerunde, F46: abgespeckt zum reinen Katalog): Navigationsreihenfolge
|
|
534
797
|
// Ziele, MCP, Plugins, Skills, Karten mit Name/Beschreibung/Homepage-Link, F39-Warnzeile,
|
|
535
798
|
// Einhängen-Dialog öffnen/abbrechen ohne IPC-Schreibaktion. Orte-Liste und Fremdkasten
|
|
@@ -640,12 +903,33 @@ function fensterOeffnen() {
|
|
|
640
903
|
?.replace(/\\s+/g, ' ').trim() ?? null;
|
|
641
904
|
var kundeXMcpZeilenKnopfZahl = kundeXKarte?.querySelectorAll('.mcpkartezeilen button').length ?? 0;
|
|
642
905
|
var kundeXMcpKnopfVorhanden = mcpHinzuKnopfAn(kundeXKarte) !== undefined;
|
|
906
|
+
// Kartenkopf-Zahl (E71): der Kopf zählt Skills, gebündelt wie der Körper zeilt. An der
|
|
907
|
+
// Verzeichnis-Karte kunde-x hängen dieselben zwei Skills an zwei Harnesses, eine Summe
|
|
908
|
+
// über die Abschnitte ergäbe hier 4.
|
|
909
|
+
var kopfZahlVon = function (karte) {
|
|
910
|
+
return karte?.querySelector('.zielkopf .zahl')?.textContent?.trim() ?? null;
|
|
911
|
+
};
|
|
912
|
+
var kundeXKopfZahl = kopfZahlVon(kundeXKarte);
|
|
913
|
+
var hermesKopfZahl = kopfZahlVon(hermesKarte);
|
|
643
914
|
var agentsKarte = karteMitKopftext('Agents · Home');
|
|
644
915
|
// Ohne diese Zusicherung wären die beiden Abwesenheits-Prüfungen darunter leer erfüllt,
|
|
645
916
|
// sobald die Karte selbst aus der Liste fiele: „kein Abschnitt" hieße dann nur „keine Karte".
|
|
646
917
|
var agentsKarteVorhanden = agentsKarte !== undefined;
|
|
647
918
|
var agentsMcpAbschnittVorhanden = (agentsKarte?.querySelector('.mcpkarteabschnitt') ?? null) !== null;
|
|
648
919
|
var agentsMcpKnopfVorhanden = mcpHinzuKnopfAn(agentsKarte) !== undefined;
|
|
920
|
+
// Warnzahl am Kartenkopf (E75): das Dreieck nennt die Zahl der Auslöser. Im Fixture
|
|
921
|
+
// liegt ein fremder Ordner im Hermes-Home-Ziel (1) und drei im Verzeichnis kunde-x,
|
|
922
|
+
// ungleich auf die zwei Harness-Teile verteilt — dort ist die Zahl die Summe. Die
|
|
923
|
+
// agents-Karte hat keinen Auslöser und darum gar kein Dreieck.
|
|
924
|
+
var warnungVon = function (karte) { return karte?.querySelector('.zielkopf .warnung') ?? null; };
|
|
925
|
+
var warnzahlVon = function (karte) {
|
|
926
|
+
return warnungVon(karte)?.querySelector('.warnzahl')?.textContent?.trim() ?? null;
|
|
927
|
+
};
|
|
928
|
+
var hermesWarnzahl = warnzahlVon(hermesKarte);
|
|
929
|
+
var hermesWarnlabel = warnungVon(hermesKarte)?.getAttribute('aria-label') ?? null;
|
|
930
|
+
var kundeXWarnzahl = warnzahlVon(kundeXKarte);
|
|
931
|
+
var kundeXWarnlabel = warnungVon(kundeXKarte)?.getAttribute('aria-label') ?? null;
|
|
932
|
+
var agentsWarnungVorhanden = warnungVon(agentsKarte) !== null;
|
|
649
933
|
// Dialog „MCP hinzufügen" (E47-Muster): öffnen an der Hermes-Karte und über Abbrechen
|
|
650
934
|
// wieder schließen, ohne IPC-Schreibaktion (mcpEinhaengen liefe erst nach „Einhängen").
|
|
651
935
|
var dmhFehler = null;
|
|
@@ -673,17 +957,46 @@ function fensterOeffnen() {
|
|
|
673
957
|
meldung: document.getElementById('meldungtext')?.textContent,
|
|
674
958
|
einstellungenOffen,
|
|
675
959
|
harnessSchalter,
|
|
960
|
+
heimZeilen,
|
|
961
|
+
heimWslKnopfVorhanden,
|
|
962
|
+
heimPfadFehler,
|
|
963
|
+
dHeimPfadOffenNachOeffnen,
|
|
964
|
+
dHeimPfadOffenNachAbbrechen,
|
|
676
965
|
zielBeschreibung,
|
|
677
966
|
zielMarken,
|
|
678
967
|
zielManuellVorhanden,
|
|
679
968
|
zahnradVorhanden,
|
|
680
969
|
katalogBeschreibung,
|
|
970
|
+
katalogHerkunft,
|
|
971
|
+
katalogSortierungGeaendert,
|
|
972
|
+
katalogSortierungDatumGedrueckt,
|
|
973
|
+
katalogSortierungNameGedrueckt,
|
|
974
|
+
katalogSortierungDatumRichtung,
|
|
975
|
+
katalogSortierungDatumPfeil,
|
|
976
|
+
katalogSortierungNamePfeil,
|
|
977
|
+
katalogSortierungGeaendertAuf,
|
|
978
|
+
katalogSortierungDatumRichtungAuf,
|
|
979
|
+
katalogSortierungDatumPfeilAuf,
|
|
980
|
+
katalogErsterNachName,
|
|
981
|
+
katalogNamenAuf,
|
|
982
|
+
katalogSortierungNameRichtungAuf,
|
|
983
|
+
katalogSortierungNamePfeilAuf,
|
|
984
|
+
katalogNamenAb,
|
|
985
|
+
katalogSortierungNameRichtungAb,
|
|
986
|
+
katalogSortierungNamePfeilAb,
|
|
987
|
+
katalogSortierungNameRichtungNachDrittemKlick,
|
|
681
988
|
katalogMarken,
|
|
682
989
|
katalogDetailsZu,
|
|
683
990
|
katalogAufgeklappt,
|
|
684
991
|
katalogDetails,
|
|
685
992
|
katalogMarkenWarnend,
|
|
686
993
|
dskMarken,
|
|
994
|
+
dskBundleBoxSichtbar,
|
|
995
|
+
dskBundleText,
|
|
996
|
+
dzZieleText,
|
|
997
|
+
bundleGruppeText,
|
|
998
|
+
bundleGruppeZeilen,
|
|
999
|
+
bundleZerbrichtText,
|
|
687
1000
|
interGeladen: document.fonts.check('400 16px Inter'),
|
|
688
1001
|
monoGeladen: document.fonts.check('500 16px "JetBrains Mono"'),
|
|
689
1002
|
quelleneuVorhanden,
|
|
@@ -733,6 +1046,14 @@ function fensterOeffnen() {
|
|
|
733
1046
|
kundeXMcpText,
|
|
734
1047
|
kundeXMcpZeilenKnopfZahl,
|
|
735
1048
|
kundeXMcpKnopfVorhanden,
|
|
1049
|
+
kundeXKopfZahl,
|
|
1050
|
+
hermesKopfZahl,
|
|
1051
|
+
hermesWarnzahl,
|
|
1052
|
+
hermesWarnlabel,
|
|
1053
|
+
kundeXWarnzahl,
|
|
1054
|
+
kundeXWarnlabel,
|
|
1055
|
+
agentsWarnungVorhanden,
|
|
1056
|
+
quellenLeerDisplay,
|
|
736
1057
|
agentsKarteVorhanden,
|
|
737
1058
|
agentsMcpAbschnittVorhanden,
|
|
738
1059
|
agentsMcpKnopfVorhanden,
|
|
@@ -740,22 +1061,39 @@ function fensterOeffnen() {
|
|
|
740
1061
|
dMcpHinzuOffenNachAbbrechen,
|
|
741
1062
|
dmhListeText,
|
|
742
1063
|
dmhFehler,
|
|
1064
|
+
selbstupdateVorhanden,
|
|
1065
|
+
selbstupdateVerborgen,
|
|
1066
|
+
selbstupdateLeer,
|
|
1067
|
+
selbstupdateVorEinstellungen,
|
|
1068
|
+
selbstupdateNeustartVorhanden,
|
|
1069
|
+
selbstupdateNeustartVerborgen,
|
|
1070
|
+
standknopfText,
|
|
1071
|
+
updatestartKringel,
|
|
1072
|
+
updatestartLaeuft,
|
|
1073
|
+
berichtVorhanden,
|
|
1074
|
+
verlaufVorhanden,
|
|
1075
|
+
verlaufText,
|
|
1076
|
+
verlaufBloecke,
|
|
743
1077
|
});
|
|
744
1078
|
})()`)
|
|
745
1079
|
.then((auszug) => console.log(`[smoke] DOM-Auszug: ${auszug}`))
|
|
1080
|
+
// E72: der Fenstertitel steht nicht im DOM (dort steht weiter der <title> der
|
|
1081
|
+
// index.html), er lässt sich nur hier im Main-Prozess ablesen.
|
|
1082
|
+
.then(() => console.log(`[smoke] Fenster: ${JSON.stringify({ fenstertitel: fenster.getTitle() })}`))
|
|
746
1083
|
.finally(() => app.quit());
|
|
747
1084
|
}, 3000);
|
|
748
1085
|
});
|
|
749
1086
|
}
|
|
750
1087
|
// Selbsttätiges Aktualisieren (E44): einmal, sobald der Renderer bereit ist, danach alle sechs
|
|
751
1088
|
// Stunden, solange das Fenster offen bleibt. Im Smoke-Test nie, sonst würde ein automatisierter
|
|
752
|
-
// Lauf einen echten Git-Fetch anstoßen.
|
|
1089
|
+
// Lauf einen echten Git-Fetch anstoßen. Der Auslöser (E80) geht mit, damit der Verlauf zeigt, ob
|
|
1090
|
+
// ein Fund vom Start, vom Intervall oder einem Klick kam.
|
|
753
1091
|
if (!smoketest) {
|
|
754
1092
|
const fenster = hauptfenster;
|
|
755
1093
|
fenster.webContents.once('did-finish-load', () => {
|
|
756
|
-
fenster.webContents.send('hmgr:aktualisierenAnstossen');
|
|
1094
|
+
fenster.webContents.send('hmgr:aktualisierenAnstossen', 'start');
|
|
757
1095
|
});
|
|
758
|
-
const intervall = setInterval(() => fenster.webContents.send('hmgr:aktualisierenAnstossen'), SECHS_STUNDEN_MS);
|
|
1096
|
+
const intervall = setInterval(() => fenster.webContents.send('hmgr:aktualisierenAnstossen', 'intervall'), SECHS_STUNDEN_MS);
|
|
759
1097
|
fenster.on('closed', () => clearInterval(intervall));
|
|
760
1098
|
}
|
|
761
1099
|
// Die GUI erfährt von Änderungen außerhalb ihrer selbst (E62): Fokus-Reload und Datei-Watcher.
|
|
@@ -777,6 +1115,107 @@ function fensterOeffnen() {
|
|
|
777
1115
|
}
|
|
778
1116
|
void hauptfenster.loadFile(path.join(hierVerzeichnis, 'renderer', 'index.html'));
|
|
779
1117
|
}
|
|
1118
|
+
const { autoUpdater } = electronUpdater;
|
|
1119
|
+
/** Die Listener hängen am Modul, nicht am Aufruf: `updatePruefen` läuft beim Start und je Prüfen-Klick. */
|
|
1120
|
+
let updaterVerdrahtet = false;
|
|
1121
|
+
/**
|
|
1122
|
+
* Autoupdate der Windows-GUI (E73). Der Feed sind die Gitea-Releases des hmgr-Repos: die
|
|
1123
|
+
* `publish`-Angabe im build-Block der package.json erzeugt beim Bauen die `latest.yml` und legt
|
|
1124
|
+
* `app-update.yml` ins Paket, aus der electron-updater die Feed-URL liest.
|
|
1125
|
+
*
|
|
1126
|
+
* `checkForUpdatesAndNotify()` heißt: im Hintergrund prüfen, ein gefundenes Update herunterladen,
|
|
1127
|
+
* eine Windows-Benachrichtigung zeigen und beim Beenden der App installieren
|
|
1128
|
+
* (`autoInstallOnAppQuit` ist Vorgabe). Das Einspielen bleibt damit der App-Neustart; die GUI zeigt
|
|
1129
|
+
* seit E77 zusätzlich eine Zeile in der Sidebar, sobald das Paket geladen ist (`update-downloaded`).
|
|
1130
|
+
*
|
|
1131
|
+
* Drei Bedingungen, alle nötig:
|
|
1132
|
+
* — `app.isPackaged`: ungepackt gibt es keine `app-update.yml`, die Prüfung stiege ohnehin aus.
|
|
1133
|
+
* — `win32`: macOS lehnt electron-updater unsigniert ab, das wartet auf die Signierung (A4).
|
|
1134
|
+
* — kein Smoke-Test: dieselbe Zurückhaltung wie bei Watcher und Auto-Aktualisieren — ein
|
|
1135
|
+
* automatisierter Lauf greift nie ins Netz.
|
|
1136
|
+
*
|
|
1137
|
+
* Fehler werden nur vermerkt, nie weitergereicht: auf den Team-Rechnern kann das Gitea unerreichbar
|
|
1138
|
+
* sein (VPN, Abschaltung, DNS), und daran darf die App nicht scheitern. Der `error`-Listener ist
|
|
1139
|
+
* dabei nicht optional — electron-updater meldet Fehler als Event, und ein `error`-Event ohne
|
|
1140
|
+
* Zuhörer wirft in Node.
|
|
1141
|
+
*/
|
|
1142
|
+
function updatePruefen() {
|
|
1143
|
+
if (smoketest || !app.isPackaged || process.platform !== 'win32')
|
|
1144
|
+
return;
|
|
1145
|
+
try {
|
|
1146
|
+
if (!updaterVerdrahtet) {
|
|
1147
|
+
updaterVerdrahtet = true;
|
|
1148
|
+
autoUpdater.on('error', (fehler) => {
|
|
1149
|
+
console.log(`[updater] ${fehler instanceof Error ? fehler.message : String(fehler)}`);
|
|
1150
|
+
});
|
|
1151
|
+
// E77: das geladene Paket wird sichtbar, seit E80 mit Neustart-Knopf. Zusätzlich ein
|
|
1152
|
+
// Verlaufseintrag „GUI-Update X geladen" (E80), einmal je neuer Version — vor der Meldung an
|
|
1153
|
+
// die Sidebar, damit die Ansicht ihn beim nächsten `ladeVerlauf()` schon findet.
|
|
1154
|
+
autoUpdater.on('update-downloaded', (info) => {
|
|
1155
|
+
const version = info?.version;
|
|
1156
|
+
const guiVersion = typeof version === 'string' ? version : paketVersion();
|
|
1157
|
+
void anwendungsfaelle
|
|
1158
|
+
.hmgrEreignisVermerken({ art: 'gui-update-geladen', version: guiVersion }, letzterAusloeser)
|
|
1159
|
+
.catch((fehler) => {
|
|
1160
|
+
console.log(`[verlauf] ${fehler instanceof Error ? fehler.message : String(fehler)}`);
|
|
1161
|
+
});
|
|
1162
|
+
selbstUpdateMelden({ art: 'gui', version: guiVersion });
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
void autoUpdater.checkForUpdatesAndNotify().catch((fehler) => {
|
|
1166
|
+
console.log(`[updater] ${fehler instanceof Error ? fehler.message : String(fehler)}`);
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
catch (fehler) {
|
|
1170
|
+
console.log(`[updater] ${fehler instanceof Error ? fehler.message : String(fehler)}`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
/** Ein Hinweis für die Sidebar (E77). Ist das Fenster schon zu, verfällt er stillschweigend. */
|
|
1174
|
+
function selbstUpdateMelden(nachricht) {
|
|
1175
|
+
if (hauptfenster === null || hauptfenster.isDestroyed())
|
|
1176
|
+
return;
|
|
1177
|
+
hauptfenster.webContents.send('hmgr:selbstUpdate', nachricht);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Die daneben installierte hmgr-CLI gegen npm (E77, F83=O1). Läuft auf allen Plattformen — anders
|
|
1181
|
+
* als die GUI-Prüfung hängt sie an keinem Paket-Format und an keiner Signierung. Gemeldet wird nur,
|
|
1182
|
+
* nie installiert: ein `npm install -g` aus der App scheitert leicht an nvm-Pfaden und Rechten
|
|
1183
|
+
* (F82).
|
|
1184
|
+
*
|
|
1185
|
+
* Still bei allem, was schiefgehen kann: keine CLI im PATH, Fehlercode, Registry unerreichbar,
|
|
1186
|
+
* gleiche oder ältere Version auf npm. Der Login-PATH (F47) muss vorher stehen, sonst findet der
|
|
1187
|
+
* Aufruf die per nvm installierte CLI nicht.
|
|
1188
|
+
*/
|
|
1189
|
+
async function cliUpdatePruefen() {
|
|
1190
|
+
await pathBereit;
|
|
1191
|
+
const installiert = await hmgrCliVersion();
|
|
1192
|
+
if (installiert === null)
|
|
1193
|
+
return;
|
|
1194
|
+
const hinweis = await cliUpdateHinweis(installiert, { protokoll: (zeile) => console.log(zeile) });
|
|
1195
|
+
if (hinweis === null)
|
|
1196
|
+
return;
|
|
1197
|
+
// Verlaufseintrag „CLI X verfügbar" (E80), einmal je neuer Version, vor der Sidebar-Meldung.
|
|
1198
|
+
await anwendungsfaelle
|
|
1199
|
+
.hmgrEreignisVermerken({ art: 'cli-verfuegbar', version: hinweis.neu }, letzterAusloeser)
|
|
1200
|
+
.catch((fehler) => {
|
|
1201
|
+
console.log(`[verlauf] ${fehler instanceof Error ? fehler.message : String(fehler)}`);
|
|
1202
|
+
});
|
|
1203
|
+
selbstUpdateMelden({ art: 'cli', ...hinweis });
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Jeder Aktualisieren-Lauf prüft auch hmgr selbst (E80: Start, Intervall, Klick auf den Kringel) —
|
|
1207
|
+
* der Renderer ruft das aus `aktualisierenLauf`, unabhängig vom Auslöser. Beide Wege sind
|
|
1208
|
+
* unabhängig: die GUI-Prüfung gilt nur unter den E73-Bedingungen, die CLI-Prüfung überall. Im
|
|
1209
|
+
* Smoke-Test unterbleiben beide — kein Spawn, kein Netz.
|
|
1210
|
+
*/
|
|
1211
|
+
function selbstUpdatePruefen() {
|
|
1212
|
+
if (smoketest)
|
|
1213
|
+
return;
|
|
1214
|
+
updatePruefen();
|
|
1215
|
+
void cliUpdatePruefen().catch((fehler) => {
|
|
1216
|
+
console.log(`[cli-update] ${fehler instanceof Error ? fehler.message : String(fehler)}`);
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
780
1219
|
/**
|
|
781
1220
|
* Holt das vorhandene Fenster nach vorn — die Antwort der laufenden Instanz auf einen zweiten
|
|
782
1221
|
* Start (E67). Minimiert kommt es erst wieder hoch, dann bekommt es den Fokus.
|
|
@@ -811,6 +1250,8 @@ if (einzelinstanz) {
|
|
|
811
1250
|
Menu.setApplicationMenu(null);
|
|
812
1251
|
}
|
|
813
1252
|
fensterOeffnen();
|
|
1253
|
+
// Seit E80 prüft schon der erste Aktualisieren-Lauf (Renderer, ausgelöst bei `did-finish-load`)
|
|
1254
|
+
// auch hmgr selbst über `selbstUpdatePruefen` — ein eigener Start-Aufruf hier wäre der zweite.
|
|
814
1255
|
app.on('activate', () => {
|
|
815
1256
|
if (BrowserWindow.getAllWindows().length === 0)
|
|
816
1257
|
fensterOeffnen();
|