hmgr 0.0.41 → 0.0.42
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/aenderungen.json +4 -0
- package/dist/cli/hilfe.js +22 -10
- package/dist/cli/main.js +53 -23
- package/dist/cli/verlauf-anzeige.js +61 -0
- package/dist/gui/main.js +4 -1
- package/dist/gui/renderer/renderer.js +117 -12
- package/dist/gui/renderer/styles.css +5 -0
- package/dist/kern/anwendungsfaelle.js +32 -2
- package/dist/kern/modell.js +18 -0
- package/package.json +1 -1
package/dist/aenderungen.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"fassung": 1,
|
|
3
3
|
"versionen": {
|
|
4
|
+
"0.0.42": [
|
|
5
|
+
"Der Verlauf meldet jetzt auch neue und entfernte Skills einer Quelle, nicht nur geänderte.",
|
|
6
|
+
"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."
|
|
7
|
+
],
|
|
4
8
|
"0.0.41": [
|
|
5
9
|
"Im Verlauf steht unter „hmgr … installiert\" jetzt aufklappbar, was sich in der neuen Version geändert hat."
|
|
6
10
|
]
|
package/dist/cli/hilfe.js
CHANGED
|
@@ -496,10 +496,16 @@ damit nichts verloren geht. Lokale Quellen haben keinen Checkout und bleiben unb
|
|
|
496
496
|
Quelle ohne eigenen Checkout wird dabei aufgefrischt (E85): ihr Katalog zeigt danach, was neu im
|
|
497
497
|
Remote liegt, auch ohne dass irgendwo ein Checkout von ihr existiert.
|
|
498
498
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
499
|
+
Aktualisierte Checkouts erscheinen je Quelle als Gruppe (E109): der Name der Quelle aus
|
|
500
|
+
hmgr source list, darunter „Neu", „Entfernt" und „Geändert" mit den Skill-Namen, leere Zeilen
|
|
501
|
+
fallen weg. Neu und entfernt gelten für alle Skills der Quelle (der Vergleich von altem und neuem
|
|
502
|
+
Commit nach dem Namen aus der SKILL.md, eine Umbenennung zeigt beides), „Geändert" nur für Skills,
|
|
503
|
+
die in diesem Checkout ausgecheckt sind (E70). Liegt der Checkout auf einem anderen Ref als main
|
|
504
|
+
oder master, steht er in Klammern hinter dem Namen, etwa „cmux (nightly)". Quellen, bei denen sich
|
|
505
|
+
an Skills nichts getan hat (README, CI), nennt eine Zeile am Ende des Berichts: „Außerdem neuer
|
|
506
|
+
Stand bei cmux, ohne Änderung an deinen Skills." Commit-Kürzel zeigt update nicht mehr, sie stehen
|
|
507
|
+
in ~/.hmgr/verlauf.json. Ist eine Quelle nicht mehr in der Quellenliste, steht ihr Pfad
|
|
508
|
+
besitzer/repo statt des Namens.
|
|
503
509
|
|
|
504
510
|
Im selben Lauf gleicht update die installierten Bundles gegen ihre Definitionen ab (E69): eine
|
|
505
511
|
Definition, die inzwischen ein Mitglied mehr oder weniger nennt, wird in jedem Ziel nachgezogen, in
|
|
@@ -557,12 +563,18 @@ die hmgr update schreibt und die GUI bei jedem automatischen und angestoßenen L
|
|
|
557
563
|
liest. Ein Lauf ohne Ereignis (nichts Neues) steht nicht drin, die Datei ist ein Changelog, keine
|
|
558
564
|
vollständige Historie — sie hält die letzten 20 Einträge.
|
|
559
565
|
|
|
560
|
-
Je Eintrag steht eine Kopfzeile mit Datum und Uhrzeit,
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
+
Je Eintrag steht eine Kopfzeile mit Datum und Uhrzeit („Heute, 00:26", „Gestern, 22:58", sonst
|
|
567
|
+
„06.10.2026, 17:42"), darunter die Ereignisse. Aktualisierte Checkouts stehen je Quelle als Gruppe
|
|
568
|
+
(E109): der Name der Quelle, darunter „Neu", „Entfernt" und „Geändert" mit Skill-Namen, leere
|
|
569
|
+
Zeilen fallen weg; ein Ref außer main oder master steht in Klammern hinter dem Namen. Neu und
|
|
570
|
+
entfernt gelten für alle Skills der Quelle (eine Umbenennung zeigt beides), „Geändert" nur für
|
|
571
|
+
ausgecheckte Skills (E70). Quellen ohne Skill-Änderung sammelt eine Zeile am Ende des Laufs
|
|
572
|
+
(„Außerdem neuer Stand bei cmux, ohne Änderung an deinen Skills."). Dazwischen die übrigen
|
|
573
|
+
Ereignisse, je eine Zeile: ein nachgezogenes Bundle mit gelegten und entfernten Links, ein
|
|
574
|
+
aufgeräumter (gelöschter) Checkout, ein Fehler zu einer Quelle oder einem Bundle, dazu
|
|
575
|
+
hmgr-Ereignisse: ein Versionssprung von hmgr selbst (GUI oder CLI, erkannt beim Start bzw. bei
|
|
576
|
+
jedem CLI-Aufruf) oder ein gefundenes Update, das noch nicht installiert ist. Einträge aus der
|
|
577
|
+
Zeit vor E109 zeigen nur „Geändert". Plugins stehen nicht im Verlauf (siehe hmgr help update).
|
|
566
578
|
|
|
567
579
|
Unter einem „installiert"-Eintrag von hmgr stehen die Änderungen: je Version seit der zuvor
|
|
568
580
|
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";
|
|
@@ -764,16 +765,27 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
764
765
|
const { checkouts: liste, pluginFehler, bundles, aufraeumen, doctor, } = await anwendungsfaelle.aktualisieren({ ausloeser: 'cli', ...(nurQuelle === undefined ? {} : { quelle: nurQuelle }) });
|
|
765
766
|
if (liste.length === 0)
|
|
766
767
|
console.log('Keine Checkouts.');
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
768
|
+
const updateQuellen = liste.length === 0 ? [] : await anwendungsfaelle.quellen();
|
|
769
|
+
// Aktualisierte Checkouts als Quellengruppen, ohne Commit-Kürzel (E109, F125); Quellen ohne
|
|
770
|
+
// Skill-Änderung sammelt die Zeile am Ende. Die übrigen Zeilenarten nennen die Quelle beim Namen.
|
|
771
|
+
const { zeilen: updateGruppen, ohneAenderung: updateOhne } = checkoutGruppen(liste
|
|
772
|
+
.filter(({ aktualisierung }) => aktualisierung.ergebnis === 'aktualisiert')
|
|
773
|
+
.map(({ checkout, geaenderteSkills, neueSkills, entfernteSkills }) => ({
|
|
774
|
+
quelle: quelleAlsText(checkout.quelle),
|
|
775
|
+
ref: checkout.ref,
|
|
776
|
+
neu: neueSkills,
|
|
777
|
+
entfernt: entfernteSkills,
|
|
778
|
+
geaendert: geaenderteSkills,
|
|
779
|
+
})), updateQuellen);
|
|
780
|
+
for (const zeile of updateGruppen)
|
|
781
|
+
console.log(zeile);
|
|
782
|
+
let updateAndere = updateGruppen.length > 0;
|
|
783
|
+
for (const { checkout, aktualisierung } of liste) {
|
|
784
|
+
if (aktualisierung.ergebnis === 'aktualisiert')
|
|
785
|
+
continue;
|
|
786
|
+
updateAndere = true;
|
|
787
|
+
const kopf = quellenAnzeigeName({ quelle: quelleAlsText(checkout.quelle), ref: checkout.ref }, updateQuellen);
|
|
788
|
+
if (aktualisierung.ergebnis === 'unveraendert')
|
|
777
789
|
console.log(` unverändert ${kopf}`);
|
|
778
790
|
else if (aktualisierung.ergebnis === 'uebersprungen')
|
|
779
791
|
console.log(` übersprungen ${kopf} lokale Änderung (E15)`);
|
|
@@ -807,6 +819,14 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
807
819
|
else
|
|
808
820
|
console.log(` liegen lassen Arbeitsverzeichnis ${pfad} (nicht mehr auf der Platte, ohne Bestätigung nicht vergessen)`);
|
|
809
821
|
}
|
|
822
|
+
// Die Sammelzeile steht ganz am Ende des Berichts (E109); „Außerdem" nur, wenn sonst etwas im Lauf stand.
|
|
823
|
+
const updateSammel = sammelZeile(updateOhne, !updateAndere &&
|
|
824
|
+
pluginFehler.length === 0 &&
|
|
825
|
+
bundleBericht(bundles).length === 0 &&
|
|
826
|
+
aufraeumen.verwaiste.length === 0 &&
|
|
827
|
+
doctor.fehlendeArbeitsverzeichnisse.length === 0);
|
|
828
|
+
if (updateSammel !== undefined)
|
|
829
|
+
console.log(updateSammel);
|
|
810
830
|
// Zuletzt hmgr selbst (E77): wer schon aktualisiert, will auch wissen, dass sein hmgr alt ist.
|
|
811
831
|
// Verglichen wird die eigene Version (E72) gegen npm, gemeldet wird nur — installiert wird
|
|
812
832
|
// nichts. Offline oder Registry unerreichbar heißt keine Zeile, still: der Lauf selbst ist
|
|
@@ -858,9 +878,28 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
858
878
|
const eintraege = await anwendungsfaelle.verlauf();
|
|
859
879
|
if (eintraege.length === 0)
|
|
860
880
|
console.log('Noch keine Ereignisse.');
|
|
881
|
+
const quellenListe = eintraege.length === 0 ? [] : await anwendungsfaelle.quellen();
|
|
861
882
|
for (const eintrag of eintraege) {
|
|
862
883
|
console.log(verlaufZeitpunkt(eintrag.zeit));
|
|
884
|
+
// Erst die Quellengruppen, dann die übrigen Ereignisse, zuletzt die Sammelzeile (E109).
|
|
885
|
+
const vermerke = [];
|
|
886
|
+
for (const ereignis of eintrag.ereignisse)
|
|
887
|
+
if (ereignis.art === 'checkout')
|
|
888
|
+
vermerke.push({
|
|
889
|
+
quelle: ereignis.quelle,
|
|
890
|
+
ref: ereignis.ref,
|
|
891
|
+
neu: ereignis.neu ?? [],
|
|
892
|
+
entfernt: ereignis.entfernt ?? [],
|
|
893
|
+
geaendert: ereignis.skills,
|
|
894
|
+
});
|
|
895
|
+
const { zeilen: gruppenZeilen, ohneAenderung } = checkoutGruppen(vermerke, quellenListe);
|
|
896
|
+
for (const zeile of gruppenZeilen)
|
|
897
|
+
console.log(zeile);
|
|
898
|
+
let andere = gruppenZeilen.length > 0;
|
|
863
899
|
for (const ereignis of eintrag.ereignisse) {
|
|
900
|
+
if (ereignis.art === 'checkout')
|
|
901
|
+
continue;
|
|
902
|
+
andere = true;
|
|
864
903
|
console.log(` ${verlaufEreignisZeile(ereignis)}`);
|
|
865
904
|
// Unter einem Versionssprung die Commits der neuen Version(en), eingerückt (E108).
|
|
866
905
|
if (ereignis.art === 'hmgr-installiert') {
|
|
@@ -869,6 +908,9 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
869
908
|
console.log(zeile);
|
|
870
909
|
}
|
|
871
910
|
}
|
|
911
|
+
const sammel = sammelZeile(ohneAenderung, !andere);
|
|
912
|
+
if (sammel !== undefined)
|
|
913
|
+
console.log(sammel);
|
|
872
914
|
}
|
|
873
915
|
return 0;
|
|
874
916
|
}
|
|
@@ -1093,24 +1135,12 @@ function bundleBericht(eintraege) {
|
|
|
1093
1135
|
}
|
|
1094
1136
|
return zeilen;
|
|
1095
1137
|
}
|
|
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
1138
|
/** Je Version die Nummer und darunter ihre Commit-Betreffs, tiefer eingerückt als die Ereigniszeile (E108). */
|
|
1104
1139
|
function aenderungsZeilen(aenderungen) {
|
|
1105
1140
|
return aenderungen.flatMap((a) => [` ${a.version}`, ...a.commits.map((c) => ` ${c}`)]);
|
|
1106
1141
|
}
|
|
1107
|
-
/** Eine Ereigniszeile im Verlauf (E80), eingerückt unter der Kopfzeile ihres Laufs. */
|
|
1142
|
+
/** Eine Ereigniszeile im Verlauf (E80), eingerückt unter der Kopfzeile ihres Laufs. Checkouts stellt `checkoutGruppen` dar (E109). */
|
|
1108
1143
|
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
1144
|
if (ereignis.art === 'bundle') {
|
|
1115
1145
|
const teile = [
|
|
1116
1146
|
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
|
@@ -275,6 +275,8 @@ function registriereIpc() {
|
|
|
275
275
|
art: quelle.art,
|
|
276
276
|
text: quelleAlsText(quelle),
|
|
277
277
|
name: quelleName(quelle),
|
|
278
|
+
// Nur der gespeicherte Name (E109): der Verlauf braucht den Unterschied zum abgeleiteten.
|
|
279
|
+
gespeichert: quelle.name,
|
|
278
280
|
}))));
|
|
279
281
|
// Quelle-Verwaltung in den Einstellungen (E54, E59): Text-zu-Quelle passiert im Main-Prozess, der
|
|
280
282
|
// Renderer kennt nur Zeichenketten. Der Anzeigename kommt serverseitig mit, damit dem Renderer nie
|
|
@@ -286,6 +288,7 @@ function registriereIpc() {
|
|
|
286
288
|
art: ergebnis.quelle.art,
|
|
287
289
|
text: quelleAlsText(ergebnis.quelle),
|
|
288
290
|
name: quelleName(ergebnis.quelle),
|
|
291
|
+
gespeichert: ergebnis.quelle.name,
|
|
289
292
|
},
|
|
290
293
|
skills: ergebnis.skills,
|
|
291
294
|
marketplace: ergebnis.marketplace,
|
|
@@ -295,7 +298,7 @@ function registriereIpc() {
|
|
|
295
298
|
ipcMain.handle('hmgr:quelleEntfernen', async (_event, text) => auffangen(async () => anwendungsfaelle.quelleEntfernen(quelleAusText(text))));
|
|
296
299
|
ipcMain.handle('hmgr:quelleUmbenennen', async (_event, text, name) => auffangen(async () => {
|
|
297
300
|
const umbenannt = await anwendungsfaelle.quelleUmbenennen(quelleAusText(text), name);
|
|
298
|
-
return { art: umbenannt.art, text: quelleAlsText(umbenannt), name: quelleName(umbenannt) };
|
|
301
|
+
return { art: umbenannt.art, text: quelleAlsText(umbenannt), name: quelleName(umbenannt), gespeichert: umbenannt.name };
|
|
299
302
|
}));
|
|
300
303
|
// Aufräum-Angebot nach dem Entfernen einer Quelle (E58): Inventar vor dem eigentlichen Entfernen
|
|
301
304
|
// erheben (der Renderer ruft das, bevor er quelleEntfernen aufruft), dann bei Zustimmung abräumen.
|
|
@@ -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'),
|
|
@@ -2894,17 +2908,11 @@
|
|
|
2894
2908
|
|
|
2895
2909
|
/**
|
|
2896
2910
|
* Eine Ereigniszeile im Verlauf (E80): Marke + Text, Muster wie die frühere baueBericht-Zeile
|
|
2897
|
-
*
|
|
2911
|
+
* und die CLI-Ausgabe (verlaufEreignisZeile, src/cli/main.ts). Checkouts stellt
|
|
2912
|
+
* verlaufQuellenGruppen dar (E109).
|
|
2898
2913
|
*/
|
|
2899
2914
|
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') {
|
|
2915
|
+
if (ereignis.art === 'bundle') {
|
|
2908
2916
|
li.appendChild(e('span', 'marke-ok', 'Bundle'));
|
|
2909
2917
|
var teile = [];
|
|
2910
2918
|
if (ereignis.gelegt.length > 0) teile.push('gelegt ' + ereignis.gelegt.join(', '));
|
|
@@ -2942,6 +2950,89 @@
|
|
|
2942
2950
|
}
|
|
2943
2951
|
}
|
|
2944
2952
|
|
|
2953
|
+
/**
|
|
2954
|
+
* Anzeigename einer Quelle im Verlauf (E109). Baut quelleVerlaufName (src/kern/modell.ts) nach,
|
|
2955
|
+
* der Renderer kann die Kern-Datei nicht importieren: Quelle aus QUELLEN über die Identität
|
|
2956
|
+
* (Host/Besitzer/Repo, E36) suchen; fehlt sie oder hat sie keinen Namen, `besitzer/repo` aus der
|
|
2957
|
+
* URL, bei einer lokalen Quelle der letzte Pfadteil. Ein Ref außer main/master steht in Klammern.
|
|
2958
|
+
*/
|
|
2959
|
+
function verlaufQuellenOrt(text) {
|
|
2960
|
+
var t = text.trim().replace(/\/+$/, '');
|
|
2961
|
+
var segmente = null;
|
|
2962
|
+
var host = '';
|
|
2963
|
+
var m;
|
|
2964
|
+
if (t.indexOf('://') >= 0) {
|
|
2965
|
+
try {
|
|
2966
|
+
var u = new URL(t);
|
|
2967
|
+
segmente = decodeURIComponent(u.pathname).split('/');
|
|
2968
|
+
host = u.protocol === 'file:' ? 'local' : u.hostname;
|
|
2969
|
+
} catch (x) { return null; }
|
|
2970
|
+
} else if (!/^[A-Za-z]:[\\/]/.test(t) && (m = /^(?:[^@/]+@)?([^@/:]+):(.+)$/.exec(t))) {
|
|
2971
|
+
host = m[1];
|
|
2972
|
+
segmente = m[2].split('/');
|
|
2973
|
+
} else {
|
|
2974
|
+
return null;
|
|
2975
|
+
}
|
|
2976
|
+
segmente = segmente.filter(function (x) { return x.length > 0; });
|
|
2977
|
+
var repo = (segmente[segmente.length - 1] || 'repo').replace(/\.git$/, '');
|
|
2978
|
+
return { host: host.toLowerCase(), besitzer: segmente[segmente.length - 2] || '_', repo: repo };
|
|
2979
|
+
}
|
|
2980
|
+
function verlaufQuellenName(text, ref) {
|
|
2981
|
+
var ort = verlaufQuellenOrt(text);
|
|
2982
|
+
var schluessel = ort === null ? text : ort.host + '/' + ort.besitzer + '/' + ort.repo;
|
|
2983
|
+
var gefunden = QUELLEN.filter(function (q) {
|
|
2984
|
+
var qt = q.text;
|
|
2985
|
+
var qo = verlaufQuellenOrt(qt);
|
|
2986
|
+
return (qo === null ? qt : qo.host + '/' + qo.besitzer + '/' + qo.repo) === schluessel;
|
|
2987
|
+
})[0];
|
|
2988
|
+
var name;
|
|
2989
|
+
if (gefunden && gefunden.gespeichert && gefunden.gespeichert.trim().length > 0) name = gefunden.gespeichert;
|
|
2990
|
+
else if (ort !== null && ort.besitzer !== '_') name = ort.besitzer + '/' + ort.repo;
|
|
2991
|
+
else if (ort !== null) name = ort.repo;
|
|
2992
|
+
else {
|
|
2993
|
+
var teile = text.replace(/[\/\\]+$/, '').split(/[\/\\]/).filter(function (x) { return x.length > 0; });
|
|
2994
|
+
name = teile[teile.length - 1] || text;
|
|
2995
|
+
}
|
|
2996
|
+
return ref === 'main' || ref === 'master' ? name : name + ' (' + ref + ')';
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
/**
|
|
3000
|
+
* Die Checkout-Ereignisse eines Laufs (E109): je Quelle mit Skill-Änderung eine Gruppe (Name,
|
|
3001
|
+
* darunter Neu/Entfernt/Geändert, leere fallen weg), dieselbe Darstellung wie `hmgr log`
|
|
3002
|
+
* (src/cli/verlauf-anzeige.ts). Gibt die Namen der Quellen ohne Skill-Änderung zurück, aus
|
|
3003
|
+
* denen der Aufrufer die Sammelzeile baut; die Gruppen hängt sie an `ul` an.
|
|
3004
|
+
*/
|
|
3005
|
+
function verlaufQuellenGruppen(ul, ereignisse) {
|
|
3006
|
+
var ohne = [];
|
|
3007
|
+
var gruppen = 0;
|
|
3008
|
+
ereignisse.forEach(function (ereignis) {
|
|
3009
|
+
if (ereignis.art !== 'checkout') return;
|
|
3010
|
+
var name = verlaufQuellenName(ereignis.quelle, ereignis.ref);
|
|
3011
|
+
var teile = [
|
|
3012
|
+
['Neu', ereignis.neu || []],
|
|
3013
|
+
['Entfernt', ereignis.entfernt || []],
|
|
3014
|
+
['Geändert', ereignis.skills || []],
|
|
3015
|
+
].filter(function (t) { return t[1].length > 0; });
|
|
3016
|
+
if (teile.length === 0) {
|
|
3017
|
+
if (ohne.indexOf(name) < 0) ohne.push(name);
|
|
3018
|
+
return;
|
|
3019
|
+
}
|
|
3020
|
+
gruppen += 1;
|
|
3021
|
+
var li = e('li', 'quellengruppe');
|
|
3022
|
+
li.appendChild(e('b', null, name));
|
|
3023
|
+
var innen = e('ul', 'gruppenzeilen');
|
|
3024
|
+
teile.forEach(function (t) {
|
|
3025
|
+
var zeile = e('li');
|
|
3026
|
+
zeile.appendChild(e('span', 'marke-ok', t[0]));
|
|
3027
|
+
zeile.appendChild(e('span', 'pfad', t[1].join(', ')));
|
|
3028
|
+
innen.appendChild(zeile);
|
|
3029
|
+
});
|
|
3030
|
+
li.appendChild(innen);
|
|
3031
|
+
ul.appendChild(li);
|
|
3032
|
+
});
|
|
3033
|
+
return { ohne: ohne, gruppen: gruppen };
|
|
3034
|
+
}
|
|
3035
|
+
|
|
2945
3036
|
/**
|
|
2946
3037
|
* Baut das Changelog in der Update-Ansicht aus ~/.hmgr/verlauf.json (E80): ein Block je Lauf,
|
|
2947
3038
|
* neueste zuerst, das Etikett der lokale Zeitpunkt, darunter eine Zeile je Ereignis. Die Datei
|
|
@@ -2959,15 +3050,29 @@
|
|
|
2959
3050
|
var block = e('div', 'block');
|
|
2960
3051
|
var zeit = new Date(eintrag.zeit);
|
|
2961
3052
|
var zwei = function (n) { return String(n).padStart(2, '0'); };
|
|
2962
|
-
|
|
2963
|
-
|
|
3053
|
+
// „Heute, 00:26", „Gestern, 22:58", sonst „06.10.2026, 17:42" (E109), lokale Zeit.
|
|
3054
|
+
var uhr = zwei(zeit.getHours()) + ':' + zwei(zeit.getMinutes());
|
|
3055
|
+
var jetzt = new Date();
|
|
3056
|
+
var tagStart = function (d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); };
|
|
3057
|
+
var etikett = tagStart(zeit) === tagStart(jetzt) ? 'Heute, ' + uhr
|
|
3058
|
+
: tagStart(zeit) === new Date(jetzt.getFullYear(), jetzt.getMonth(), jetzt.getDate() - 1).getTime() ? 'Gestern, ' + uhr
|
|
3059
|
+
: zwei(zeit.getDate()) + '.' + zwei(zeit.getMonth() + 1) + '.' + zeit.getFullYear() + ', ' + uhr;
|
|
2964
3060
|
block.appendChild(e('span', 'etikett', etikett));
|
|
2965
3061
|
var ul = e('ul');
|
|
3062
|
+
// Erst die Quellengruppen, dann die übrigen Ereignisse, zuletzt die Sammelzeile (E109).
|
|
3063
|
+
var gruppen = verlaufQuellenGruppen(ul, eintrag.ereignisse);
|
|
3064
|
+
var andere = gruppen.gruppen > 0;
|
|
2966
3065
|
eintrag.ereignisse.forEach(function (ereignis) {
|
|
3066
|
+
if (ereignis.art === 'checkout') return;
|
|
3067
|
+
andere = true;
|
|
2967
3068
|
var li = e('li');
|
|
2968
3069
|
verlaufEreignisZeile(li, ereignis);
|
|
2969
3070
|
ul.appendChild(li);
|
|
2970
3071
|
});
|
|
3072
|
+
if (gruppen.ohne.length > 0) {
|
|
3073
|
+
ul.appendChild(e('li', 'sammelzeile', (andere ? 'Außerdem neuer' : 'Neuer') + ' Stand bei '
|
|
3074
|
+
+ gruppen.ohne.join(', ') + ', ohne Änderung an deinen Skills.'));
|
|
3075
|
+
}
|
|
2971
3076
|
block.appendChild(ul);
|
|
2972
3077
|
box.appendChild(block);
|
|
2973
3078
|
});
|
|
@@ -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}
|
|
@@ -1931,10 +1931,14 @@ export class Anwendungsfaelle {
|
|
|
1931
1931
|
// Der Quellen-Port baut Checkouts von der Platte (remote.origin.url), ohne den Zustand zu
|
|
1932
1932
|
// befragen: der Anzeigename fehlt dort. Für den Bericht kommt er aus der geführten Quelle
|
|
1933
1933
|
// (E59), sonst zeigte eine umbenannte Quelle hier wieder ihren abgeleiteten Namen.
|
|
1934
|
+
const { neu, entfernt } = await this.neueUndEntfernteSkills(checkout, aktualisierung);
|
|
1935
|
+
const geaendert = await this.geaenderteSkills(checkout, aktualisierung);
|
|
1934
1936
|
checkouts.push({
|
|
1935
1937
|
checkout: { ...checkout, quelle: quelleMitNamen(checkout.quelle, gefuehrt.name) },
|
|
1936
1938
|
aktualisierung,
|
|
1937
|
-
geaenderteSkills:
|
|
1939
|
+
geaenderteSkills: geaendert.filter((name) => !neu.includes(name)),
|
|
1940
|
+
neueSkills: neu,
|
|
1941
|
+
entfernteSkills: entfernt,
|
|
1938
1942
|
});
|
|
1939
1943
|
// `aktualisieren` holt für einen Checkout dieselben Refs wie `refsNachziehen` (fetch auf dem
|
|
1940
1944
|
// Basis-Clone). Ist das gelaufen, braucht der Bundle-Abgleich unten die Quelle nicht ein
|
|
@@ -2003,7 +2007,7 @@ export class Anwendungsfaelle {
|
|
|
2003
2007
|
*/
|
|
2004
2008
|
aktualisierungsEreignisse(checkouts, pluginFehler, bundles, aufraeumen, doctorErgebnis) {
|
|
2005
2009
|
const ereignisse = [];
|
|
2006
|
-
for (const { checkout, aktualisierung, geaenderteSkills } of checkouts) {
|
|
2010
|
+
for (const { checkout, aktualisierung, geaenderteSkills, neueSkills, entfernteSkills } of checkouts) {
|
|
2007
2011
|
if (aktualisierung.ergebnis === 'aktualisiert')
|
|
2008
2012
|
ereignisse.push({
|
|
2009
2013
|
art: 'checkout',
|
|
@@ -2012,6 +2016,8 @@ export class Anwendungsfaelle {
|
|
|
2012
2016
|
von: aktualisierung.von,
|
|
2013
2017
|
nach: aktualisierung.nach,
|
|
2014
2018
|
skills: geaenderteSkills,
|
|
2019
|
+
neu: neueSkills,
|
|
2020
|
+
entfernt: entfernteSkills,
|
|
2015
2021
|
});
|
|
2016
2022
|
else if (aktualisierung.ergebnis === 'fehlgeschlagen')
|
|
2017
2023
|
ereignisse.push({
|
|
@@ -2138,6 +2144,30 @@ export class Anwendungsfaelle {
|
|
|
2138
2144
|
namen.add(skill.name);
|
|
2139
2145
|
return [...namen].sort((a, b) => a.localeCompare(b, 'de'));
|
|
2140
2146
|
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Neue und entfernte Skills zwischen altem und neuem Commit (E109): Vergleich der Skill-Namen
|
|
2149
|
+
* (E32) von `skills(quelle, von)` und `skills(quelle, nach)`, für alle Skills der Quelle. Eine
|
|
2150
|
+
* Umbenennung erscheint als beides. Scheitert einer der beiden Aufrufe, bleiben beide Listen
|
|
2151
|
+
* leer: der Bericht wird ungenauer, der Lauf nicht kaputt.
|
|
2152
|
+
*/
|
|
2153
|
+
async neueUndEntfernteSkills(checkout, aktualisierung) {
|
|
2154
|
+
const leer = { neu: [], entfernt: [] };
|
|
2155
|
+
if (aktualisierung.ergebnis !== 'aktualisiert')
|
|
2156
|
+
return leer;
|
|
2157
|
+
const [vorher, nachher] = await Promise.all([
|
|
2158
|
+
this.p.quellen.skills(checkout.quelle, aktualisierung.von).catch(() => undefined),
|
|
2159
|
+
this.p.quellen.skills(checkout.quelle, aktualisierung.nach).catch(() => undefined),
|
|
2160
|
+
]);
|
|
2161
|
+
if (vorher === undefined || nachher === undefined)
|
|
2162
|
+
return leer;
|
|
2163
|
+
const alt = new Set(vorher.map((s) => s.name));
|
|
2164
|
+
const neuNamen = new Set(nachher.map((s) => s.name));
|
|
2165
|
+
const sortiert = (namen) => [...namen].sort((a, b) => a.localeCompare(b, 'de'));
|
|
2166
|
+
return {
|
|
2167
|
+
neu: sortiert([...neuNamen].filter((n) => !alt.has(n))),
|
|
2168
|
+
entfernt: sortiert([...alt].filter((n) => !neuNamen.has(n))),
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2141
2171
|
/**
|
|
2142
2172
|
* Pfade, in die ein Link zeigt. Gelesen wird wie beim Ist-Zustand (E19): die Ziele der
|
|
2143
2173
|
* 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;
|