hmgr 0.0.1
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/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/adapter/checkout-pfad.js +66 -0
- package/dist/adapter/claude-code.js +184 -0
- package/dist/adapter/composer.js +94 -0
- package/dist/adapter/dateisystem.js +126 -0
- package/dist/adapter/git.js +339 -0
- package/dist/adapter/gitignore.js +16 -0
- package/dist/adapter/hermes.js +142 -0
- package/dist/adapter/marketplace-json.js +98 -0
- package/dist/adapter/quelle-text.js +11 -0
- package/dist/adapter/skill-md.js +49 -0
- package/dist/adapter/zustand-json.js +25 -0
- package/dist/cli/main.js +407 -0
- package/dist/kern/anwendungsfaelle.js +683 -0
- package/dist/kern/modell.js +146 -0
- package/dist/kern/version.js +244 -0
- package/dist/ports/harness.js +6 -0
- package/dist/ports/quellen.js +1 -0
- package/dist/ports/zeiger.js +1 -0
- package/dist/ports/ziele.js +1 -0
- package/dist/ports/zustand.js +3 -0
- package/package.json +35 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
// Anwendungsfälle. Pfade werden hier nur als Zeichenketten normalisiert, gelesen wird über die Ports.
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { AbhaengigkeitFehlt, EinschraenkungOhneZeiger, HarnessNichtVorhanden, KeinPassenderRef, OrtNichtUnterstuetzt, PluginMehrdeutig, PluginUnbekannt, SkillMehrdeutig, SkillUnbekannt, ZeigerMehrdeutig, marketplaceUrl, pluginSchluessel, pluginSchluesselZerlegen, quelleSchluessel, zielSchluessel, } from "./modell.js";
|
|
4
|
+
import { passenderRef } from "./version.js";
|
|
5
|
+
import { kannPlugins, kannProfile } from "../ports/harness.js";
|
|
6
|
+
function arbeitsverzeichnisZiel(harness, pfad) {
|
|
7
|
+
return { harness, ort: { art: 'arbeitsverzeichnis', pfad } };
|
|
8
|
+
}
|
|
9
|
+
function fehlertext(fehler) {
|
|
10
|
+
return fehler instanceof Error ? fehler.message : String(fehler);
|
|
11
|
+
}
|
|
12
|
+
export class Anwendungsfaelle {
|
|
13
|
+
p;
|
|
14
|
+
constructor(p) {
|
|
15
|
+
this.p = p;
|
|
16
|
+
}
|
|
17
|
+
harness(name) {
|
|
18
|
+
const h = this.p.harnesses.find((h) => h.name === name);
|
|
19
|
+
if (!h)
|
|
20
|
+
throw new Error(`Unbekannter Harness „${name}"`);
|
|
21
|
+
return h;
|
|
22
|
+
}
|
|
23
|
+
/** Ein namentlich genannter Harness muss auf dem Rechner liegen, sonst passiert nichts Stilles. */
|
|
24
|
+
async harnessGeprueft(name) {
|
|
25
|
+
const h = this.harness(name);
|
|
26
|
+
if (!(await h.vorhanden()))
|
|
27
|
+
throw new HarnessNichtVorhanden(name);
|
|
28
|
+
return h;
|
|
29
|
+
}
|
|
30
|
+
/** Harnesses, die auf dem Rechner liegen. Alle anderen überspringt hmgr. */
|
|
31
|
+
async vorhandeneHarnesses() {
|
|
32
|
+
const ergebnis = [];
|
|
33
|
+
for (const harness of this.p.harnesses)
|
|
34
|
+
if (await harness.vorhanden())
|
|
35
|
+
ergebnis.push(harness);
|
|
36
|
+
return ergebnis;
|
|
37
|
+
}
|
|
38
|
+
/** Namen der Harnesses, die nicht auf dem Rechner liegen. Für die Ausgabe von `status`. */
|
|
39
|
+
async nichtVorhandeneHarnesses() {
|
|
40
|
+
const ergebnis = [];
|
|
41
|
+
for (const harness of this.p.harnesses)
|
|
42
|
+
if (!(await harness.vorhanden()))
|
|
43
|
+
ergebnis.push(harness.name);
|
|
44
|
+
return ergebnis;
|
|
45
|
+
}
|
|
46
|
+
/** Quelle hinzufügen: prüft, dass sie erreichbar ist, und liest Skills und Marketplace (E24). */
|
|
47
|
+
async quelleHinzufuegen(quelle) {
|
|
48
|
+
const ref = await this.p.quellen.standardRef(quelle);
|
|
49
|
+
const skills = await this.p.quellen.skills(quelle, ref);
|
|
50
|
+
const marketplace = await this.p.quellen.marketplace(quelle, ref);
|
|
51
|
+
const zustand = await this.p.zustand.lesen();
|
|
52
|
+
const key = quelleSchluessel(quelle);
|
|
53
|
+
if (!zustand.quellen.some((q) => quelleSchluessel(q) === key)) {
|
|
54
|
+
zustand.quellen.push(quelle);
|
|
55
|
+
await this.p.zustand.schreiben(zustand);
|
|
56
|
+
}
|
|
57
|
+
return { skills, marketplace };
|
|
58
|
+
}
|
|
59
|
+
/** Marketplace einer Quelle am Standard-Ref, mit den Plugins als Modellwerte. */
|
|
60
|
+
async marketplaceLesen(quelle) {
|
|
61
|
+
const ref = await this.p.quellen.standardRef(quelle);
|
|
62
|
+
const beschreibung = await this.p.quellen.marketplace(quelle, ref);
|
|
63
|
+
if (beschreibung === null)
|
|
64
|
+
return null;
|
|
65
|
+
return {
|
|
66
|
+
ref,
|
|
67
|
+
name: beschreibung.name,
|
|
68
|
+
plugins: beschreibung.plugins.map((p) => ({
|
|
69
|
+
name: p.name,
|
|
70
|
+
marketplace: beschreibung.name,
|
|
71
|
+
skills: p.skills,
|
|
72
|
+
dependencies: p.dependencies,
|
|
73
|
+
})),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Alle Marketplaces der bekannten Quellen, für `hmgr plugin list`. */
|
|
77
|
+
async marketplaces() {
|
|
78
|
+
const zustand = await this.p.zustand.lesen();
|
|
79
|
+
const ergebnis = [];
|
|
80
|
+
for (const quelle of zustand.quellen) {
|
|
81
|
+
const gelesen = await this.marketplaceLesen(quelle);
|
|
82
|
+
if (gelesen === null)
|
|
83
|
+
continue;
|
|
84
|
+
ergebnis.push({ quelle, name: gelesen.name, plugins: gelesen.plugins });
|
|
85
|
+
}
|
|
86
|
+
return ergebnis;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Plugin über die Marketplaces aller bekannten Quellen suchen. Mehrere Treffer sind nur mit
|
|
90
|
+
* Quellenhinweis erlaubt, wie bei Skills (E12). `marketplace` grenzt zusätzlich ein.
|
|
91
|
+
*/
|
|
92
|
+
async pluginFinden(name, quelleHinweis, marketplace) {
|
|
93
|
+
const zustand = await this.p.zustand.lesen();
|
|
94
|
+
const funde = [];
|
|
95
|
+
for (const quelle of zustand.quellen) {
|
|
96
|
+
if (quelleHinweis && !quelleSchluessel(quelle).includes(quelleHinweis))
|
|
97
|
+
continue;
|
|
98
|
+
const gelesen = await this.marketplaceLesen(quelle).catch(() => null);
|
|
99
|
+
if (gelesen === null)
|
|
100
|
+
continue;
|
|
101
|
+
for (const plugin of gelesen.plugins) {
|
|
102
|
+
if (plugin.name !== name)
|
|
103
|
+
continue;
|
|
104
|
+
if (marketplace !== undefined && plugin.marketplace !== marketplace)
|
|
105
|
+
continue;
|
|
106
|
+
funde.push({ quelle, ref: gelesen.ref, plugin });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (funde.length === 0)
|
|
110
|
+
throw new PluginUnbekannt(name);
|
|
111
|
+
if (funde.length > 1)
|
|
112
|
+
throw new PluginMehrdeutig(name, funde.map((f) => f.quelle));
|
|
113
|
+
return funde[0];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Flache Skill-Liste (B5): das Plugin plus alle Abhängigkeiten, rekursiv im selben Marketplace,
|
|
117
|
+
* ohne Dubletten. Ein Zyklus wird abgefangen, ein Plugin kommt nur einmal vor.
|
|
118
|
+
*/
|
|
119
|
+
async flacheSkillListe(pluginName, quelle) {
|
|
120
|
+
const gelesen = await this.marketplaceLesen(quelle);
|
|
121
|
+
if (gelesen === null)
|
|
122
|
+
throw new PluginUnbekannt(pluginName);
|
|
123
|
+
const nachName = new Map(gelesen.plugins.map((p) => [p.name, p]));
|
|
124
|
+
if (!nachName.has(pluginName))
|
|
125
|
+
throw new PluginUnbekannt(pluginName);
|
|
126
|
+
const gesehen = new Set();
|
|
127
|
+
const skills = [];
|
|
128
|
+
const pfade = new Set();
|
|
129
|
+
const offen = [pluginName];
|
|
130
|
+
while (offen.length > 0) {
|
|
131
|
+
const name = offen.shift();
|
|
132
|
+
if (gesehen.has(name))
|
|
133
|
+
continue; // Zyklus oder Dublette
|
|
134
|
+
gesehen.add(name);
|
|
135
|
+
const plugin = nachName.get(name);
|
|
136
|
+
if (plugin === undefined)
|
|
137
|
+
throw new AbhaengigkeitFehlt(pluginName, name);
|
|
138
|
+
for (const skill of plugin.skills)
|
|
139
|
+
if (!pfade.has(skill.pfad)) {
|
|
140
|
+
pfade.add(skill.pfad);
|
|
141
|
+
skills.push(skill);
|
|
142
|
+
}
|
|
143
|
+
offen.push(...plugin.dependencies);
|
|
144
|
+
}
|
|
145
|
+
return skills;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Arbeitsverzeichnis hinzufügen (E21, E26): Zeiger suchen, Bindung je vorhandenem Harness merken.
|
|
149
|
+
* Kein Zeiger heißt: Verzeichnis merken, keine Bindung, Hinweis. Mehrdeutig heißt: Fehler,
|
|
150
|
+
* es sei denn, der Nutzer nennt eine composer.json. Nie Zufall.
|
|
151
|
+
*/
|
|
152
|
+
async arbeitsverzeichnisHinzufuegen(pfad, composerPfad) {
|
|
153
|
+
const verzeichnis = path.resolve(pfad);
|
|
154
|
+
let zeigerPfad;
|
|
155
|
+
let hinweis;
|
|
156
|
+
// Eine schon gemerkte Bindung gewinnt, sonst würde ein zweiter Aufruf erneut mehrdeutig werden.
|
|
157
|
+
const bekannt = await this.bindungFuerVerzeichnis(verzeichnis);
|
|
158
|
+
if (composerPfad !== undefined) {
|
|
159
|
+
zeigerPfad = path.resolve(composerPfad);
|
|
160
|
+
}
|
|
161
|
+
else if (bekannt !== undefined) {
|
|
162
|
+
zeigerPfad = bekannt;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const fund = await this.p.zeiger.finden(verzeichnis);
|
|
166
|
+
if (fund.ergebnis === 'gefunden')
|
|
167
|
+
zeigerPfad = fund.pfad;
|
|
168
|
+
else if (fund.ergebnis === 'mehrdeutig')
|
|
169
|
+
throw new ZeigerMehrdeutig(verzeichnis, fund.kandidaten);
|
|
170
|
+
else
|
|
171
|
+
hinweis = `Kein Zeiger unter ${verzeichnis}, alles bleibt lokal (E26).`;
|
|
172
|
+
}
|
|
173
|
+
const zustand = await this.p.zustand.lesen();
|
|
174
|
+
if (!zustand.arbeitsverzeichnisse.includes(verzeichnis))
|
|
175
|
+
zustand.arbeitsverzeichnisse.push(verzeichnis);
|
|
176
|
+
if (zeigerPfad !== undefined) {
|
|
177
|
+
const gebunden = zeigerPfad;
|
|
178
|
+
for (const harness of await this.vorhandeneHarnesses()) {
|
|
179
|
+
if (!harness.orte().includes('arbeitsverzeichnis'))
|
|
180
|
+
continue;
|
|
181
|
+
const ziel = arbeitsverzeichnisZiel(harness.name, verzeichnis);
|
|
182
|
+
const schluessel = zielSchluessel(ziel);
|
|
183
|
+
zustand.bindungen = zustand.bindungen.filter((b) => zielSchluessel(b.ziel) !== schluessel);
|
|
184
|
+
zustand.bindungen.push({ ziel, zeigerPfad: gebunden });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
await this.p.zustand.schreiben(zustand);
|
|
188
|
+
return {
|
|
189
|
+
pfad: verzeichnis,
|
|
190
|
+
...(zeigerPfad === undefined ? {} : { zeigerPfad }),
|
|
191
|
+
...(hinweis === undefined ? {} : { hinweis }),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/** Arbeitsverzeichnis vergessen: Verzeichnis und Bindungen weg, die Links bleiben liegen (E26). */
|
|
195
|
+
async arbeitsverzeichnisVergessen(pfad) {
|
|
196
|
+
const verzeichnis = path.resolve(pfad);
|
|
197
|
+
const zustand = await this.p.zustand.lesen();
|
|
198
|
+
zustand.arbeitsverzeichnisse = zustand.arbeitsverzeichnisse.filter((p) => p !== verzeichnis);
|
|
199
|
+
zustand.bindungen = zustand.bindungen.filter((b) => !(b.ziel.ort.art === 'arbeitsverzeichnis' && b.ziel.ort.pfad === verzeichnis));
|
|
200
|
+
await this.p.zustand.schreiben(zustand);
|
|
201
|
+
}
|
|
202
|
+
/** Zeiger eines Arbeitsverzeichnisses aus dem Zustand, egal für welchen Harness (E26). */
|
|
203
|
+
async bindungFuerVerzeichnis(verzeichnis) {
|
|
204
|
+
const zustand = await this.p.zustand.lesen();
|
|
205
|
+
return zustand.bindungen.find((b) => b.ziel.ort.art === 'arbeitsverzeichnis' && b.ziel.ort.pfad === verzeichnis)?.zeigerPfad;
|
|
206
|
+
}
|
|
207
|
+
/** Zeiger eines Ziels aus dem Zustand. Undefined heißt: keine Bindung. */
|
|
208
|
+
async bindungFinden(ziel) {
|
|
209
|
+
const schluessel = zielSchluessel(ziel);
|
|
210
|
+
const zustand = await this.p.zustand.lesen();
|
|
211
|
+
return zustand.bindungen.find((b) => zielSchluessel(b.ziel) === schluessel)?.zeigerPfad;
|
|
212
|
+
}
|
|
213
|
+
/** Skill über alle Quellen suchen. Mehrere Treffer sind nur mit Quellenhinweis erlaubt. */
|
|
214
|
+
async skillFinden(name, quelleHinweis) {
|
|
215
|
+
const zustand = await this.p.zustand.lesen();
|
|
216
|
+
const funde = [];
|
|
217
|
+
for (const quelle of zustand.quellen) {
|
|
218
|
+
if (quelleHinweis && !quelleSchluessel(quelle).includes(quelleHinweis))
|
|
219
|
+
continue;
|
|
220
|
+
const ref = await this.p.quellen.standardRef(quelle);
|
|
221
|
+
const skills = await this.p.quellen.skills(quelle, ref);
|
|
222
|
+
for (const skill of skills)
|
|
223
|
+
if (skill.name === name)
|
|
224
|
+
funde.push({ quelle, ref, skill });
|
|
225
|
+
}
|
|
226
|
+
if (funde.length === 0)
|
|
227
|
+
throw new SkillUnbekannt(name);
|
|
228
|
+
if (funde.length > 1)
|
|
229
|
+
throw new SkillMehrdeutig(name, funde.map((f) => f.quelle));
|
|
230
|
+
return funde[0];
|
|
231
|
+
}
|
|
232
|
+
/** Checkout anlegen oder erweitern, Ziel vorbereiten, Link setzen. Ohne Zeiger-Arbeit. */
|
|
233
|
+
async linkAnlegen(harness, ziel, fund, ersetzen) {
|
|
234
|
+
if (!harness.orte().includes(ziel.ort.art))
|
|
235
|
+
throw new OrtNichtUnterstuetzt(harness.name, ziel.ort.art);
|
|
236
|
+
const verzeichnis = harness.zielVerzeichnis(ziel.ort);
|
|
237
|
+
// Die Prüfung auf einen fremden Eintrag macht der Ziele-Port unmittelbar vor dem Anlegen (E12).
|
|
238
|
+
const checkout = await this.p.quellen.checkout(fund.quelle, fund.ref, [fund.skill]);
|
|
239
|
+
await harness.vorbereiten(ziel.ort);
|
|
240
|
+
await this.p.ziele.linkLegen(ziel, verzeichnis, fund.skill.name, checkout, fund.skill, ersetzen);
|
|
241
|
+
return { ziel, name: fund.skill.name, checkout, skill: fund.skill };
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Quelle und Skill in den Zeiger schreiben, dann das Composer-Script sichern (E17, E20, E25).
|
|
245
|
+
* Eine genannte Einschränkung ersetzt die bisherige, ohne Einschränkung bleibt ein vorhandener
|
|
246
|
+
* Eintrag stehen.
|
|
247
|
+
*/
|
|
248
|
+
async zeigerErgaenzen(zeigerPfad, fund, einschraenkung) {
|
|
249
|
+
const zeiger = await this.p.zeiger.lesen(zeigerPfad);
|
|
250
|
+
const key = quelleSchluessel(fund.quelle);
|
|
251
|
+
if (!zeiger.quellen.some((q) => quelleSchluessel(q) === key))
|
|
252
|
+
zeiger.quellen.push(fund.quelle);
|
|
253
|
+
if (einschraenkung !== undefined)
|
|
254
|
+
zeiger.skills.set(fund.skill.name, einschraenkung);
|
|
255
|
+
else if (!zeiger.skills.has(fund.skill.name))
|
|
256
|
+
zeiger.skills.set(fund.skill.name, null);
|
|
257
|
+
await this.p.zeiger.schreiben(zeigerPfad, zeiger);
|
|
258
|
+
await this.p.zeiger.scriptEintragen(zeigerPfad);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Link legen. In ein Arbeitsverzeichnis ist der Link standardmäßig geteilt und landet im Zeiger
|
|
262
|
+
* (E17). `lokal` unterdrückt das, für Home und Profil gibt es keinen Zeiger. Eine
|
|
263
|
+
* Versionseinschränkung wird sofort aufgelöst und in den Zeiger geschrieben (E33), ohne Zeiger
|
|
264
|
+
* hätte sie keinen Platz und der Aufruf schlägt fehl.
|
|
265
|
+
*/
|
|
266
|
+
async linkLegen(args) {
|
|
267
|
+
const harness = await this.harnessGeprueft(args.ziel.harness);
|
|
268
|
+
if (!harness.orte().includes(args.ziel.ort.art))
|
|
269
|
+
throw new OrtNichtUnterstuetzt(harness.name, args.ziel.ort.art);
|
|
270
|
+
const version = args.version;
|
|
271
|
+
if (version === undefined) {
|
|
272
|
+
const fund = await this.skillFinden(args.skill, args.quelle);
|
|
273
|
+
return await this.linkAusFund(harness, args.ziel, fund, args.ersetzen, args.lokal);
|
|
274
|
+
}
|
|
275
|
+
if (args.lokal === true || args.ziel.ort.art !== 'arbeitsverzeichnis')
|
|
276
|
+
throw new EinschraenkungOhneZeiger(args.ziel, version);
|
|
277
|
+
const zeigerPfad = await this.bindungFinden(args.ziel);
|
|
278
|
+
if (zeigerPfad === undefined)
|
|
279
|
+
throw new EinschraenkungOhneZeiger(args.ziel, version);
|
|
280
|
+
const fund = await this.fundAusZeiger(args.skill, version, args.quelle);
|
|
281
|
+
const link = await this.linkAnlegen(harness, args.ziel, fund, args.ersetzen);
|
|
282
|
+
await this.zeigerErgaenzen(zeigerPfad, fund, version);
|
|
283
|
+
return link;
|
|
284
|
+
}
|
|
285
|
+
/** Link legen, wenn Quelle, Ref und Skill schon feststehen. Die Zeiger-Arbeit ist dieselbe. */
|
|
286
|
+
async linkAusFund(harness, ziel, fund, ersetzen, lokal) {
|
|
287
|
+
const link = await this.linkAnlegen(harness, ziel, fund, ersetzen);
|
|
288
|
+
if (lokal === true || ziel.ort.art !== 'arbeitsverzeichnis')
|
|
289
|
+
return link;
|
|
290
|
+
const zeigerPfad = await this.bindungFinden(ziel);
|
|
291
|
+
// Ziel ohne Zeiger: der Link wird trotzdem gelegt, hmgr weist darauf hin (E26).
|
|
292
|
+
if (zeigerPfad === undefined)
|
|
293
|
+
return { ...link, hinweis: `Ohne Zeiger bleibt „${link.name}" lokal, der Link ist gelegt (E26).` };
|
|
294
|
+
await this.zeigerErgaenzen(zeigerPfad, fund);
|
|
295
|
+
return link;
|
|
296
|
+
}
|
|
297
|
+
/** Link entfernen. Ohne `lokal` verschwindet der Skill auch aus dem Zeiger (E17). */
|
|
298
|
+
async linkEntfernen(args) {
|
|
299
|
+
const harness = await this.harnessGeprueft(args.ziel.harness);
|
|
300
|
+
await this.p.ziele.linkEntfernen(args.ziel, harness.zielVerzeichnis(args.ziel.ort), args.skill);
|
|
301
|
+
if (args.lokal === true || args.ziel.ort.art !== 'arbeitsverzeichnis')
|
|
302
|
+
return;
|
|
303
|
+
const zeigerPfad = await this.bindungFinden(args.ziel);
|
|
304
|
+
if (zeigerPfad === undefined)
|
|
305
|
+
return;
|
|
306
|
+
const zeiger = await this.p.zeiger.lesen(zeigerPfad);
|
|
307
|
+
if (!zeiger.skills.delete(args.skill))
|
|
308
|
+
return;
|
|
309
|
+
await this.p.zeiger.schreiben(zeigerPfad, zeiger);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Plugin an einem Ziel herstellen: mit Plugin-Fähigkeit über die CLI des Harness, sonst als
|
|
313
|
+
* flache Skill-Liste (E16, B5). Der Zeiger wird hier nicht angefasst.
|
|
314
|
+
*/
|
|
315
|
+
async pluginHerstellen(harness, ziel, fund) {
|
|
316
|
+
if (!harness.orte().includes(ziel.ort.art))
|
|
317
|
+
throw new OrtNichtUnterstuetzt(harness.name, ziel.ort.art);
|
|
318
|
+
if (kannPlugins(harness)) {
|
|
319
|
+
if (!(await harness.plugins.marketplaceBekannt(fund.plugin.marketplace)))
|
|
320
|
+
await harness.plugins.marketplaceHinzufuegen(fund.plugin.marketplace, marketplaceUrl(fund.quelle));
|
|
321
|
+
await harness.vorbereiten(ziel.ort);
|
|
322
|
+
await harness.plugins.installieren(fund.plugin.name, fund.plugin.marketplace, ziel);
|
|
323
|
+
return { plugin: fund.plugin, art: 'installiert', skills: [], imZeiger: false };
|
|
324
|
+
}
|
|
325
|
+
const skills = await this.flacheSkillListe(fund.plugin.name, fund.quelle);
|
|
326
|
+
for (const skill of skills)
|
|
327
|
+
await this.linkAnlegen(harness, ziel, { quelle: fund.quelle, ref: fund.ref, skill });
|
|
328
|
+
return { plugin: fund.plugin, art: 'verlinkt', skills: skills.map((s) => s.name), imZeiger: false };
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Plugin legen (E16, E17, E24, E27). In ein Arbeitsverzeichnis mit Zeiger ist das Plugin
|
|
332
|
+
* standardmäßig geteilt: in den Zeiger kommt das Plugin, nicht seine Einzelskills.
|
|
333
|
+
*/
|
|
334
|
+
async pluginLegen(args) {
|
|
335
|
+
const harness = await this.harnessGeprueft(args.ziel.harness);
|
|
336
|
+
const fund = await this.pluginFinden(args.plugin, args.quelle);
|
|
337
|
+
const ergebnis = await this.pluginHerstellen(harness, args.ziel, fund);
|
|
338
|
+
if (args.lokal === true || args.ziel.ort.art !== 'arbeitsverzeichnis')
|
|
339
|
+
return ergebnis;
|
|
340
|
+
const zeigerPfad = await this.bindungFinden(args.ziel);
|
|
341
|
+
if (zeigerPfad === undefined)
|
|
342
|
+
return { ...ergebnis, hinweis: `Ohne Zeiger bleibt „${args.plugin}" lokal, das Plugin liegt am Ziel (E26).` };
|
|
343
|
+
const zeiger = await this.p.zeiger.lesen(zeigerPfad);
|
|
344
|
+
const key = quelleSchluessel(fund.quelle);
|
|
345
|
+
if (!zeiger.quellen.some((q) => quelleSchluessel(q) === key))
|
|
346
|
+
zeiger.quellen.push(fund.quelle);
|
|
347
|
+
const schluessel = pluginSchluessel(fund.plugin);
|
|
348
|
+
if (!zeiger.plugins.includes(schluessel))
|
|
349
|
+
zeiger.plugins.push(schluessel);
|
|
350
|
+
await this.p.zeiger.schreiben(zeigerPfad, zeiger);
|
|
351
|
+
await this.p.zeiger.scriptEintragen(zeigerPfad);
|
|
352
|
+
return { ...ergebnis, imZeiger: true };
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Namen der Skills, die andere am Ziel liegende Plugins brauchen. Quellen sind der Zeiger und,
|
|
356
|
+
* bei einem Harness mit Plugin-Fähigkeit, die installierten Plugins.
|
|
357
|
+
*/
|
|
358
|
+
async fremdeSkillNamen(harness, ziel, ausser) {
|
|
359
|
+
const schluessel = new Set();
|
|
360
|
+
const zeigerPfad = await this.bindungFinden(ziel);
|
|
361
|
+
if (zeigerPfad !== undefined)
|
|
362
|
+
for (const eintrag of (await this.p.zeiger.lesen(zeigerPfad)).plugins)
|
|
363
|
+
schluessel.add(eintrag);
|
|
364
|
+
if (kannPlugins(harness))
|
|
365
|
+
for (const eintrag of await harness.plugins.installierte(ziel).catch(() => []))
|
|
366
|
+
schluessel.add(eintrag);
|
|
367
|
+
schluessel.delete(ausser);
|
|
368
|
+
const namen = new Set();
|
|
369
|
+
for (const eintrag of schluessel) {
|
|
370
|
+
const zerlegt = pluginSchluesselZerlegen(eintrag);
|
|
371
|
+
const fund = await this.pluginFinden(zerlegt.name, undefined, zerlegt.marketplace).catch(() => undefined);
|
|
372
|
+
if (fund === undefined)
|
|
373
|
+
continue;
|
|
374
|
+
const skills = await this.flacheSkillListe(fund.plugin.name, fund.quelle).catch(() => []);
|
|
375
|
+
for (const skill of skills)
|
|
376
|
+
namen.add(skill.name);
|
|
377
|
+
}
|
|
378
|
+
return namen;
|
|
379
|
+
}
|
|
380
|
+
/** Plugin entfernen. Gegenstück zu `pluginLegen`. */
|
|
381
|
+
async pluginEntfernen(args) {
|
|
382
|
+
const harness = await this.harnessGeprueft(args.ziel.harness);
|
|
383
|
+
const fund = await this.pluginFinden(args.plugin);
|
|
384
|
+
const schluessel = pluginSchluessel(fund.plugin);
|
|
385
|
+
const entfernt = [];
|
|
386
|
+
if (kannPlugins(harness)) {
|
|
387
|
+
await harness.plugins.entfernen(fund.plugin.name, fund.plugin.marketplace, args.ziel);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
// Ein Skill bleibt liegen, wenn ihn ein anderes Plugin am selben Ziel braucht.
|
|
391
|
+
const gebraucht = await this.fremdeSkillNamen(harness, args.ziel, schluessel);
|
|
392
|
+
const verzeichnis = harness.zielVerzeichnis(args.ziel.ort);
|
|
393
|
+
for (const skill of await this.flacheSkillListe(fund.plugin.name, fund.quelle)) {
|
|
394
|
+
if (gebraucht.has(skill.name))
|
|
395
|
+
continue;
|
|
396
|
+
await this.p.ziele.linkEntfernen(args.ziel, verzeichnis, skill.name);
|
|
397
|
+
entfernt.push(skill.name);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const ergebnis = {
|
|
401
|
+
plugin: fund.plugin,
|
|
402
|
+
art: kannPlugins(harness) ? 'installiert' : 'verlinkt',
|
|
403
|
+
skills: entfernt,
|
|
404
|
+
imZeiger: false,
|
|
405
|
+
};
|
|
406
|
+
if (args.lokal === true || args.ziel.ort.art !== 'arbeitsverzeichnis')
|
|
407
|
+
return ergebnis;
|
|
408
|
+
const zeigerPfad = await this.bindungFinden(args.ziel);
|
|
409
|
+
if (zeigerPfad === undefined)
|
|
410
|
+
return ergebnis;
|
|
411
|
+
const zeiger = await this.p.zeiger.lesen(zeigerPfad);
|
|
412
|
+
const vorher = zeiger.plugins.length;
|
|
413
|
+
zeiger.plugins = zeiger.plugins.filter((p) => p !== schluessel);
|
|
414
|
+
if (zeiger.plugins.length === vorher)
|
|
415
|
+
return ergebnis;
|
|
416
|
+
await this.p.zeiger.schreiben(zeigerPfad, zeiger);
|
|
417
|
+
return { ...ergebnis, imZeiger: true };
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Fund zu einem Skill aus dem Zeiger (E33). Die Einschränkung wird zuerst nach Composer-Regeln
|
|
421
|
+
* gegen die Tags der Quellen ausgewertet, der höchste passende Tag gewinnt. Findet sich keiner,
|
|
422
|
+
* gilt der Text als genauer Ref-Name gegen alle Refs, damit Tags und Branches ohne
|
|
423
|
+
* Versions-Schema erreichbar bleiben. Nie auf den Standard-Ref ausweichen (B8).
|
|
424
|
+
*/
|
|
425
|
+
async fundAusZeiger(name, einschraenkung, quelleHinweis) {
|
|
426
|
+
if (einschraenkung === null)
|
|
427
|
+
return await this.skillFinden(name, quelleHinweis);
|
|
428
|
+
const zustand = await this.p.zustand.lesen();
|
|
429
|
+
const quellen = zustand.quellen.filter((q) => quelleHinweis === undefined || quelleSchluessel(q).includes(quelleHinweis));
|
|
430
|
+
for (const quelle of quellen) {
|
|
431
|
+
const tags = await this.p.quellen.tags(quelle).catch(() => []);
|
|
432
|
+
const ref = passenderRef(tags, einschraenkung);
|
|
433
|
+
if (ref === null)
|
|
434
|
+
continue;
|
|
435
|
+
const skill = (await this.p.quellen.skills(quelle, ref)).find((s) => s.name === name);
|
|
436
|
+
if (skill)
|
|
437
|
+
return { quelle, ref, skill };
|
|
438
|
+
}
|
|
439
|
+
for (const quelle of quellen) {
|
|
440
|
+
const refs = await this.p.quellen.refs(quelle).catch(() => []);
|
|
441
|
+
if (!refs.includes(einschraenkung))
|
|
442
|
+
continue;
|
|
443
|
+
const skill = (await this.p.quellen.skills(quelle, einschraenkung)).find((s) => s.name === name);
|
|
444
|
+
if (skill)
|
|
445
|
+
return { quelle, ref: einschraenkung, skill };
|
|
446
|
+
}
|
|
447
|
+
throw new KeinPassenderRef(name, einschraenkung);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Abgleich (E20, E25, E26): Zeiger des Verzeichnisses lesen, fehlende Quellen übernehmen,
|
|
451
|
+
* fehlende Links legen, Abweichungen melden. Ohne Zeiger oder ohne Harness passiert nichts.
|
|
452
|
+
*/
|
|
453
|
+
async abgleich(pfad) {
|
|
454
|
+
const verzeichnis = path.resolve(pfad ?? process.cwd());
|
|
455
|
+
const ergebnis = { gelegt: [], vorhanden: [], abweichend: [], fehler: [], hinweise: [] };
|
|
456
|
+
const zustand = await this.p.zustand.lesen();
|
|
457
|
+
// Zeiger nach E26: erst die gemerkte Bindung, dann die Suche im Verzeichnis.
|
|
458
|
+
let zeigerPfad = await this.bindungFuerVerzeichnis(verzeichnis);
|
|
459
|
+
if (zeigerPfad === undefined) {
|
|
460
|
+
const fund = await this.p.zeiger.finden(verzeichnis);
|
|
461
|
+
if (fund.ergebnis === 'gefunden')
|
|
462
|
+
zeigerPfad = fund.pfad;
|
|
463
|
+
else if (fund.ergebnis === 'mehrdeutig')
|
|
464
|
+
throw new ZeigerMehrdeutig(verzeichnis, fund.kandidaten);
|
|
465
|
+
}
|
|
466
|
+
if (zeigerPfad === undefined) {
|
|
467
|
+
ergebnis.hinweise.push(`Kein Zeiger unter ${verzeichnis}, nichts zu tun.`);
|
|
468
|
+
return ergebnis;
|
|
469
|
+
}
|
|
470
|
+
ergebnis.zeigerPfad = zeigerPfad;
|
|
471
|
+
const harnesses = (await this.vorhandeneHarnesses()).filter((h) => h.orte().includes('arbeitsverzeichnis'));
|
|
472
|
+
if (harnesses.length === 0) {
|
|
473
|
+
ergebnis.hinweise.push('Kein Harness auf diesem Rechner, nichts zu tun (E20).');
|
|
474
|
+
return ergebnis;
|
|
475
|
+
}
|
|
476
|
+
const zeiger = await this.p.zeiger.lesen(zeigerPfad);
|
|
477
|
+
for (const quelle of zeiger.quellen) {
|
|
478
|
+
const key = quelleSchluessel(quelle);
|
|
479
|
+
if (zustand.quellen.some((q) => quelleSchluessel(q) === key))
|
|
480
|
+
continue;
|
|
481
|
+
zustand.quellen.push(quelle);
|
|
482
|
+
ergebnis.gelegt.push(`Quelle ${key}`);
|
|
483
|
+
}
|
|
484
|
+
if (!zustand.arbeitsverzeichnisse.includes(verzeichnis))
|
|
485
|
+
zustand.arbeitsverzeichnisse.push(verzeichnis);
|
|
486
|
+
for (const harness of harnesses) {
|
|
487
|
+
const ziel = arbeitsverzeichnisZiel(harness.name, verzeichnis);
|
|
488
|
+
const schluessel = zielSchluessel(ziel);
|
|
489
|
+
if (zustand.bindungen.some((b) => zielSchluessel(b.ziel) === schluessel))
|
|
490
|
+
continue;
|
|
491
|
+
zustand.bindungen.push({ ziel, zeigerPfad });
|
|
492
|
+
}
|
|
493
|
+
// Vor dem ersten Link schreiben, damit Quellen und Bindung für die Ports sichtbar sind.
|
|
494
|
+
await this.p.zustand.schreiben(zustand);
|
|
495
|
+
for (const harness of harnesses) {
|
|
496
|
+
const ziel = arbeitsverzeichnisZiel(harness.name, verzeichnis);
|
|
497
|
+
const zielVerzeichnis = harness.zielVerzeichnis(ziel.ort);
|
|
498
|
+
const eintraege = await this.p.ziele.eintraege(ziel, zielVerzeichnis).catch(() => []);
|
|
499
|
+
// Skills, die über ein Plugin des Zeigers ans Ziel kommen, sind keine Abweichung.
|
|
500
|
+
const ausPlugins = new Set();
|
|
501
|
+
for (const eintrag of zeiger.plugins) {
|
|
502
|
+
const zerlegt = pluginSchluesselZerlegen(eintrag);
|
|
503
|
+
try {
|
|
504
|
+
const fund = await this.pluginFinden(zerlegt.name, undefined, zerlegt.marketplace);
|
|
505
|
+
for (const skill of await this.flacheSkillListe(fund.plugin.name, fund.quelle))
|
|
506
|
+
ausPlugins.add(skill.name);
|
|
507
|
+
if (await this.pluginLiegt(harness, ziel, fund, eintraege)) {
|
|
508
|
+
ergebnis.vorhanden.push(`${harness.name}: Plugin ${eintrag}`);
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
await this.pluginHerstellen(harness, ziel, fund);
|
|
512
|
+
ergebnis.gelegt.push(`${harness.name}: Plugin ${eintrag}`);
|
|
513
|
+
}
|
|
514
|
+
catch (fehler) {
|
|
515
|
+
ergebnis.fehler.push(`${harness.name}: Plugin ${eintrag}: ${fehlertext(fehler)}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
for (const [name, einschraenkung] of zeiger.skills) {
|
|
519
|
+
try {
|
|
520
|
+
const fund = await this.fundAusZeiger(name, einschraenkung);
|
|
521
|
+
const gefunden = eintraege.find((e) => e.art === 'link' && e.link.name === name);
|
|
522
|
+
// Ein eigener Link auf einen anderen Ref weicht ab und wird umgelegt (E33). Das trifft
|
|
523
|
+
// die Einschränkung, die inzwischen einen höheren Tag findet.
|
|
524
|
+
const alt = gefunden?.art === 'link' ? gefunden.link.checkout.ref : undefined;
|
|
525
|
+
if (alt === fund.ref) {
|
|
526
|
+
ergebnis.vorhanden.push(`${harness.name}: ${name}`);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
await this.linkAnlegen(harness, ziel, fund);
|
|
530
|
+
ergebnis.gelegt.push(alt === undefined ? `${harness.name}: ${name}` : `${harness.name}: ${name} (${alt} → ${fund.ref})`);
|
|
531
|
+
}
|
|
532
|
+
catch (fehler) {
|
|
533
|
+
ergebnis.fehler.push(`${harness.name}: ${name}: ${fehlertext(fehler)}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// Eigene Links, die im Zeiger fehlen, werden gemeldet und nicht entfernt. Fremde bleiben unberührt.
|
|
537
|
+
for (const eintrag of eintraege) {
|
|
538
|
+
const name = eintrag.art === 'fremd' ? undefined : eintrag.art === 'link' ? eintrag.link.name : eintrag.name;
|
|
539
|
+
if (name === undefined || zeiger.skills.has(name) || ausPlugins.has(name))
|
|
540
|
+
continue;
|
|
541
|
+
ergebnis.abweichend.push(`${harness.name}: ${name} steht nicht im Zeiger`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return ergebnis;
|
|
545
|
+
}
|
|
546
|
+
/** Liegt das Plugin schon am Ziel: installiert, oder mit allen Skills der flachen Liste verlinkt. */
|
|
547
|
+
async pluginLiegt(harness, ziel, fund, eintraege) {
|
|
548
|
+
if (kannPlugins(harness)) {
|
|
549
|
+
const installiert = await harness.plugins.installierte(ziel).catch(() => []);
|
|
550
|
+
return installiert.includes(pluginSchluessel(fund.plugin));
|
|
551
|
+
}
|
|
552
|
+
const namen = new Set(eintraege.map((e) => (e.art === 'link' ? e.link.name : e.art === 'fremd' ? e.name : e.name)));
|
|
553
|
+
const skills = await this.flacheSkillListe(fund.plugin.name, fund.quelle);
|
|
554
|
+
return skills.length > 0 && skills.every((s) => namen.has(s.name));
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Aktualisieren (E14, E15): jeden Checkout unter der Wurzel auf den Stand seines Refs bringen.
|
|
558
|
+
* Lokale Quellen haben keinen Checkout und bleiben unberührt (B11). Links zeigen auf die
|
|
559
|
+
* Worktree-Pfade, die dabei stehen bleiben, also müssen sie nicht erneuert werden.
|
|
560
|
+
*/
|
|
561
|
+
async aktualisieren() {
|
|
562
|
+
const checkouts = [];
|
|
563
|
+
for (const checkout of await this.p.quellen.checkouts())
|
|
564
|
+
checkouts.push({ checkout, aktualisierung: await this.p.quellen.aktualisieren(checkout) });
|
|
565
|
+
// Nach den Checkouts die Plugins nachziehen (E14).
|
|
566
|
+
const pluginHarnesses = [];
|
|
567
|
+
for (const harness of await this.vorhandeneHarnesses()) {
|
|
568
|
+
if (!kannPlugins(harness))
|
|
569
|
+
continue;
|
|
570
|
+
await harness.plugins.aktualisieren();
|
|
571
|
+
pluginHarnesses.push(harness.name);
|
|
572
|
+
}
|
|
573
|
+
return { checkouts, pluginHarnesses };
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Übernahme (B12, E31): fremde Symlinks eines Ziels durch eigene Links auf einen hmgr-Checkout
|
|
577
|
+
* derselben Quelle am selben Ref ersetzen. Die Arbeitskopie bleibt unberührt und wird nicht als
|
|
578
|
+
* Quelle registriert, das ist B11. Ein Eintrag, der nicht auflösbar ist, bleibt liegen und wird
|
|
579
|
+
* gemeldet, damit ein Lauf über viele Einträge nicht an einem einzelnen abbricht.
|
|
580
|
+
*
|
|
581
|
+
* `probelauf` schreibt weder Zustand noch Checkout noch Link. Die Skill-Liste der Quelle wird
|
|
582
|
+
* trotzdem gelesen, sonst wäre der Grund „skill-unbekannt" nicht zu haben. Der Git-Adapter legt
|
|
583
|
+
* dabei den Basis-Clone der Quelle an, das Ziel und der Zustand bleiben unangetastet.
|
|
584
|
+
*/
|
|
585
|
+
async uebernehmen(args) {
|
|
586
|
+
const harness = await this.harnessGeprueft(args.ziel.harness);
|
|
587
|
+
if (!harness.orte().includes(args.ziel.ort.art))
|
|
588
|
+
throw new OrtNichtUnterstuetzt(harness.name, args.ziel.ort.art);
|
|
589
|
+
const probelauf = args.probelauf === true;
|
|
590
|
+
const verzeichnis = harness.zielVerzeichnis(args.ziel.ort);
|
|
591
|
+
const bekannt = new Set((await this.p.zustand.lesen()).quellen.map(quelleSchluessel));
|
|
592
|
+
const eintraege = [];
|
|
593
|
+
for (const eintrag of await this.p.ziele.eintraege(args.ziel, verzeichnis)) {
|
|
594
|
+
// Eigene Links sind schon fertig, sie kommen nicht in die Meldung.
|
|
595
|
+
if (eintrag.art !== 'fremd')
|
|
596
|
+
continue;
|
|
597
|
+
if (args.name !== undefined && eintrag.name !== args.name)
|
|
598
|
+
continue;
|
|
599
|
+
const name = eintrag.name;
|
|
600
|
+
const liegenLassen = (grund) => void eintraege.push({ ergebnis: 'uebersprungen', name, grund });
|
|
601
|
+
if (eintrag.linkZiel === undefined) {
|
|
602
|
+
liegenLassen('kein-link');
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
const kopie = await this.p.quellen.arbeitskopie(eintrag.linkZiel);
|
|
606
|
+
if (kopie === null) {
|
|
607
|
+
liegenLassen('keine-arbeitskopie');
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (kopie.url === null) {
|
|
611
|
+
liegenLassen('keine-url');
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
// Ohne Branch gibt es keinen Ref, auf den ein Checkout zeigen könnte (detached HEAD).
|
|
615
|
+
if (kopie.ref === null) {
|
|
616
|
+
liegenLassen('kein-branch');
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
const quelle = { art: 'git', url: kopie.url };
|
|
620
|
+
const ref = kopie.ref;
|
|
621
|
+
const skills = await this.p.quellen.skills(quelle, ref);
|
|
622
|
+
const skill = skills.find((s) => s.pfad === kopie.relativerPfad);
|
|
623
|
+
if (skill === undefined) {
|
|
624
|
+
liegenLassen('skill-unbekannt');
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
const quelleNeu = !bekannt.has(quelleSchluessel(quelle));
|
|
628
|
+
bekannt.add(quelleSchluessel(quelle));
|
|
629
|
+
if (!probelauf) {
|
|
630
|
+
if (quelleNeu)
|
|
631
|
+
await this.quelleHinzufuegen(quelle);
|
|
632
|
+
await this.linkAusFund(harness, args.ziel, { quelle, ref, skill }, true, args.lokal);
|
|
633
|
+
}
|
|
634
|
+
eintraege.push({ ergebnis: 'uebernommen', name, quelle, ref, skill, quelleNeu });
|
|
635
|
+
}
|
|
636
|
+
return { ziel: args.ziel, probelauf, eintraege };
|
|
637
|
+
}
|
|
638
|
+
/** Versionseinschränkungen des Zeigers, an den ein Ziel gebunden ist (E33). Ohne Zeiger leer. */
|
|
639
|
+
async einschraenkungen(ziel) {
|
|
640
|
+
const ergebnis = new Map();
|
|
641
|
+
const zeigerPfad = await this.bindungFinden(ziel);
|
|
642
|
+
if (zeigerPfad === undefined)
|
|
643
|
+
return ergebnis;
|
|
644
|
+
const zeiger = await this.p.zeiger.lesen(zeigerPfad).catch(() => undefined);
|
|
645
|
+
if (zeiger === undefined)
|
|
646
|
+
return ergebnis;
|
|
647
|
+
for (const [name, einschraenkung] of zeiger.skills)
|
|
648
|
+
if (einschraenkung !== null)
|
|
649
|
+
ergebnis.set(name, einschraenkung);
|
|
650
|
+
return ergebnis;
|
|
651
|
+
}
|
|
652
|
+
/** Ist-Zustand: alle Ziele der vorhandenen Harnesses, aus dem Dateisystem gelesen. */
|
|
653
|
+
async istZustand() {
|
|
654
|
+
const zustand = await this.p.zustand.lesen();
|
|
655
|
+
const ergebnis = [];
|
|
656
|
+
for (const harness of await this.vorhandeneHarnesses()) {
|
|
657
|
+
const ziele = [];
|
|
658
|
+
if (harness.orte().includes('home'))
|
|
659
|
+
ziele.push({ harness: harness.name, ort: { art: 'home' } });
|
|
660
|
+
if (harness.orte().includes('profil') && kannProfile(harness))
|
|
661
|
+
for (const name of await harness.profile())
|
|
662
|
+
ziele.push({ harness: harness.name, ort: { art: 'profil', name } });
|
|
663
|
+
if (harness.orte().includes('arbeitsverzeichnis'))
|
|
664
|
+
for (const pfad of zustand.arbeitsverzeichnisse)
|
|
665
|
+
ziele.push(arbeitsverzeichnisZiel(harness.name, pfad));
|
|
666
|
+
for (const ziel of ziele) {
|
|
667
|
+
const verzeichnis = harness.zielVerzeichnis(ziel.ort);
|
|
668
|
+
const eintraege = await this.p.ziele.eintraege(ziel, verzeichnis).catch(() => []);
|
|
669
|
+
const plugins = kannPlugins(harness)
|
|
670
|
+
? await harness.plugins.installierte(ziel).catch(() => [])
|
|
671
|
+
: undefined;
|
|
672
|
+
ergebnis.push({
|
|
673
|
+
ziel,
|
|
674
|
+
verzeichnis,
|
|
675
|
+
eintraege,
|
|
676
|
+
einschraenkungen: await this.einschraenkungen(ziel),
|
|
677
|
+
...(plugins === undefined ? {} : { plugins }),
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return ergebnis;
|
|
682
|
+
}
|
|
683
|
+
}
|