hmgr 0.0.41 → 0.0.43
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/gui-signal.js +29 -0
- package/dist/aenderungen.json +7 -0
- package/dist/cli/hilfe.js +29 -10
- package/dist/cli/main.js +90 -39
- package/dist/cli/verlauf-anzeige.js +61 -0
- package/dist/gui/main.js +84 -1
- package/dist/gui/preload.cjs +5 -0
- package/dist/gui/renderer/renderer.js +158 -12
- package/dist/gui/renderer/styles.css +5 -0
- package/dist/gui/signal.js +58 -0
- package/dist/kern/anwendungsfaelle.js +35 -3
- package/dist/kern/modell.js +18 -0
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Signaldatei von der CLI an eine laufende GUI (E110): `<hmgrHome>/gui-signal.json`. Die CLI
|
|
2
|
+
// schreibt sie nach `dir add` und Verwandten, die GUI beobachtet das Verzeichnis (src/gui/signal.ts).
|
|
3
|
+
// Kein Port: der Kern kennt das Signal nicht, die Entscheidung „Signal ja/nein" fällt in der CLI.
|
|
4
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { ohneBom } from "./datei-text.js";
|
|
7
|
+
export const SIGNAL_DATEI = 'gui-signal.json';
|
|
8
|
+
/** Atomar: Temp-Datei im selben Verzeichnis, dann rename darüber. */
|
|
9
|
+
export async function signalSchreiben(hmgrHome, signal) {
|
|
10
|
+
await mkdir(hmgrHome, { recursive: true });
|
|
11
|
+
const ziel = path.join(hmgrHome, SIGNAL_DATEI);
|
|
12
|
+
const temp = path.join(hmgrHome, `${SIGNAL_DATEI}.${process.pid}.tmp`);
|
|
13
|
+
await writeFile(temp, `${JSON.stringify(signal)}\n`);
|
|
14
|
+
await rename(temp, ziel);
|
|
15
|
+
}
|
|
16
|
+
/** `undefined` bei fehlender, kaputter oder unbekannt geformter Datei. */
|
|
17
|
+
export async function signalLesen(hmgrHome) {
|
|
18
|
+
try {
|
|
19
|
+
const roh = await readFile(path.join(hmgrHome, SIGNAL_DATEI), 'utf8');
|
|
20
|
+
const daten = JSON.parse(ohneBom(roh));
|
|
21
|
+
if (daten.art !== 'verzeichnis-zeigen' || typeof daten.pfad !== 'string' || typeof daten.zeit !== 'number') {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return { art: daten.art, pfad: daten.pfad, zeit: daten.zeit };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/aenderungen.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"fassung": 1,
|
|
3
3
|
"versionen": {
|
|
4
|
+
"0.0.43": [
|
|
5
|
+
"Fügst du im Terminal ein Arbeitsverzeichnis hinzu (hmgr dir add oder link mit einem neuen Verzeichnis), kommt ein offenes hmgr-Fenster nach vorn und zeigt das Verzeichnis sofort an."
|
|
6
|
+
],
|
|
7
|
+
"0.0.42": [
|
|
8
|
+
"Der Verlauf meldet jetzt auch neue und entfernte Skills einer Quelle, nicht nur geänderte.",
|
|
9
|
+
"Verlauf und „hmgr update“ sind leichter zu lesen: Quellen erscheinen mit ihrem Namen, darunter Neu, Entfernt und Geändert, ohne Commit-Kürzel. Quellen ohne Änderung an deinen Skills stehen gesammelt in einer Zeile am Ende."
|
|
10
|
+
],
|
|
4
11
|
"0.0.41": [
|
|
5
12
|
"Im Verlauf steht unter „hmgr … installiert\" jetzt aufklappbar, was sich in der neuen Version geändert hat."
|
|
6
13
|
]
|
package/dist/cli/hilfe.js
CHANGED
|
@@ -104,6 +104,10 @@ Plugins des Vorhabens stehen. Ohne Zeiger bleibt das Verzeichnis gemerkt und jed
|
|
|
104
104
|
(E26). dir forget löst Bindung und Eintrag, die gelegten Links bleiben liegen. Gebunden wird nur für
|
|
105
105
|
Harnesses, die hmgr verwaltet: standardmäßig nur claude-code (E38).
|
|
106
106
|
|
|
107
|
+
Läuft die hmgr-GUI, kommt sie bei dir add nach vorn und zeigt das Verzeichnis (E110). Dasselbe
|
|
108
|
+
passiert bei link, unlink, adopt, bundle add und plugin add mit --ort dir:<pfad>, wenn das Verzeichnis
|
|
109
|
+
dabei neu gemerkt wird. Über SSH bleibt die GUI in Ruhe, und läuft keine GUI, passiert nichts.
|
|
110
|
+
|
|
107
111
|
Ein Arbeitsverzeichnis unter Windows, das in einer WSL-Distro liegt (\\\\wsl$\\<distro>\\… oder
|
|
108
112
|
\\\\wsl.localhost\\<distro>\\…, E96), bedient hmgr über die Bridge dieser Distro, wie ein Homedir in
|
|
109
113
|
WSL: der Link liegt im Linux-Dateisystem und zeigt auf den Windows-Checkout (/mnt/c). MCP-Server
|
|
@@ -135,6 +139,9 @@ nur für ein Arbeitsverzeichnis mit composer.json, an Home und Profil bleibt jed
|
|
|
135
139
|
Das Schreiben in einen Zeiger ist gesperrt, bis das Composer-Thema entschieden ist (E57): an einem
|
|
136
140
|
Arbeitsverzeichnis mit Zeiger bricht link ohne --local ab, der Link wird nicht gelegt.
|
|
137
141
|
|
|
142
|
+
Mit --ort dir:<pfad> kommt eine laufende hmgr-GUI nach vorn und zeigt das Verzeichnis, wenn es dabei
|
|
143
|
+
neu gemerkt wird (nicht per SSH, E110).
|
|
144
|
+
|
|
138
145
|
Schalter:
|
|
139
146
|
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
140
147
|
--ort <ort> home, wsl:<distro>, dir:<pfad> oder profil:<name>, Standard home. profil
|
|
@@ -496,10 +503,16 @@ damit nichts verloren geht. Lokale Quellen haben keinen Checkout und bleiben unb
|
|
|
496
503
|
Quelle ohne eigenen Checkout wird dabei aufgefrischt (E85): ihr Katalog zeigt danach, was neu im
|
|
497
504
|
Remote liegt, auch ohne dass irgendwo ein Checkout von ihr existiert.
|
|
498
505
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
506
|
+
Aktualisierte Checkouts erscheinen je Quelle als Gruppe (E109): der Name der Quelle aus
|
|
507
|
+
hmgr source list, darunter „Neu", „Entfernt" und „Geändert" mit den Skill-Namen, leere Zeilen
|
|
508
|
+
fallen weg. Neu und entfernt gelten für alle Skills der Quelle (der Vergleich von altem und neuem
|
|
509
|
+
Commit nach dem Namen aus der SKILL.md, eine Umbenennung zeigt beides), „Geändert" nur für Skills,
|
|
510
|
+
die in diesem Checkout ausgecheckt sind (E70). Liegt der Checkout auf einem anderen Ref als main
|
|
511
|
+
oder master, steht er in Klammern hinter dem Namen, etwa „cmux (nightly)". Quellen, bei denen sich
|
|
512
|
+
an Skills nichts getan hat (README, CI), nennt eine Zeile am Ende des Berichts: „Außerdem neuer
|
|
513
|
+
Stand bei cmux, ohne Änderung an deinen Skills." Commit-Kürzel zeigt update nicht mehr, sie stehen
|
|
514
|
+
in ~/.hmgr/verlauf.json. Ist eine Quelle nicht mehr in der Quellenliste, steht ihr Pfad
|
|
515
|
+
besitzer/repo statt des Namens.
|
|
503
516
|
|
|
504
517
|
Im selben Lauf gleicht update die installierten Bundles gegen ihre Definitionen ab (E69): eine
|
|
505
518
|
Definition, die inzwischen ein Mitglied mehr oder weniger nennt, wird in jedem Ziel nachgezogen, in
|
|
@@ -557,12 +570,18 @@ die hmgr update schreibt und die GUI bei jedem automatischen und angestoßenen L
|
|
|
557
570
|
liest. Ein Lauf ohne Ereignis (nichts Neues) steht nicht drin, die Datei ist ein Changelog, keine
|
|
558
571
|
vollständige Historie — sie hält die letzten 20 Einträge.
|
|
559
572
|
|
|
560
|
-
Je Eintrag steht eine Kopfzeile mit Datum und Uhrzeit,
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
573
|
+
Je Eintrag steht eine Kopfzeile mit Datum und Uhrzeit („Heute, 00:26", „Gestern, 22:58", sonst
|
|
574
|
+
„06.10.2026, 17:42"), darunter die Ereignisse. Aktualisierte Checkouts stehen je Quelle als Gruppe
|
|
575
|
+
(E109): der Name der Quelle, darunter „Neu", „Entfernt" und „Geändert" mit Skill-Namen, leere
|
|
576
|
+
Zeilen fallen weg; ein Ref außer main oder master steht in Klammern hinter dem Namen. Neu und
|
|
577
|
+
entfernt gelten für alle Skills der Quelle (eine Umbenennung zeigt beides), „Geändert" nur für
|
|
578
|
+
ausgecheckte Skills (E70). Quellen ohne Skill-Änderung sammelt eine Zeile am Ende des Laufs
|
|
579
|
+
(„Außerdem neuer Stand bei cmux, ohne Änderung an deinen Skills."). Dazwischen die übrigen
|
|
580
|
+
Ereignisse, je eine Zeile: ein nachgezogenes Bundle mit gelegten und entfernten Links, ein
|
|
581
|
+
aufgeräumter (gelöschter) Checkout, ein Fehler zu einer Quelle oder einem Bundle, dazu
|
|
582
|
+
hmgr-Ereignisse: ein Versionssprung von hmgr selbst (GUI oder CLI, erkannt beim Start bzw. bei
|
|
583
|
+
jedem CLI-Aufruf) oder ein gefundenes Update, das noch nicht installiert ist. Einträge aus der
|
|
584
|
+
Zeit vor E109 zeigen nur „Geändert". Plugins stehen nicht im Verlauf (siehe hmgr help update).
|
|
566
585
|
|
|
567
586
|
Unter einem „installiert"-Eintrag von hmgr stehen die Änderungen: je Version seit der zuvor
|
|
568
587
|
vermerkten die Nummer und darunter die Änderungen als kurze Sätze (E108). Einträge ohne bekannte Vorversion
|
package/dist/cli/main.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { realpathSync } from 'node:fs';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { checkoutGruppen, quellenAnzeigeName, sammelZeile, verlaufZeitpunkt } from "./verlauf-anzeige.js";
|
|
7
8
|
import { Anwendungsfaelle, pluginWeltText, } from "../kern/anwendungsfaelle.js";
|
|
8
9
|
import { AbhaengigkeitFehlt, BundleDateiFehler, BundleMehrdeutig, BundleMitgliederFehlen, BundleNamenskonflikte, BundleNichtInstalliert, BundleUnbekannt, EinschraenkungOhneZeiger, HarnessInaktiv, HarnessNichtVorhanden, HarnessTypUnbekannt, HeimAdresseUngueltig, HeimBereitsVorhanden, HeimFaehigkeitFehlt, HeimNurUnterWindows, ClaudeCliNichtErreichbar, HeimUnbekannt, KeinPassenderRef, LinkAnlegenFehlgeschlagen, McpAbweichenderEintrag, McpEbeneNichtUnterstuetzt, McpFaehigkeitFehlt, McpKatalogFehler, McpNichtInGeteilterDatei, McpNurGeteilterFundort, McpOrtUnterGitWurzel, McpUebernahmeNichtMoeglich, McpUebernahmeUnvollstaendig, McpUnbekannt, NameBelegt, OrtNichtUnterstuetzt, PluginMehrdeutig, PluginUnbekannt, QuelleAufraeumenLokaleAenderungen, QuelleNameBelegt, QuelleUnbekannt, SkillMehrdeutig, SkillOrdnerBereitsVerwaltet, SkillOrdnerFaehigkeitFehlt, SkillOrdnerNichtVerwaltet, SkillOrdnerUngueltig, SkillUnbekannt, ZeigerMehrdeutig, WslBrueckeFehlgeschlagen, ZeigerSchreibenGesperrt, heimAdresseAlsText, heimAdresseZerlegen, wslDistroVonOrt, wslUncZerlegen, pluginSchluessel, quelleName, quelleSchluessel, } from "../kern/modell.js";
|
|
9
10
|
import { Agents } from "../adapter/agents.js";
|
|
@@ -20,6 +21,7 @@ import { paketVersion } from "../adapter/paket-version.js";
|
|
|
20
21
|
import { quelleAlsText, quelleAusText } from "../adapter/quelle-text.js";
|
|
21
22
|
import { TerminalRueckfrage } from "../adapter/terminal.js";
|
|
22
23
|
import { ProzessUmgebung } from "../adapter/umgebung.js";
|
|
24
|
+
import { signalSchreiben } from "../adapter/gui-signal.js";
|
|
23
25
|
import { ZustandJson } from "../adapter/zustand-json.js";
|
|
24
26
|
import { VerlaufJson } from "../adapter/verlauf-json.js";
|
|
25
27
|
import { AenderungenJson } from "../adapter/aenderungen-json.js";
|
|
@@ -258,6 +260,21 @@ const UEBERNAHME_GRUND = {
|
|
|
258
260
|
'lokale-aenderungen': 'Ordner weicht von der Quelle ab',
|
|
259
261
|
'nicht-pruefbar': 'hmgr kann nicht prüfen, ob der Ordner der Quelle entspricht',
|
|
260
262
|
};
|
|
263
|
+
/**
|
|
264
|
+
* E110: gibt einer laufenden GUI Bescheid, dass dieses Verzeichnis gezeigt werden soll. Nie bei
|
|
265
|
+
* SSH-Aufrufen, nie ein Grund zu scheitern.
|
|
266
|
+
*/
|
|
267
|
+
async function guiBenachrichtigen(umgebung, ports, pfad) {
|
|
268
|
+
try {
|
|
269
|
+
const variablen = ports.umgebung.variablen();
|
|
270
|
+
if ((variablen.SSH_CONNECTION ?? '') !== '' || (variablen.SSH_TTY ?? '') !== '')
|
|
271
|
+
return;
|
|
272
|
+
await signalSchreiben(umgebung.hmgrHome, { art: 'verzeichnis-zeigen', pfad, zeit: Date.now() });
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// Das Signal ist ein Zusatz; das Kommando selbst hat funktioniert.
|
|
276
|
+
}
|
|
277
|
+
}
|
|
261
278
|
export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
262
279
|
const { anwendungsfaelle, ports } = bauen(umgebung, optionen);
|
|
263
280
|
const kommando = argv[0];
|
|
@@ -425,6 +442,7 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
425
442
|
const ergebnis = composer === undefined
|
|
426
443
|
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(pfad)
|
|
427
444
|
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(pfad, composer);
|
|
445
|
+
await guiBenachrichtigen(umgebung, ports, ergebnis.pfad);
|
|
428
446
|
console.log(`Arbeitsverzeichnis ${ergebnis.pfad}`);
|
|
429
447
|
if (ergebnis.zeigerPfad !== undefined)
|
|
430
448
|
console.log(` Zeiger ${ergebnis.zeigerPfad}`);
|
|
@@ -464,10 +482,11 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
464
482
|
if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
465
483
|
// Verzeichnis merken und binden, damit status es wiederfindet und der Zeiger bekannt ist (E21).
|
|
466
484
|
const composer = werte.get('composer');
|
|
467
|
-
|
|
468
|
-
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
469
|
-
|
|
470
|
-
|
|
485
|
+
const verz = composer === undefined
|
|
486
|
+
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
487
|
+
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad, composer);
|
|
488
|
+
if (verz.neu)
|
|
489
|
+
await guiBenachrichtigen(umgebung, ports, verz.pfad);
|
|
471
490
|
}
|
|
472
491
|
if (kommando === 'unlink') {
|
|
473
492
|
const ergebnis = await anwendungsfaelle.linkEntfernen({ skill, ziel, lokal });
|
|
@@ -503,10 +522,11 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
503
522
|
const name = rest[0];
|
|
504
523
|
if (ziel.ort.art === 'arbeitsverzeichnis' && !probelauf) {
|
|
505
524
|
const composer = werte.get('composer');
|
|
506
|
-
|
|
507
|
-
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
508
|
-
|
|
509
|
-
|
|
525
|
+
const verz = composer === undefined
|
|
526
|
+
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
527
|
+
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad, composer);
|
|
528
|
+
if (verz.neu)
|
|
529
|
+
await guiBenachrichtigen(umgebung, ports, verz.pfad);
|
|
510
530
|
}
|
|
511
531
|
const ergebnis = await anwendungsfaelle.uebernehmen({
|
|
512
532
|
ziel,
|
|
@@ -567,10 +587,11 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
567
587
|
const lokal = flaggen.has('local') || flaggen.has('lokal');
|
|
568
588
|
if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
569
589
|
const composer = werte.get('composer');
|
|
570
|
-
|
|
571
|
-
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
572
|
-
|
|
573
|
-
|
|
590
|
+
const verz = composer === undefined
|
|
591
|
+
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
592
|
+
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad, composer);
|
|
593
|
+
if (verz.neu)
|
|
594
|
+
await guiBenachrichtigen(umgebung, ports, verz.pfad);
|
|
574
595
|
}
|
|
575
596
|
const quelle = werte.get('quelle');
|
|
576
597
|
const ergebnis = unterkommando === 'add'
|
|
@@ -626,10 +647,11 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
626
647
|
if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
627
648
|
// Wie bei link: Verzeichnis merken und binden, damit status es wiederfindet (E21).
|
|
628
649
|
const composer = werte.get('composer');
|
|
629
|
-
|
|
630
|
-
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
631
|
-
|
|
632
|
-
|
|
650
|
+
const verz = composer === undefined
|
|
651
|
+
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
652
|
+
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad, composer);
|
|
653
|
+
if (verz.neu)
|
|
654
|
+
await guiBenachrichtigen(umgebung, ports, verz.pfad);
|
|
633
655
|
}
|
|
634
656
|
if (unterkommando === 'add') {
|
|
635
657
|
const quelle = werte.get('quelle');
|
|
@@ -764,16 +786,27 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
764
786
|
const { checkouts: liste, pluginFehler, bundles, aufraeumen, doctor, } = await anwendungsfaelle.aktualisieren({ ausloeser: 'cli', ...(nurQuelle === undefined ? {} : { quelle: nurQuelle }) });
|
|
765
787
|
if (liste.length === 0)
|
|
766
788
|
console.log('Keine Checkouts.');
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
789
|
+
const updateQuellen = liste.length === 0 ? [] : await anwendungsfaelle.quellen();
|
|
790
|
+
// Aktualisierte Checkouts als Quellengruppen, ohne Commit-Kürzel (E109, F125); Quellen ohne
|
|
791
|
+
// Skill-Änderung sammelt die Zeile am Ende. Die übrigen Zeilenarten nennen die Quelle beim Namen.
|
|
792
|
+
const { zeilen: updateGruppen, ohneAenderung: updateOhne } = checkoutGruppen(liste
|
|
793
|
+
.filter(({ aktualisierung }) => aktualisierung.ergebnis === 'aktualisiert')
|
|
794
|
+
.map(({ checkout, geaenderteSkills, neueSkills, entfernteSkills }) => ({
|
|
795
|
+
quelle: quelleAlsText(checkout.quelle),
|
|
796
|
+
ref: checkout.ref,
|
|
797
|
+
neu: neueSkills,
|
|
798
|
+
entfernt: entfernteSkills,
|
|
799
|
+
geaendert: geaenderteSkills,
|
|
800
|
+
})), updateQuellen);
|
|
801
|
+
for (const zeile of updateGruppen)
|
|
802
|
+
console.log(zeile);
|
|
803
|
+
let updateAndere = updateGruppen.length > 0;
|
|
804
|
+
for (const { checkout, aktualisierung } of liste) {
|
|
805
|
+
if (aktualisierung.ergebnis === 'aktualisiert')
|
|
806
|
+
continue;
|
|
807
|
+
updateAndere = true;
|
|
808
|
+
const kopf = quellenAnzeigeName({ quelle: quelleAlsText(checkout.quelle), ref: checkout.ref }, updateQuellen);
|
|
809
|
+
if (aktualisierung.ergebnis === 'unveraendert')
|
|
777
810
|
console.log(` unverändert ${kopf}`);
|
|
778
811
|
else if (aktualisierung.ergebnis === 'uebersprungen')
|
|
779
812
|
console.log(` übersprungen ${kopf} lokale Änderung (E15)`);
|
|
@@ -807,6 +840,14 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
807
840
|
else
|
|
808
841
|
console.log(` liegen lassen Arbeitsverzeichnis ${pfad} (nicht mehr auf der Platte, ohne Bestätigung nicht vergessen)`);
|
|
809
842
|
}
|
|
843
|
+
// Die Sammelzeile steht ganz am Ende des Berichts (E109); „Außerdem" nur, wenn sonst etwas im Lauf stand.
|
|
844
|
+
const updateSammel = sammelZeile(updateOhne, !updateAndere &&
|
|
845
|
+
pluginFehler.length === 0 &&
|
|
846
|
+
bundleBericht(bundles).length === 0 &&
|
|
847
|
+
aufraeumen.verwaiste.length === 0 &&
|
|
848
|
+
doctor.fehlendeArbeitsverzeichnisse.length === 0);
|
|
849
|
+
if (updateSammel !== undefined)
|
|
850
|
+
console.log(updateSammel);
|
|
810
851
|
// Zuletzt hmgr selbst (E77): wer schon aktualisiert, will auch wissen, dass sein hmgr alt ist.
|
|
811
852
|
// Verglichen wird die eigene Version (E72) gegen npm, gemeldet wird nur — installiert wird
|
|
812
853
|
// nichts. Offline oder Registry unerreichbar heißt keine Zeile, still: der Lauf selbst ist
|
|
@@ -858,9 +899,28 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
858
899
|
const eintraege = await anwendungsfaelle.verlauf();
|
|
859
900
|
if (eintraege.length === 0)
|
|
860
901
|
console.log('Noch keine Ereignisse.');
|
|
902
|
+
const quellenListe = eintraege.length === 0 ? [] : await anwendungsfaelle.quellen();
|
|
861
903
|
for (const eintrag of eintraege) {
|
|
862
904
|
console.log(verlaufZeitpunkt(eintrag.zeit));
|
|
905
|
+
// Erst die Quellengruppen, dann die übrigen Ereignisse, zuletzt die Sammelzeile (E109).
|
|
906
|
+
const vermerke = [];
|
|
907
|
+
for (const ereignis of eintrag.ereignisse)
|
|
908
|
+
if (ereignis.art === 'checkout')
|
|
909
|
+
vermerke.push({
|
|
910
|
+
quelle: ereignis.quelle,
|
|
911
|
+
ref: ereignis.ref,
|
|
912
|
+
neu: ereignis.neu ?? [],
|
|
913
|
+
entfernt: ereignis.entfernt ?? [],
|
|
914
|
+
geaendert: ereignis.skills,
|
|
915
|
+
});
|
|
916
|
+
const { zeilen: gruppenZeilen, ohneAenderung } = checkoutGruppen(vermerke, quellenListe);
|
|
917
|
+
for (const zeile of gruppenZeilen)
|
|
918
|
+
console.log(zeile);
|
|
919
|
+
let andere = gruppenZeilen.length > 0;
|
|
863
920
|
for (const ereignis of eintrag.ereignisse) {
|
|
921
|
+
if (ereignis.art === 'checkout')
|
|
922
|
+
continue;
|
|
923
|
+
andere = true;
|
|
864
924
|
console.log(` ${verlaufEreignisZeile(ereignis)}`);
|
|
865
925
|
// Unter einem Versionssprung die Commits der neuen Version(en), eingerückt (E108).
|
|
866
926
|
if (ereignis.art === 'hmgr-installiert') {
|
|
@@ -869,6 +929,9 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
869
929
|
console.log(zeile);
|
|
870
930
|
}
|
|
871
931
|
}
|
|
932
|
+
const sammel = sammelZeile(ohneAenderung, !andere);
|
|
933
|
+
if (sammel !== undefined)
|
|
934
|
+
console.log(sammel);
|
|
872
935
|
}
|
|
873
936
|
return 0;
|
|
874
937
|
}
|
|
@@ -1093,24 +1156,12 @@ function bundleBericht(eintraege) {
|
|
|
1093
1156
|
}
|
|
1094
1157
|
return zeilen;
|
|
1095
1158
|
}
|
|
1096
|
-
/** Kopfzeile eines Verlaufseintrags (E80): lokale Zeit, damit sie zum Rechner des Lesers passt. */
|
|
1097
|
-
function verlaufZeitpunkt(iso) {
|
|
1098
|
-
const datum = new Date(iso);
|
|
1099
|
-
const zwei = (n) => String(n).padStart(2, '0');
|
|
1100
|
-
return (`${zwei(datum.getDate())}.${zwei(datum.getMonth() + 1)}.${datum.getFullYear()} ` +
|
|
1101
|
-
`${zwei(datum.getHours())}:${zwei(datum.getMinutes())}`);
|
|
1102
|
-
}
|
|
1103
1159
|
/** Je Version die Nummer und darunter ihre Commit-Betreffs, tiefer eingerückt als die Ereigniszeile (E108). */
|
|
1104
1160
|
function aenderungsZeilen(aenderungen) {
|
|
1105
1161
|
return aenderungen.flatMap((a) => [` ${a.version}`, ...a.commits.map((c) => ` ${c}`)]);
|
|
1106
1162
|
}
|
|
1107
|
-
/** Eine Ereigniszeile im Verlauf (E80), eingerückt unter der Kopfzeile ihres Laufs. */
|
|
1163
|
+
/** Eine Ereigniszeile im Verlauf (E80), eingerückt unter der Kopfzeile ihres Laufs. Checkouts stellt `checkoutGruppen` dar (E109). */
|
|
1108
1164
|
function verlaufEreignisZeile(ereignis) {
|
|
1109
|
-
if (ereignis.art === 'checkout') {
|
|
1110
|
-
const stand = `${ereignis.von.slice(0, 7)} → ${ereignis.nach.slice(0, 7)}`;
|
|
1111
|
-
const skills = ereignis.skills.length > 0 ? `, Skills: ${ereignis.skills.join(', ')}` : '';
|
|
1112
|
-
return `${ereignis.quelle} @ ${ereignis.ref}: ${stand}${skills}`;
|
|
1113
|
-
}
|
|
1114
1165
|
if (ereignis.art === 'bundle') {
|
|
1115
1166
|
const teile = [
|
|
1116
1167
|
ereignis.gelegt.length > 0 ? `gelegt ${ereignis.gelegt.join(', ')}` : undefined,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Darstellung von Verlauf und `hmgr update` für Menschen (E109): Quellengruppen mit Neu, Entfernt,
|
|
2
|
+
// Geändert und eine Sammelzeile für Quellen ohne Skill-Änderung. Reine Funktionen, die GUI baut
|
|
3
|
+
// dieselbe Darstellung im Renderer nach (renderer.js, verlaufGruppen).
|
|
4
|
+
import { quelleVerlaufName } from "../kern/modell.js";
|
|
5
|
+
/** Name der Quelle, bei einem Ref außer main/master mit dem Ref in Klammern dahinter. */
|
|
6
|
+
export function quellenAnzeigeName(vermerk, quellen) {
|
|
7
|
+
const name = quelleVerlaufName(vermerk.quelle, quellen);
|
|
8
|
+
return vermerk.ref === 'main' || vermerk.ref === 'master' ? name : `${name} (${vermerk.ref})`;
|
|
9
|
+
}
|
|
10
|
+
const BESCHRIFTUNG_BREITE = 11;
|
|
11
|
+
/**
|
|
12
|
+
* Je Quelle mit Skill-Änderung eine Gruppe (Name, darunter Neu/Entfernt/Geändert, leere fallen weg),
|
|
13
|
+
* eingerückt für die Ausgabe unter einer Kopfzeile. `ohneAenderung` sind die Namen der übrigen.
|
|
14
|
+
*/
|
|
15
|
+
export function checkoutGruppen(vermerke, quellen) {
|
|
16
|
+
const zeilen = [];
|
|
17
|
+
const ohneAenderung = [];
|
|
18
|
+
for (const v of vermerke) {
|
|
19
|
+
const name = quellenAnzeigeName(v, quellen);
|
|
20
|
+
const teile = [
|
|
21
|
+
['Neu', v.neu],
|
|
22
|
+
['Entfernt', v.entfernt],
|
|
23
|
+
['Geändert', v.geaendert],
|
|
24
|
+
];
|
|
25
|
+
const belegt = teile.filter(([, namen]) => namen.length > 0);
|
|
26
|
+
if (belegt.length === 0) {
|
|
27
|
+
if (!ohneAenderung.includes(name))
|
|
28
|
+
ohneAenderung.push(name);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
zeilen.push(` ${name}`);
|
|
32
|
+
for (const [beschriftung, namen] of belegt)
|
|
33
|
+
zeilen.push(` ${beschriftung.padEnd(BESCHRIFTUNG_BREITE)}${namen.join(', ')}`);
|
|
34
|
+
}
|
|
35
|
+
return { zeilen, ohneAenderung };
|
|
36
|
+
}
|
|
37
|
+
/** Die Sammelzeile am Ende; `alleinig`, wenn sonst nichts im Lauf steht. */
|
|
38
|
+
export function sammelZeile(namen, alleinig) {
|
|
39
|
+
if (namen.length === 0)
|
|
40
|
+
return undefined;
|
|
41
|
+
const liste = namen.join(', ');
|
|
42
|
+
return alleinig
|
|
43
|
+
? ` Neuer Stand bei ${liste}, ohne Änderung an deinen Skills.`
|
|
44
|
+
: ` Außerdem neuer Stand bei ${liste}, ohne Änderung an deinen Skills.`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Kopfzeile eines Verlaufseintrags (E80, E109): „Heute, 00:26", „Gestern, 22:58", sonst
|
|
48
|
+
* „06.10.2026, 17:42". Lokale Zeit, damit sie zum Rechner des Lesers passt.
|
|
49
|
+
*/
|
|
50
|
+
export function verlaufZeitpunkt(iso, jetzt = new Date()) {
|
|
51
|
+
const datum = new Date(iso);
|
|
52
|
+
const zwei = (n) => String(n).padStart(2, '0');
|
|
53
|
+
const uhr = `${zwei(datum.getHours())}:${zwei(datum.getMinutes())}`;
|
|
54
|
+
const tag = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
55
|
+
const gestern = new Date(jetzt.getFullYear(), jetzt.getMonth(), jetzt.getDate() - 1).getTime();
|
|
56
|
+
if (tag(datum) === tag(jetzt))
|
|
57
|
+
return `Heute, ${uhr}`;
|
|
58
|
+
if (tag(datum) === gestern)
|
|
59
|
+
return `Gestern, ${uhr}`;
|
|
60
|
+
return `${zwei(datum.getDate())}.${zwei(datum.getMonth() + 1)}.${datum.getFullYear()}, ${uhr}`;
|
|
61
|
+
}
|
package/dist/gui/main.js
CHANGED
|
@@ -21,6 +21,8 @@ import { iconFuerQuelle } from "./icons.js";
|
|
|
21
21
|
import { entscheideOeffnen } from "./oeffnen.js";
|
|
22
22
|
import { mcpUebernahmeSerialisieren, mcpUebersichtSerialisieren, mcpWirkungSerialisieren } from "./mcp-ansicht.js";
|
|
23
23
|
import { beobachten } from "./beobachter.js";
|
|
24
|
+
import { signalSchreiben } from "../adapter/gui-signal.js";
|
|
25
|
+
import { signalBeobachten } from "./signal.js";
|
|
24
26
|
import { loginPathUebernehmen } from "./login-pfad.js";
|
|
25
27
|
import { hmgrCliVersion } from "./hmgr-cli.js";
|
|
26
28
|
import { cliUpdateHinweis } from "../adapter/npm-registry.js";
|
|
@@ -275,6 +277,8 @@ function registriereIpc() {
|
|
|
275
277
|
art: quelle.art,
|
|
276
278
|
text: quelleAlsText(quelle),
|
|
277
279
|
name: quelleName(quelle),
|
|
280
|
+
// Nur der gespeicherte Name (E109): der Verlauf braucht den Unterschied zum abgeleiteten.
|
|
281
|
+
gespeichert: quelle.name,
|
|
278
282
|
}))));
|
|
279
283
|
// Quelle-Verwaltung in den Einstellungen (E54, E59): Text-zu-Quelle passiert im Main-Prozess, der
|
|
280
284
|
// Renderer kennt nur Zeichenketten. Der Anzeigename kommt serverseitig mit, damit dem Renderer nie
|
|
@@ -286,6 +290,7 @@ function registriereIpc() {
|
|
|
286
290
|
art: ergebnis.quelle.art,
|
|
287
291
|
text: quelleAlsText(ergebnis.quelle),
|
|
288
292
|
name: quelleName(ergebnis.quelle),
|
|
293
|
+
gespeichert: ergebnis.quelle.name,
|
|
289
294
|
},
|
|
290
295
|
skills: ergebnis.skills,
|
|
291
296
|
marketplace: ergebnis.marketplace,
|
|
@@ -295,7 +300,7 @@ function registriereIpc() {
|
|
|
295
300
|
ipcMain.handle('hmgr:quelleEntfernen', async (_event, text) => auffangen(async () => anwendungsfaelle.quelleEntfernen(quelleAusText(text))));
|
|
296
301
|
ipcMain.handle('hmgr:quelleUmbenennen', async (_event, text, name) => auffangen(async () => {
|
|
297
302
|
const umbenannt = await anwendungsfaelle.quelleUmbenennen(quelleAusText(text), name);
|
|
298
|
-
return { art: umbenannt.art, text: quelleAlsText(umbenannt), name: quelleName(umbenannt) };
|
|
303
|
+
return { art: umbenannt.art, text: quelleAlsText(umbenannt), name: quelleName(umbenannt), gespeichert: umbenannt.name };
|
|
299
304
|
}));
|
|
300
305
|
// Aufräum-Angebot nach dem Entfernen einer Quelle (E58): Inventar vor dem eigentlichen Entfernen
|
|
301
306
|
// erheben (der Renderer ruft das, bevor er quelleEntfernen aufruft), dann bei Zustimmung abräumen.
|
|
@@ -502,6 +507,7 @@ function neuLadenAnstossenFabrik(minAbstandMs) {
|
|
|
502
507
|
}
|
|
503
508
|
const neuLadenAnstossen = neuLadenAnstossenFabrik(FOKUS_RELOAD_MIN_ABSTAND_MS);
|
|
504
509
|
let beobachter;
|
|
510
|
+
let signalBeobachter;
|
|
505
511
|
function fensterOeffnen() {
|
|
506
512
|
hauptfenster = new BrowserWindow({
|
|
507
513
|
width: 1280,
|
|
@@ -1364,6 +1370,63 @@ function fensterOeffnen() {
|
|
|
1364
1370
|
await new Promise((r) => setTimeout(r, 800));
|
|
1365
1371
|
const auszug = await fenster.webContents.executeJavaScript(NEUSTART_AUSZUG_SKRIPT);
|
|
1366
1372
|
console.log(`[smoke] Neustart-Modal: ${String(auszug)}`);
|
|
1373
|
+
})
|
|
1374
|
+
// E110: CLI-Signal gegen die laufende GUI. Das Signal schreibt dieser Main-Prozess in das
|
|
1375
|
+
// Wegwerf-Home (der Renderer kann keine Dateien schreiben). Fall 1: ohne Dialog springt die
|
|
1376
|
+
// GUI aus einer anderen Ansicht zur Karte. Fall 2: mit offenem Dialog bleibt alles stehen,
|
|
1377
|
+
// erst nach dem Schließen folgt der Sprung. Läuft nach der Neustart-Modal-Prüfung, weil der Fall
|
|
1378
|
+
// offene Dialoge schließt.
|
|
1379
|
+
.then(async () => {
|
|
1380
|
+
const pfade = (process.env.HMGR_GUI_SMOKE_SIGNAL_PFADE ?? '').split('\n').filter((p) => p !== '');
|
|
1381
|
+
if (pfade.length < 2)
|
|
1382
|
+
return;
|
|
1383
|
+
const [pfad1, pfad2] = pfade;
|
|
1384
|
+
const js = (code) => fenster.webContents.executeJavaScript(code);
|
|
1385
|
+
const karteJs = (pfad) => `(function () {
|
|
1386
|
+
var schluessel = ${JSON.stringify(`dir:${pfad}`)};
|
|
1387
|
+
var karte = Array.from(document.querySelectorAll('#zielliste .ziel.verzeichnis'))
|
|
1388
|
+
.find(function (k) { return k.dataset.verzKey === schluessel; });
|
|
1389
|
+
return {
|
|
1390
|
+
karte: karte !== undefined,
|
|
1391
|
+
zieleSichtbar: document.getElementById('ansicht-ziele').hidden === false,
|
|
1392
|
+
aufgeklappt: karte?.querySelector('.zielkopf')?.getAttribute('aria-expanded') ?? null,
|
|
1393
|
+
};
|
|
1394
|
+
})()`;
|
|
1395
|
+
const warteAufSprung = async (pfad) => {
|
|
1396
|
+
let stand = null;
|
|
1397
|
+
for (let ms = 0; ms < 4000; ms += 100) {
|
|
1398
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1399
|
+
stand = await js(karteJs(pfad));
|
|
1400
|
+
const k = stand;
|
|
1401
|
+
if (k.zieleSichtbar && k.aufgeklappt === 'true')
|
|
1402
|
+
break;
|
|
1403
|
+
}
|
|
1404
|
+
return stand;
|
|
1405
|
+
};
|
|
1406
|
+
const ergebnis = {};
|
|
1407
|
+
try {
|
|
1408
|
+
await js(`new Promise(function (r) { var n = 0; (function w() { if (document.querySelector('dialog[open]') || ++n > 30) r(); else setTimeout(w, 50); })(); })`);
|
|
1409
|
+
await js(`document.querySelectorAll('dialog[open]').forEach(function (d) { d.close(); });
|
|
1410
|
+
document.querySelector('[data-ansicht="mcp"]').click(); true`);
|
|
1411
|
+
ergebnis.fall1Vorher = await js(karteJs(pfad1));
|
|
1412
|
+
await signalSchreiben(umgebung.hmgrHome, { art: 'verzeichnis-zeigen', pfad: pfad1, zeit: Date.now() });
|
|
1413
|
+
ergebnis.fall1 = await warteAufSprung(pfad1);
|
|
1414
|
+
// Fall 2: Dialog offen, andere Ansicht, Signal für ein anderes Verzeichnis.
|
|
1415
|
+
await js(`document.querySelector('[data-ansicht="mcp"]').click();
|
|
1416
|
+
document.getElementById('d-einstellungen').showModal(); true`);
|
|
1417
|
+
ergebnis.fall2Vorher = await js(karteJs(pfad2));
|
|
1418
|
+
await signalSchreiben(umgebung.hmgrHome, { art: 'verzeichnis-zeigen', pfad: pfad2, zeit: Date.now() });
|
|
1419
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
1420
|
+
ergebnis.fall2Offen = await js(`({ dialogOffen: document.getElementById('d-einstellungen').open === true, ` +
|
|
1421
|
+
`ansichtMcp: document.getElementById('ansicht-mcp').hidden === false })`);
|
|
1422
|
+
ergebnis.fall2NachWarten = await js(karteJs(pfad2));
|
|
1423
|
+
await js(`document.getElementById('d-einstellungen').close(); true`);
|
|
1424
|
+
ergebnis.fall2 = await warteAufSprung(pfad2);
|
|
1425
|
+
}
|
|
1426
|
+
catch (fehler) {
|
|
1427
|
+
ergebnis.fehler = String(fehler);
|
|
1428
|
+
}
|
|
1429
|
+
console.log(`[smoke] Signal: ${JSON.stringify(ergebnis)}`);
|
|
1367
1430
|
})
|
|
1368
1431
|
.finally(() => app.quit());
|
|
1369
1432
|
}, 3000);
|
|
@@ -1404,6 +1467,26 @@ function fensterOeffnen() {
|
|
|
1404
1467
|
beobachter = undefined;
|
|
1405
1468
|
});
|
|
1406
1469
|
}
|
|
1470
|
+
// CLI-Signal (E110): `hmgr dir add` und Verwandte schreiben `gui-signal.json` ins hmgr-Home. Anders
|
|
1471
|
+
// als der Watcher oben ohne Entprellen und ohne Drossel; läuft auch im Smoke-Test (mit dessen
|
|
1472
|
+
// Wegwerf-Home), dort aber ohne Fokusklau.
|
|
1473
|
+
{
|
|
1474
|
+
const fenster = hauptfenster;
|
|
1475
|
+
signalBeobachter = signalBeobachten(umgebung.hmgrHome, (pfad) => {
|
|
1476
|
+
if (fenster.isDestroyed())
|
|
1477
|
+
return;
|
|
1478
|
+
if (!smoketest) {
|
|
1479
|
+
fensterNachVornHolen();
|
|
1480
|
+
if (process.platform === 'darwin')
|
|
1481
|
+
app.focus({ steal: true });
|
|
1482
|
+
}
|
|
1483
|
+
fenster.webContents.send('hmgr:verzeichnisZeigen', pfad);
|
|
1484
|
+
});
|
|
1485
|
+
fenster.on('closed', () => {
|
|
1486
|
+
signalBeobachter?.close();
|
|
1487
|
+
signalBeobachter = undefined;
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1407
1490
|
void hauptfenster.loadFile(path.join(hierVerzeichnis, 'renderer', 'index.html'));
|
|
1408
1491
|
}
|
|
1409
1492
|
const { autoUpdater } = electronUpdater;
|
package/dist/gui/preload.cjs
CHANGED
|
@@ -79,6 +79,11 @@ contextBridge.exposeInMainWorld('hmgr', {
|
|
|
79
79
|
onNeuLadenAnstossen: (hoerer) => {
|
|
80
80
|
ipcRenderer.on('hmgr:neuLadenAnstossen', () => hoerer());
|
|
81
81
|
},
|
|
82
|
+
/** Signal der CLI (E110): ein Arbeitsverzeichnis soll gezeigt werden. Main hat das Fenster schon
|
|
83
|
+
nach vorn geholt; kein Entprellen, keine Drossel. */
|
|
84
|
+
onVerzeichnisZeigen: (hoerer) => {
|
|
85
|
+
ipcRenderer.on('hmgr:verzeichnisZeigen', (_event, pfad) => hoerer(pfad));
|
|
86
|
+
},
|
|
82
87
|
/** Prüfung auf neue hmgr-Stände (E77), angestoßen bei jedem Aktualisieren-Lauf (E80: Start,
|
|
83
88
|
Intervall, Klick). Antwortet nicht, meldet sich später über onSelbstUpdate — Netz und Spawn
|
|
84
89
|
dauern länger als ein Knopfdruck. */
|
|
@@ -2707,7 +2707,7 @@
|
|
|
2707
2707
|
if (standLaeuft) { el.textContent = 'Prüft …'; return; }
|
|
2708
2708
|
if (standNeu !== null) {
|
|
2709
2709
|
el.appendChild(e('b', null, standNeu.zahlwort));
|
|
2710
|
-
el.appendChild(document.createTextNode(' ' + standNeu.anhang + ', ' + standZeit));
|
|
2710
|
+
el.appendChild(document.createTextNode((standNeu.anhang === '' ? '' : ' ' + standNeu.anhang) + ', ' + standZeit));
|
|
2711
2711
|
return;
|
|
2712
2712
|
}
|
|
2713
2713
|
var text = 'Noch nicht geprüft';
|
|
@@ -2738,6 +2738,15 @@
|
|
|
2738
2738
|
(c.geaenderteSkills || []).forEach(function (name) { geaenderteSkills[name] = true; });
|
|
2739
2739
|
});
|
|
2740
2740
|
var skillZahl = Object.keys(geaenderteSkills).length;
|
|
2741
|
+
// Neue und entfernte Skills (E109): über alle Checkouts, doppelte Namen zählen einmal.
|
|
2742
|
+
var neueSkills = {};
|
|
2743
|
+
var entfernteSkills = {};
|
|
2744
|
+
ergebnis.checkouts.forEach(function (c) {
|
|
2745
|
+
(c.neueSkills || []).forEach(function (name) { neueSkills[name] = true; });
|
|
2746
|
+
(c.entfernteSkills || []).forEach(function (name) { entfernteSkills[name] = true; });
|
|
2747
|
+
});
|
|
2748
|
+
var neueZahl = Object.keys(neueSkills).length;
|
|
2749
|
+
var entferntZahl = Object.keys(entfernteSkills).length;
|
|
2741
2750
|
// Ein nachgezogenes Bundle ist Neues wie ein neuer Commit (E69, F64=O1): sonst bliebe die
|
|
2742
2751
|
// Stand-Zeile still, obwohl sich am Ziel gerade Links geändert haben.
|
|
2743
2752
|
var bundlesNeu = (ergebnis.bundles || []).filter(function (b) { return b.ergebnis === 'aktualisiert'; }).length;
|
|
@@ -2748,10 +2757,15 @@
|
|
|
2748
2757
|
var automatisch = ausloeser !== 'klick';
|
|
2749
2758
|
if ((neue === 0 && bundlesNeu === 0) || !automatisch || !$('ansicht-update').hidden) {
|
|
2750
2759
|
standNeu = null;
|
|
2760
|
+
} else if (neueZahl > 0) {
|
|
2761
|
+
// Ein neuer Skill ist die größte Neuigkeit (E109), danach geänderte, danach entfernte.
|
|
2762
|
+
standNeu = { zahlwort: neueZahl === 1 ? '1 neuer Skill' : neueZahl + ' neue Skills', anhang: '' };
|
|
2751
2763
|
} else if (skillZahl > 0) {
|
|
2752
2764
|
// Ist die Skill-Zahl bekannt, sagt sie mehr als die Zahl der Quellen (E70). Sonst bleibt es
|
|
2753
2765
|
// beim alten Signal: eine Quelle kann Neues haben, ohne dass ein Skill davon betroffen ist.
|
|
2754
2766
|
standNeu = { zahlwort: zahlwort(skillZahl, 'Skill', 'Skills'), anhang: 'aktualisiert' };
|
|
2767
|
+
} else if (entferntZahl > 0) {
|
|
2768
|
+
standNeu = { zahlwort: zahlwort(entferntZahl, 'Skill', 'Skills'), anhang: 'entfernt' };
|
|
2755
2769
|
} else {
|
|
2756
2770
|
standNeu = {
|
|
2757
2771
|
zahlwort: neue > 0 ? zahlwort(neue, 'Quelle', 'Quellen') : zahlwort(bundlesNeu, 'Bundle', 'Bundles'),
|
|
@@ -2790,6 +2804,7 @@
|
|
|
2790
2804
|
knopf.classList.remove('laeuft');
|
|
2791
2805
|
// Scheiterte der Lauf, bliebe sonst „Prüft …" stehen.
|
|
2792
2806
|
if ($('standtext').textContent === 'Prüft …') standText();
|
|
2807
|
+
reloadWartendeFreigeben();
|
|
2793
2808
|
}
|
|
2794
2809
|
}
|
|
2795
2810
|
|
|
@@ -2869,6 +2884,21 @@
|
|
|
2869
2884
|
// überholt. Fehler landen wie sonst über rufe() in der Meldungsleiste, kein stiller Absturz.
|
|
2870
2885
|
var stillerReloadLaeuft = false;
|
|
2871
2886
|
var stillerReloadVorgemerkt = false;
|
|
2887
|
+
// Wer auf das Ende des Neuladens wartet (E110): die Auflöser werden frei, sobald weder ein
|
|
2888
|
+
// stiller Reload noch ein Aktualisieren-Lauf (der mit ladeAlles endet) mehr läuft.
|
|
2889
|
+
var reloadWartende = [];
|
|
2890
|
+
function reloadWartendeFreigeben() {
|
|
2891
|
+
if (stillerReloadLaeuft || stillerReloadVorgemerkt || $('updatestart').disabled) return;
|
|
2892
|
+
var liste = reloadWartende;
|
|
2893
|
+
reloadWartende = [];
|
|
2894
|
+
liste.forEach(function (aufloeser) { aufloeser(); });
|
|
2895
|
+
}
|
|
2896
|
+
function stillerReloadAbwarten() {
|
|
2897
|
+
return new Promise(function (aufloeser) {
|
|
2898
|
+
reloadWartende.push(aufloeser);
|
|
2899
|
+
void stillerReload();
|
|
2900
|
+
});
|
|
2901
|
+
}
|
|
2872
2902
|
async function stillerReload() {
|
|
2873
2903
|
// Läuft gerade ein Aktualisieren-Lauf, wird nicht parallel geladen: er endet ohnehin mit
|
|
2874
2904
|
// ladeAlles und nimmt die Änderung mit. Sonst bauten zwei Läufe gleichzeitig die Ansicht auf.
|
|
@@ -2887,24 +2917,43 @@
|
|
|
2887
2917
|
if (stillerReloadVorgemerkt) {
|
|
2888
2918
|
stillerReloadVorgemerkt = false;
|
|
2889
2919
|
void stillerReload();
|
|
2920
|
+
} else {
|
|
2921
|
+
reloadWartendeFreigeben();
|
|
2890
2922
|
}
|
|
2891
2923
|
}
|
|
2892
2924
|
}
|
|
2893
2925
|
window.hmgr.onNeuLadenAnstossen(function () { void stillerReload(); });
|
|
2894
2926
|
|
|
2927
|
+
// CLI-Signal (E110, nach `hmgr dir add` und Verwandten): sofort neu laden, dann zur Karte des
|
|
2928
|
+
// Verzeichnisses springen, auch aus einer anderen Ansicht. Steht ein Dialog offen, bleibt er
|
|
2929
|
+
// unangetastet und der Sprung wartet, bis kein Dialog mehr offen ist; ein neueres Signal ersetzt
|
|
2930
|
+
// die Vormerkung. Die Meldungsleiste bleibt, wie sie ist.
|
|
2931
|
+
var signalPfadVorgemerkt = null;
|
|
2932
|
+
function signalSprungEinloesen() {
|
|
2933
|
+
if (signalPfadVorgemerkt === null) return;
|
|
2934
|
+
if (document.querySelector('dialog[open]')) return;
|
|
2935
|
+
var pfad = signalPfadVorgemerkt;
|
|
2936
|
+
signalPfadVorgemerkt = null;
|
|
2937
|
+
// Eine E84-Vormerkung auf denselben Pfad ist mit diesem Sprung erledigt.
|
|
2938
|
+
if (zielSprungVorgemerkt === pfad) zielSprungVorgemerkt = null;
|
|
2939
|
+
zeigeAnsicht('ziele');
|
|
2940
|
+
springeZuVerzeichnis(pfad);
|
|
2941
|
+
}
|
|
2942
|
+
window.hmgr.onVerzeichnisZeigen(function (pfad) {
|
|
2943
|
+
signalPfadVorgemerkt = pfad;
|
|
2944
|
+
void stillerReloadAbwarten().then(signalSprungEinloesen);
|
|
2945
|
+
});
|
|
2946
|
+
// `close` bubbelt nicht, der Capture-Listener am Dokument sieht aber jeden geschlossenen Dialog.
|
|
2947
|
+
// Der Aufschub um einen Takt lässt einen unmittelbar folgenden Dialog zuerst aufgehen.
|
|
2948
|
+
document.addEventListener('close', function () { setTimeout(signalSprungEinloesen, 0); }, true);
|
|
2949
|
+
|
|
2895
2950
|
/**
|
|
2896
2951
|
* Eine Ereigniszeile im Verlauf (E80): Marke + Text, Muster wie die frühere baueBericht-Zeile
|
|
2897
|
-
*
|
|
2952
|
+
* und die CLI-Ausgabe (verlaufEreignisZeile, src/cli/main.ts). Checkouts stellt
|
|
2953
|
+
* verlaufQuellenGruppen dar (E109).
|
|
2898
2954
|
*/
|
|
2899
2955
|
function verlaufEreignisZeile(li, ereignis) {
|
|
2900
|
-
if (ereignis.art === '
|
|
2901
|
-
li.appendChild(e('span', 'marke-ok', 'aktualisiert'));
|
|
2902
|
-
var skills = ereignis.skills.length > 0
|
|
2903
|
-
? ' · ' + zahlwort(ereignis.skills.length, 'Skill', 'Skills') + ': ' + ereignis.skills.join(', ')
|
|
2904
|
-
: '';
|
|
2905
|
-
var stand = ' (' + ereignis.von.slice(0, 7) + ' → ' + ereignis.nach.slice(0, 7) + ')';
|
|
2906
|
-
li.appendChild(e('span', 'pfad', ereignis.quelle + ' @ ' + ereignis.ref + skills + stand));
|
|
2907
|
-
} else if (ereignis.art === 'bundle') {
|
|
2956
|
+
if (ereignis.art === 'bundle') {
|
|
2908
2957
|
li.appendChild(e('span', 'marke-ok', 'Bundle'));
|
|
2909
2958
|
var teile = [];
|
|
2910
2959
|
if (ereignis.gelegt.length > 0) teile.push('gelegt ' + ereignis.gelegt.join(', '));
|
|
@@ -2942,6 +2991,89 @@
|
|
|
2942
2991
|
}
|
|
2943
2992
|
}
|
|
2944
2993
|
|
|
2994
|
+
/**
|
|
2995
|
+
* Anzeigename einer Quelle im Verlauf (E109). Baut quelleVerlaufName (src/kern/modell.ts) nach,
|
|
2996
|
+
* der Renderer kann die Kern-Datei nicht importieren: Quelle aus QUELLEN über die Identität
|
|
2997
|
+
* (Host/Besitzer/Repo, E36) suchen; fehlt sie oder hat sie keinen Namen, `besitzer/repo` aus der
|
|
2998
|
+
* URL, bei einer lokalen Quelle der letzte Pfadteil. Ein Ref außer main/master steht in Klammern.
|
|
2999
|
+
*/
|
|
3000
|
+
function verlaufQuellenOrt(text) {
|
|
3001
|
+
var t = text.trim().replace(/\/+$/, '');
|
|
3002
|
+
var segmente = null;
|
|
3003
|
+
var host = '';
|
|
3004
|
+
var m;
|
|
3005
|
+
if (t.indexOf('://') >= 0) {
|
|
3006
|
+
try {
|
|
3007
|
+
var u = new URL(t);
|
|
3008
|
+
segmente = decodeURIComponent(u.pathname).split('/');
|
|
3009
|
+
host = u.protocol === 'file:' ? 'local' : u.hostname;
|
|
3010
|
+
} catch (x) { return null; }
|
|
3011
|
+
} else if (!/^[A-Za-z]:[\\/]/.test(t) && (m = /^(?:[^@/]+@)?([^@/:]+):(.+)$/.exec(t))) {
|
|
3012
|
+
host = m[1];
|
|
3013
|
+
segmente = m[2].split('/');
|
|
3014
|
+
} else {
|
|
3015
|
+
return null;
|
|
3016
|
+
}
|
|
3017
|
+
segmente = segmente.filter(function (x) { return x.length > 0; });
|
|
3018
|
+
var repo = (segmente[segmente.length - 1] || 'repo').replace(/\.git$/, '');
|
|
3019
|
+
return { host: host.toLowerCase(), besitzer: segmente[segmente.length - 2] || '_', repo: repo };
|
|
3020
|
+
}
|
|
3021
|
+
function verlaufQuellenName(text, ref) {
|
|
3022
|
+
var ort = verlaufQuellenOrt(text);
|
|
3023
|
+
var schluessel = ort === null ? text : ort.host + '/' + ort.besitzer + '/' + ort.repo;
|
|
3024
|
+
var gefunden = QUELLEN.filter(function (q) {
|
|
3025
|
+
var qt = q.text;
|
|
3026
|
+
var qo = verlaufQuellenOrt(qt);
|
|
3027
|
+
return (qo === null ? qt : qo.host + '/' + qo.besitzer + '/' + qo.repo) === schluessel;
|
|
3028
|
+
})[0];
|
|
3029
|
+
var name;
|
|
3030
|
+
if (gefunden && gefunden.gespeichert && gefunden.gespeichert.trim().length > 0) name = gefunden.gespeichert;
|
|
3031
|
+
else if (ort !== null && ort.besitzer !== '_') name = ort.besitzer + '/' + ort.repo;
|
|
3032
|
+
else if (ort !== null) name = ort.repo;
|
|
3033
|
+
else {
|
|
3034
|
+
var teile = text.replace(/[\/\\]+$/, '').split(/[\/\\]/).filter(function (x) { return x.length > 0; });
|
|
3035
|
+
name = teile[teile.length - 1] || text;
|
|
3036
|
+
}
|
|
3037
|
+
return ref === 'main' || ref === 'master' ? name : name + ' (' + ref + ')';
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
/**
|
|
3041
|
+
* Die Checkout-Ereignisse eines Laufs (E109): je Quelle mit Skill-Änderung eine Gruppe (Name,
|
|
3042
|
+
* darunter Neu/Entfernt/Geändert, leere fallen weg), dieselbe Darstellung wie `hmgr log`
|
|
3043
|
+
* (src/cli/verlauf-anzeige.ts). Gibt die Namen der Quellen ohne Skill-Änderung zurück, aus
|
|
3044
|
+
* denen der Aufrufer die Sammelzeile baut; die Gruppen hängt sie an `ul` an.
|
|
3045
|
+
*/
|
|
3046
|
+
function verlaufQuellenGruppen(ul, ereignisse) {
|
|
3047
|
+
var ohne = [];
|
|
3048
|
+
var gruppen = 0;
|
|
3049
|
+
ereignisse.forEach(function (ereignis) {
|
|
3050
|
+
if (ereignis.art !== 'checkout') return;
|
|
3051
|
+
var name = verlaufQuellenName(ereignis.quelle, ereignis.ref);
|
|
3052
|
+
var teile = [
|
|
3053
|
+
['Neu', ereignis.neu || []],
|
|
3054
|
+
['Entfernt', ereignis.entfernt || []],
|
|
3055
|
+
['Geändert', ereignis.skills || []],
|
|
3056
|
+
].filter(function (t) { return t[1].length > 0; });
|
|
3057
|
+
if (teile.length === 0) {
|
|
3058
|
+
if (ohne.indexOf(name) < 0) ohne.push(name);
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
gruppen += 1;
|
|
3062
|
+
var li = e('li', 'quellengruppe');
|
|
3063
|
+
li.appendChild(e('b', null, name));
|
|
3064
|
+
var innen = e('ul', 'gruppenzeilen');
|
|
3065
|
+
teile.forEach(function (t) {
|
|
3066
|
+
var zeile = e('li');
|
|
3067
|
+
zeile.appendChild(e('span', 'marke-ok', t[0]));
|
|
3068
|
+
zeile.appendChild(e('span', 'pfad', t[1].join(', ')));
|
|
3069
|
+
innen.appendChild(zeile);
|
|
3070
|
+
});
|
|
3071
|
+
li.appendChild(innen);
|
|
3072
|
+
ul.appendChild(li);
|
|
3073
|
+
});
|
|
3074
|
+
return { ohne: ohne, gruppen: gruppen };
|
|
3075
|
+
}
|
|
3076
|
+
|
|
2945
3077
|
/**
|
|
2946
3078
|
* Baut das Changelog in der Update-Ansicht aus ~/.hmgr/verlauf.json (E80): ein Block je Lauf,
|
|
2947
3079
|
* neueste zuerst, das Etikett der lokale Zeitpunkt, darunter eine Zeile je Ereignis. Die Datei
|
|
@@ -2959,15 +3091,29 @@
|
|
|
2959
3091
|
var block = e('div', 'block');
|
|
2960
3092
|
var zeit = new Date(eintrag.zeit);
|
|
2961
3093
|
var zwei = function (n) { return String(n).padStart(2, '0'); };
|
|
2962
|
-
|
|
2963
|
-
|
|
3094
|
+
// „Heute, 00:26", „Gestern, 22:58", sonst „06.10.2026, 17:42" (E109), lokale Zeit.
|
|
3095
|
+
var uhr = zwei(zeit.getHours()) + ':' + zwei(zeit.getMinutes());
|
|
3096
|
+
var jetzt = new Date();
|
|
3097
|
+
var tagStart = function (d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); };
|
|
3098
|
+
var etikett = tagStart(zeit) === tagStart(jetzt) ? 'Heute, ' + uhr
|
|
3099
|
+
: tagStart(zeit) === new Date(jetzt.getFullYear(), jetzt.getMonth(), jetzt.getDate() - 1).getTime() ? 'Gestern, ' + uhr
|
|
3100
|
+
: zwei(zeit.getDate()) + '.' + zwei(zeit.getMonth() + 1) + '.' + zeit.getFullYear() + ', ' + uhr;
|
|
2964
3101
|
block.appendChild(e('span', 'etikett', etikett));
|
|
2965
3102
|
var ul = e('ul');
|
|
3103
|
+
// Erst die Quellengruppen, dann die übrigen Ereignisse, zuletzt die Sammelzeile (E109).
|
|
3104
|
+
var gruppen = verlaufQuellenGruppen(ul, eintrag.ereignisse);
|
|
3105
|
+
var andere = gruppen.gruppen > 0;
|
|
2966
3106
|
eintrag.ereignisse.forEach(function (ereignis) {
|
|
3107
|
+
if (ereignis.art === 'checkout') return;
|
|
3108
|
+
andere = true;
|
|
2967
3109
|
var li = e('li');
|
|
2968
3110
|
verlaufEreignisZeile(li, ereignis);
|
|
2969
3111
|
ul.appendChild(li);
|
|
2970
3112
|
});
|
|
3113
|
+
if (gruppen.ohne.length > 0) {
|
|
3114
|
+
ul.appendChild(e('li', 'sammelzeile', (andere ? 'Außerdem neuer' : 'Neuer') + ' Stand bei '
|
|
3115
|
+
+ gruppen.ohne.join(', ') + ', ohne Änderung an deinen Skills.'));
|
|
3116
|
+
}
|
|
2971
3117
|
block.appendChild(ul);
|
|
2972
3118
|
box.appendChild(block);
|
|
2973
3119
|
});
|
|
@@ -490,6 +490,11 @@ img.hsymbol{border-radius:3px}
|
|
|
490
490
|
.block .marke-ok{color:var(--emaille-hoch);font-weight:700}
|
|
491
491
|
.block .marke-aus{color:var(--messing-tief);font-weight:700}
|
|
492
492
|
.block .pfad{color:var(--tinte-weich)}
|
|
493
|
+
/* E109: je Quelle eine Gruppe mit Neu/Entfernt/Geändert, Sammelzeile für Quellen ohne Skill-Änderung. */
|
|
494
|
+
.block li.quellengruppe{display:block}
|
|
495
|
+
.block .gruppenzeilen{margin-top:5px;gap:3px;padding-left:13px;border-left:2px solid var(--strich)}
|
|
496
|
+
.block .gruppenzeilen li{grid-template-columns:5.5em minmax(0,1fr);gap:10px}
|
|
497
|
+
.block li.sammelzeile{display:block;color:var(--tinte-weich)}
|
|
493
498
|
/* E108: Änderungen unter „hmgr X installiert", aufklappbar wie die Notizen der MCP-Karte. */
|
|
494
499
|
.block .aenderungen-platz{grid-column:1 / -1}
|
|
495
500
|
.block .aenderungen-platz:empty{display:none}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Beobachter für das CLI-Signal (E110), ohne Electron-Import (testbar wie beobachter.ts). Beobachtet
|
|
2
|
+
// wird das Verzeichnis hmgrHome mit Filter auf `gui-signal.json` (aus demselben Grund wie in
|
|
3
|
+
// beobachter.ts: die Datei wird per rename ersetzt, ein Watcher auf die Datei würde taub).
|
|
4
|
+
// Anders als beobachter.ts kein Entprellen und kein Inhaltsvergleich: jedes frische Signal zählt.
|
|
5
|
+
import { watch } from 'node:fs';
|
|
6
|
+
import { SIGNAL_DATEI, signalLesen } from "../adapter/gui-signal.js";
|
|
7
|
+
export function signalBeobachten(hmgrHome, callback, optionen = {}) {
|
|
8
|
+
const sammelnMs = optionen.sammelnMs ?? 30;
|
|
9
|
+
const maxAlterMs = optionen.maxAlterMs ?? 10_000;
|
|
10
|
+
let timer;
|
|
11
|
+
let geschlossen = false;
|
|
12
|
+
let zuletzt;
|
|
13
|
+
// Eine beim Start vorhandene Datei gilt als schon gesehen. Das Alter allein reicht nicht: auf macOS
|
|
14
|
+
// liefert fs.watch kurz nach dem Anlegen auch Events für Schreibvorgänge kurz davor.
|
|
15
|
+
const startStand = signalLesen(hmgrHome).then((signal) => {
|
|
16
|
+
if (signal !== undefined)
|
|
17
|
+
zuletzt = `${signal.zeit}\u0000${signal.pfad}`;
|
|
18
|
+
});
|
|
19
|
+
async function auswerten() {
|
|
20
|
+
await startStand;
|
|
21
|
+
const signal = await signalLesen(hmgrHome);
|
|
22
|
+
if (geschlossen || signal === undefined)
|
|
23
|
+
return;
|
|
24
|
+
if (Date.now() - signal.zeit > maxAlterMs)
|
|
25
|
+
return;
|
|
26
|
+
const schluessel = `${signal.zeit}\u0000${signal.pfad}`;
|
|
27
|
+
if (schluessel === zuletzt)
|
|
28
|
+
return;
|
|
29
|
+
zuletzt = schluessel;
|
|
30
|
+
callback(signal.pfad);
|
|
31
|
+
}
|
|
32
|
+
let watcher;
|
|
33
|
+
try {
|
|
34
|
+
watcher = watch(hmgrHome, (_ereignis, name) => {
|
|
35
|
+
if (geschlossen)
|
|
36
|
+
return;
|
|
37
|
+
if (name !== null && name !== SIGNAL_DATEI)
|
|
38
|
+
return;
|
|
39
|
+
if (timer !== undefined)
|
|
40
|
+
return; // schon ein Lesen eingeplant
|
|
41
|
+
timer = setTimeout(() => {
|
|
42
|
+
timer = undefined;
|
|
43
|
+
void auswerten();
|
|
44
|
+
}, sammelnMs);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Verzeichnis fehlt: unbeobachtet.
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
close() {
|
|
52
|
+
geschlossen = true;
|
|
53
|
+
if (timer !== undefined)
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
watcher?.close();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1322,7 +1322,8 @@ export class Anwendungsfaelle {
|
|
|
1322
1322
|
hinweis = `Kein Zeiger unter ${verzeichnis}, alles bleibt lokal (E26).`;
|
|
1323
1323
|
}
|
|
1324
1324
|
const zustand = await this.p.zustand.lesen();
|
|
1325
|
-
|
|
1325
|
+
const neu = !zustand.arbeitsverzeichnisse.includes(verzeichnis);
|
|
1326
|
+
if (neu)
|
|
1326
1327
|
zustand.arbeitsverzeichnisse.push(verzeichnis);
|
|
1327
1328
|
if (zeigerPfad !== undefined) {
|
|
1328
1329
|
const gebunden = zeigerPfad;
|
|
@@ -1338,6 +1339,7 @@ export class Anwendungsfaelle {
|
|
|
1338
1339
|
await this.p.zustand.schreiben(zustand);
|
|
1339
1340
|
return {
|
|
1340
1341
|
pfad: verzeichnis,
|
|
1342
|
+
neu,
|
|
1341
1343
|
...(zeigerPfad === undefined ? {} : { zeigerPfad }),
|
|
1342
1344
|
...(hinweis === undefined ? {} : { hinweis }),
|
|
1343
1345
|
};
|
|
@@ -1931,10 +1933,14 @@ export class Anwendungsfaelle {
|
|
|
1931
1933
|
// Der Quellen-Port baut Checkouts von der Platte (remote.origin.url), ohne den Zustand zu
|
|
1932
1934
|
// befragen: der Anzeigename fehlt dort. Für den Bericht kommt er aus der geführten Quelle
|
|
1933
1935
|
// (E59), sonst zeigte eine umbenannte Quelle hier wieder ihren abgeleiteten Namen.
|
|
1936
|
+
const { neu, entfernt } = await this.neueUndEntfernteSkills(checkout, aktualisierung);
|
|
1937
|
+
const geaendert = await this.geaenderteSkills(checkout, aktualisierung);
|
|
1934
1938
|
checkouts.push({
|
|
1935
1939
|
checkout: { ...checkout, quelle: quelleMitNamen(checkout.quelle, gefuehrt.name) },
|
|
1936
1940
|
aktualisierung,
|
|
1937
|
-
geaenderteSkills:
|
|
1941
|
+
geaenderteSkills: geaendert.filter((name) => !neu.includes(name)),
|
|
1942
|
+
neueSkills: neu,
|
|
1943
|
+
entfernteSkills: entfernt,
|
|
1938
1944
|
});
|
|
1939
1945
|
// `aktualisieren` holt für einen Checkout dieselben Refs wie `refsNachziehen` (fetch auf dem
|
|
1940
1946
|
// Basis-Clone). Ist das gelaufen, braucht der Bundle-Abgleich unten die Quelle nicht ein
|
|
@@ -2003,7 +2009,7 @@ export class Anwendungsfaelle {
|
|
|
2003
2009
|
*/
|
|
2004
2010
|
aktualisierungsEreignisse(checkouts, pluginFehler, bundles, aufraeumen, doctorErgebnis) {
|
|
2005
2011
|
const ereignisse = [];
|
|
2006
|
-
for (const { checkout, aktualisierung, geaenderteSkills } of checkouts) {
|
|
2012
|
+
for (const { checkout, aktualisierung, geaenderteSkills, neueSkills, entfernteSkills } of checkouts) {
|
|
2007
2013
|
if (aktualisierung.ergebnis === 'aktualisiert')
|
|
2008
2014
|
ereignisse.push({
|
|
2009
2015
|
art: 'checkout',
|
|
@@ -2012,6 +2018,8 @@ export class Anwendungsfaelle {
|
|
|
2012
2018
|
von: aktualisierung.von,
|
|
2013
2019
|
nach: aktualisierung.nach,
|
|
2014
2020
|
skills: geaenderteSkills,
|
|
2021
|
+
neu: neueSkills,
|
|
2022
|
+
entfernt: entfernteSkills,
|
|
2015
2023
|
});
|
|
2016
2024
|
else if (aktualisierung.ergebnis === 'fehlgeschlagen')
|
|
2017
2025
|
ereignisse.push({
|
|
@@ -2138,6 +2146,30 @@ export class Anwendungsfaelle {
|
|
|
2138
2146
|
namen.add(skill.name);
|
|
2139
2147
|
return [...namen].sort((a, b) => a.localeCompare(b, 'de'));
|
|
2140
2148
|
}
|
|
2149
|
+
/**
|
|
2150
|
+
* Neue und entfernte Skills zwischen altem und neuem Commit (E109): Vergleich der Skill-Namen
|
|
2151
|
+
* (E32) von `skills(quelle, von)` und `skills(quelle, nach)`, für alle Skills der Quelle. Eine
|
|
2152
|
+
* Umbenennung erscheint als beides. Scheitert einer der beiden Aufrufe, bleiben beide Listen
|
|
2153
|
+
* leer: der Bericht wird ungenauer, der Lauf nicht kaputt.
|
|
2154
|
+
*/
|
|
2155
|
+
async neueUndEntfernteSkills(checkout, aktualisierung) {
|
|
2156
|
+
const leer = { neu: [], entfernt: [] };
|
|
2157
|
+
if (aktualisierung.ergebnis !== 'aktualisiert')
|
|
2158
|
+
return leer;
|
|
2159
|
+
const [vorher, nachher] = await Promise.all([
|
|
2160
|
+
this.p.quellen.skills(checkout.quelle, aktualisierung.von).catch(() => undefined),
|
|
2161
|
+
this.p.quellen.skills(checkout.quelle, aktualisierung.nach).catch(() => undefined),
|
|
2162
|
+
]);
|
|
2163
|
+
if (vorher === undefined || nachher === undefined)
|
|
2164
|
+
return leer;
|
|
2165
|
+
const alt = new Set(vorher.map((s) => s.name));
|
|
2166
|
+
const neuNamen = new Set(nachher.map((s) => s.name));
|
|
2167
|
+
const sortiert = (namen) => [...namen].sort((a, b) => a.localeCompare(b, 'de'));
|
|
2168
|
+
return {
|
|
2169
|
+
neu: sortiert([...neuNamen].filter((n) => !alt.has(n))),
|
|
2170
|
+
entfernt: sortiert([...alt].filter((n) => !neuNamen.has(n))),
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2141
2173
|
/**
|
|
2142
2174
|
* Pfade, in die ein Link zeigt. Gelesen wird wie beim Ist-Zustand (E19): die Ziele der
|
|
2143
2175
|
* vorhandenen Harnesses, die Arbeitsverzeichnisse aus dem Zustand. Ein Link, dessen Quelle nicht
|
package/dist/kern/modell.js
CHANGED
|
@@ -270,6 +270,24 @@ export function quelleMitNamen(quelle, name) {
|
|
|
270
270
|
return quelle;
|
|
271
271
|
return quelle.art === 'git' ? { art: 'git', url: quelle.url, name } : { art: 'lokal', pfad: quelle.pfad, name };
|
|
272
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Anzeigename einer Quelle im Verlauf (E109), aus dem Text, den das Ereignis trägt (URL oder Pfad,
|
|
275
|
+
* `quelleAlsText`). Gefunden wird die Quelle in der Quellenliste über die Identität (E36), damit
|
|
276
|
+
* SSH- und HTTPS-Schreibweise derselben Quelle zusammenfallen; beide Quellenarten werden probiert,
|
|
277
|
+
* weil der Text nicht verrät, was er war. Fehlt die Quelle oder hat sie keinen Namen, steht
|
|
278
|
+
* `besitzer/repo` aus der URL, bei einer lokalen Quelle (oder einer URL ohne Besitzer) der letzte
|
|
279
|
+
* Pfadteil. GUI und CLI nutzen dieselbe Regel (der Renderer baut sie nach, renderer.js).
|
|
280
|
+
*/
|
|
281
|
+
export function quelleVerlaufName(text, quellen) {
|
|
282
|
+
const schluessel = new Set([quelleSchluessel({ art: 'git', url: text }), quelleSchluessel({ art: 'lokal', pfad: text })]);
|
|
283
|
+
const gefunden = quellen.find((q) => schluessel.has(quelleSchluessel(q)));
|
|
284
|
+
if (gefunden?.name !== undefined && gefunden.name.trim().length > 0)
|
|
285
|
+
return gefunden.name;
|
|
286
|
+
const ort = urlOrt(text);
|
|
287
|
+
if (ort !== undefined && ort.besitzer !== '_')
|
|
288
|
+
return `${ort.besitzer}/${ort.repo}`;
|
|
289
|
+
return ort !== undefined ? ort.repo : letztesSegment(text);
|
|
290
|
+
}
|
|
273
291
|
/** Ein Anzeigename, den schon eine andere geführte Quelle trägt (E59): Namen sind eindeutig. */
|
|
274
292
|
export class QuelleNameBelegt extends Error {
|
|
275
293
|
angefragterName;
|