Server-Bibliothek
vor 18 Std. 1f44739ba685cd77dc804adcc58fbf584b21e52b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { execFile } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import { abgleichMisserfolg, alsIsoDatum, mitTemporaeremOrdner, type Abgleichauftrag, type Bezugsquelle, type QuellenAbgleich } from './basis';
 
const execFileAsync = promisify(execFile);
 
/** winget: lädt das Paket per `winget download` (nur unter Windows verfügbar). */
export class WingetQuelle implements Bezugsquelle {
  async gleicheAb(auftrag: Abgleichauftrag): Promise<QuellenAbgleich> {
    // In einen temporären Ordner laden — der bisherige Stand bleibt erhalten, falls etwas schiefgeht.
    const ablage = mitTemporaeremOrdner(auftrag.zielordner);
    try {
      const { stdout } = await execFileAsync(
        'winget',
        [
          'download',
          '--id',
          auftrag.wert,
          '--exact',
          '--download-directory',
          ablage.ordner,
          '--accept-package-agreements',
          '--accept-source-agreements',
          '--disable-interactivity',
        ],
        { timeout: 300000, windowsHide: true }
      );
      const geladeneDateien = fs.readdirSync(ablage.ordner);
      const installer = geladeneDateien.find((name) => /\.(exe|msi|msix|appx|appxbundle|zip)$/i.test(name));
      if (!installer) {
        ablage.verwerfen();
        return abgleichMisserfolg(auftrag.aktuelleVersion, 'winget hat keine Installer-Datei geliefert.');
      }
      // winget legt zusätzlich sein Manifest (.yaml) ab — im Ablageordner soll nur der Installer liegen.
      for (const name of geladeneDateien) {
        if (name !== installer) {
          fs.rmSync(path.join(ablage.ordner, name), { recursive: true, force: true });
        }
      }
      const version = versionAusAusgabe(stdout) || new Date().toISOString();
      ablage.uebernehmen();
      return {
        erfolg: true,
        geaendert: true,
        version,
        dateiname: installer,
        meldung: `Über winget geladen (${version}).`,
        // Das Veröffentlichungsdatum steht nur in der Paket-Auskunft, nicht in der Download-Ausgabe.
        veroeffentlichtAm: alsIsoDatum(await veroeffentlichungsdatum(auftrag.wert)),
      };
    } catch (fehler) {
      ablage.verwerfen();
      return abgleichMisserfolg(auftrag.aktuelleVersion, `winget fehlgeschlagen: ${fehler instanceof Error ? fehler.message : String(fehler)}`);
    }
  }
}
 
function versionAusAusgabe(ausgabe: string): string {
  const treffer = ausgabe.match(/[Vv]ersion[:\s]+([0-9][0-9.]*)/);
  return treffer ? treffer[1] : '';
}
 
/**
 * Fragt das Veröffentlichungsdatum beim Paket-Verzeichnis ab. `winget download`
 * gibt es nicht aus, `winget show` schon — dafür ein zweiter, kurzer Aufruf.
 */
async function veroeffentlichungsdatum(paketId: string): Promise<string> {
  try {
    const { stdout } = await execFileAsync(
      'winget',
      ['show', '--id', paketId, '--exact', '--accept-source-agreements', '--disable-interactivity'],
      { timeout: 120000, windowsHide: true }
    );
    const treffer = stdout.match(/(?:Release\s*Date|Ver[öo]ffentlich\w*)[:\s]+([0-9]{4}-[0-9]{2}-[0-9]{2})/i);
    return treffer ? treffer[1] : '';
  } catch {
    return ''; // ohne Datum ist die Zeile nur weniger aussagekräftig, kein Grund zu scheitern
  }
}