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
|
}
|
}
|