Compare commits
1
Commits
latest
...
08f7c21450
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08f7c21450 |
No files matched your search
@@ -0,0 +1,76 @@
|
||||
# Baut das Windows-Paket und haengt es an die Veroeffentlichung `latest`.
|
||||
#
|
||||
# Ausgeloest von einem Tag `v0.5.1` oder von Hand ueber die Actions-Seite.
|
||||
# Der Updater auf Abdus Rechner fragt immer dieselbe Adresse ab:
|
||||
# https://git.zeasy.dev/ZeasyAlex/abdulive/releases/download/latest/abdulive.json
|
||||
# Deshalb wird `latest` bei jedem Lauf geloescht und neu angelegt.
|
||||
|
||||
name: Paket bauen und veroeffentlichen
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Versionsnummer, z. B. 0.5.1'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
paket:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Tests und Typpruefung
|
||||
run: |
|
||||
node --test "packages/**/*.test.ts" "apps/**/*.test.ts"
|
||||
npm i --no-save typescript@5.9 @types/node
|
||||
npx tsc --noEmit
|
||||
|
||||
- name: Version bestimmen
|
||||
id: v
|
||||
run: |
|
||||
if [ -n "${{ inputs.version }}" ]; then
|
||||
echo "wert=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "wert=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Laedt Node, ffmpeg und whisper.cpp bei ihren Herausgebern und packt
|
||||
# alles zusammen. Siehe scripts/build-package.ts und ADR-0007.
|
||||
- name: Paket bauen
|
||||
run: node scripts/build-package.ts --version "${{ steps.v.outputs.wert }}"
|
||||
|
||||
- name: Veroeffentlichung ersetzen
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
||||
VERSION: ${{ steps.v.outputs.wert }}
|
||||
run: |
|
||||
set -eu
|
||||
auth="Authorization: token $TOKEN"
|
||||
|
||||
# Vorhandene `latest` weg - sie soll immer den aktuellen Stand tragen
|
||||
alt=$(curl -sf -H "$auth" "$API/releases/tags/latest" \
|
||||
| node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(d).id))}catch{}})' || true)
|
||||
if [ -n "$alt" ]; then
|
||||
curl -sf -X DELETE -H "$auth" "$API/releases/$alt" || true
|
||||
fi
|
||||
|
||||
id=$(curl -sf -H "$auth" -H 'Content-Type: application/json' -X POST "$API/releases" \
|
||||
-d "{\"tag_name\":\"latest\",\"target_commitish\":\"$GITHUB_SHA\",\"name\":\"abdulive $VERSION\",\"body\":\"Paket entpacken und darin install.ps1 ausfuehren.\"}" \
|
||||
| node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>process.stdout.write(String(JSON.parse(d).id)))')
|
||||
|
||||
for f in dist/abdulive-"$VERSION"-win-x64.zip dist/abdulive.json; do
|
||||
curl -sf -H "$auth" -X POST "$API/releases/$id/assets?name=$(basename "$f")" \
|
||||
-F "attachment=@$f" -o /dev/null
|
||||
echo " hochgeladen: $(basename "$f")"
|
||||
done
|
||||
|
||||
- name: Was drin ist
|
||||
run: cat dist/abdulive.json
|
||||
+10
-1
@@ -5,7 +5,7 @@
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { isNewer, looksLikeLogin } from './update.ts';
|
||||
import { isNewer, looksLikeLogin, parseJsonLoose } from './update.ts';
|
||||
|
||||
test('vergleicht Versionen als Zahlen, nicht als Text', () => {
|
||||
// Der Fall, der bei Zeichenkettenvergleich falsch herauskaeme
|
||||
@@ -42,3 +42,12 @@ test('haelt ein echtes Manifest nicht faelschlich fuer HTML', () => {
|
||||
}));
|
||||
assert.equal(looksLikeLogin(null, manifest), false);
|
||||
});
|
||||
|
||||
test('ein Byte Order Mark macht das Manifest nicht unlesbar', () => {
|
||||
// Windows PowerShell 5.1 schreibt bei `Set-Content -Encoding utf8` eines,
|
||||
// und Notepad ebenso. JSON.parse verschluckt sich daran.
|
||||
const mitBom = '{"version":"0.5.0"}';
|
||||
assert.throws(() => JSON.parse(mitBom));
|
||||
assert.deepEqual(parseJsonLoose(mitBom), { version: '0.5.0' });
|
||||
assert.deepEqual(parseJsonLoose('{"a":1}'), { a: 1 });
|
||||
});
|
||||
+8
-2
@@ -56,10 +56,15 @@ export function looksLikeLogin(contentType: string | null, body: Buffer): boolea
|
||||
head.includes('/user/login');
|
||||
}
|
||||
|
||||
/** JSON.parse verschluckt sich an einem Byte Order Mark - Windows schreibt eines. */
|
||||
export function parseJsonLoose(text: string): unknown {
|
||||
return JSON.parse(text.replace(/^/, ''));
|
||||
}
|
||||
|
||||
async function localVersion(): Promise<string> {
|
||||
const raw = await fs.readFile(path.join(installRoot, 'version.json'), 'utf8').catch(() => null);
|
||||
if (!raw) return '0';
|
||||
return (JSON.parse(raw) as { version?: string }).version ?? '0';
|
||||
return (parseJsonLoose(raw) as { version?: string }).version ?? '0';
|
||||
}
|
||||
|
||||
/** Adresse und - falls hinterlegt - der Zugriffstoken. */
|
||||
@@ -114,7 +119,8 @@ export async function checkForUpdate(opts: { force?: boolean } = {}): Promise<vo
|
||||
console.log(`Quelle: ${url}`);
|
||||
console.log(`Installiert: ${current}`);
|
||||
|
||||
const manifest = JSON.parse((await hole(`${url}/abdulive.json`, headers)).toString('utf8')) as Manifest;
|
||||
const manifest = parseJsonLoose(
|
||||
(await hole(`${url}/abdulive.json`, headers)).toString('utf8')) as Manifest;
|
||||
console.log(`Angeboten: ${manifest.version}`);
|
||||
|
||||
if (!opts.force && !isNewer(manifest.version, current)) {
|
||||
|
||||
@@ -29,7 +29,7 @@ ingest:
|
||||
# Anmeldung (privates Gitea), gehoert der Zugriffstoken NICHT hierher,
|
||||
# sondern auf die Einrichtungsseite - siehe ADR-0005.
|
||||
update:
|
||||
url: ""
|
||||
url: "https://git.zeasy.dev/ZeasyAlex/abdulive/releases/download/latest"
|
||||
|
||||
obs:
|
||||
url: ws://127.0.0.1:4455
|
||||
|
||||
+7
-7
@@ -60,9 +60,10 @@ abdulive fetch-model
|
||||
abdulive check-env
|
||||
```
|
||||
|
||||
Einen Einzeiler zum Nachladen gibt es nicht — `git.zeasy.dev` ist komplett
|
||||
angemeldet-only. **Wo die Pakete liegen sollen, ist die naechste offene
|
||||
Entscheidung**, und ohne sie funktioniert auch `abdulive update` nicht.
|
||||
Das Repo ist seit 2026-09-09 oeffentlich, die Pakete haengen an der
|
||||
Veroeffentlichung `latest`. Einen `irm … | iex`-Einzeiler gibt es trotzdem
|
||||
nicht — Rohdateien liefert Gitea als `text/plain`, und ein Fehlschlag saehe
|
||||
aus wie ein PowerShell-Syntaxfehler statt wie ein Fehlschlag.
|
||||
|
||||
Ausführlich in [`05-setup.md`](05-setup.md).
|
||||
|
||||
@@ -120,7 +121,6 @@ Verbesserungen stehen in [`offene-punkte.md`](offene-punkte.md).
|
||||
Und: Das Aufräumen im Arbeitsordner fehlt komplett. Rohdateien sammeln sich
|
||||
unbegrenzt an. Das ist die Lücke, die als Erstes weh tun wird.
|
||||
|
||||
Der Updater ist gebaut und in Teilen geprüft — Manifest, Download, Prüfsumme
|
||||
und Bereitlegen laufen. Der eigentliche Tausch durch `launcher.ps1` ist noch
|
||||
nie im Feld passiert. Vor dem ersten echten Update einmal künstlich
|
||||
durchspielen.
|
||||
Der Updater ist einmal komplett durchgelaufen: 0.4.0 erkannt, 119 MB von
|
||||
`git.zeasy.dev` geladen, Prüfsumme geprüft, bereitgelegt, von `launcher.ps1`
|
||||
getauscht, `pending` aufgeräumt. Was noch nie lief, ist die CI-Pipeline.
|
||||
+3
-21
@@ -68,33 +68,15 @@ Der Durchlauf am echten Clip war brauchbar, aber nicht gut. Was auffiel:
|
||||
|
||||
## Auslieferung
|
||||
|
||||
- [ ] **Wo liegen die Pakete?** Das ist die Entscheidung, die als Naechstes
|
||||
ansteht — ohne sie funktioniert `abdulive update` gar nicht.
|
||||
`git.zeasy.dev` verlangt **instanzweit** eine Anmeldung: `/explore`,
|
||||
die API und jede Rohdatei antworten mit 303 auf `/user/login`. Ein
|
||||
`irm … | iex`-Einzeiler bekommt dadurch die Anmeldeseite und versucht,
|
||||
sie als PowerShell auszufuehren. Drei Wege:
|
||||
*(a)* eine Ablage ohne Anmeldung, etwa `dl.zeasy.software` — am
|
||||
einfachsten, und das Paket enthaelt ohnehin den vollstaendigen Quelltext
|
||||
als lesbare `.ts`-Dateien;
|
||||
*(b)* Gitea-Release plus Lese-Token auf Abdus Rechner — der Token darf
|
||||
dann aber das ganze private Repo lesen;
|
||||
*(c)* kein Updater, Alexander spielt neue Fassungen per Fernwartung ein.
|
||||
Code und Installer sind auf *(a)* und *(b)* vorbereitet: `update.url` in
|
||||
der `config.yaml`, Token im verschluesselten Speicher.
|
||||
|
||||
- [ ] **Code-Signing.** Das Paket ist nicht signiert, SmartScreen warnt bei
|
||||
jeder Installation vor einem unbekannten Herausgeber. Ein
|
||||
OV-Zertifikat kostet rund 200 EUR im Jahr, ein EV-Zertifikat mehr und
|
||||
braucht Hardware. Solange nur Abdu installiert, reicht ein Klick auf
|
||||
"Trotzdem ausfuehren" - sobald jemand Drittes das Paket bekommt, nicht
|
||||
mehr.
|
||||
- [ ] **CI auf git.zeasy.dev.** `scripts/build-package.ts` laeuft bisher von
|
||||
Hand. Als Forgejo-Action beim Tag gebaut und als Veroeffentlichung
|
||||
hochgeladen, findet `abdulive update` sie von allein.
|
||||
- [ ] **Update noch nie im Feld gelaufen.** Bereitlegen und Pruefsumme sind
|
||||
getestet, der Tausch durch `launcher.ps1` nur gelesen. Vor dem ersten
|
||||
echten Update einmal kuenstlich durchspielen.
|
||||
- [ ] **CI einmal wirklich laufen lassen.** `.gitea/workflows/release.yml`
|
||||
ist geschrieben, der Runner (`ubuntu-latest`) steht — ausgefuehrt wurde
|
||||
sie aber noch nie. Beim ersten Tag zusehen, ob sie durchlaeuft.
|
||||
- [ ] **Hardware-Encoding.** Der mitgelieferte ffmpeg kann QSV, AMF und NVENC
|
||||
(`--enable-libvpl`, `--enable-amf`, `--enable-ffnvcodec`) - aus WSL2
|
||||
heraus ging das nicht. `clipper.ffmpeg.encoder` steht weiter auf
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Der YAML-Leser ist selbst geschrieben (ADR-0001: keine Abhaengigkeiten) und
|
||||
// liest die Konfiguration, an der alles haengt - hatte aber bis hierher keinen
|
||||
// einzigen Test. Geprueft wird genau der Sprachumfang, den config/*.yaml
|
||||
// benutzt.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseYaml } from './yaml.ts';
|
||||
|
||||
test('verschachtelte Maps ueber Einrueckung', () => {
|
||||
assert.deepEqual(parseYaml('channel:\n login: abdulive_\n language: de\n'),
|
||||
{ channel: { login: 'abdulive_', language: 'de' } });
|
||||
});
|
||||
|
||||
test('erkennt Zahlen, Wahrheitswerte und Leeres', () => {
|
||||
const y = parseYaml([
|
||||
'a: 42', 'b: -7', 'c: 3.5', 'd: true', 'e: false', 'f:', 'g: ~',
|
||||
].join('\n'));
|
||||
assert.deepEqual(y, { a: 42, b: -7, c: 3.5, d: true, e: false, f: null, g: null });
|
||||
});
|
||||
|
||||
test('Anfuehrungszeichen bleiben Text', () => {
|
||||
// Wichtig fuer update.url - sonst wuerde aus "0.5" eine Zahl
|
||||
const y = parseYaml('update:\n url: "https://git.zeasy.dev/x"\n v: "0.5"\n');
|
||||
assert.deepEqual(y, { update: { url: 'https://git.zeasy.dev/x', v: '0.5' } });
|
||||
});
|
||||
|
||||
test('Inline-Maps, wie sie im Layout stehen', () => {
|
||||
const y = parseYaml('crop: { x: 0, y: 0, w: 480, h: 270 }\n');
|
||||
assert.deepEqual(y, { crop: { x: 0, y: 0, w: 480, h: 270 } });
|
||||
});
|
||||
|
||||
test('Listen aus Skalaren', () => {
|
||||
const y = parseYaml('chapterScenes: [Gameplay, "Just Chatting"]\n');
|
||||
assert.deepEqual(y, { chapterScenes: ['Gameplay', 'Just Chatting'] });
|
||||
});
|
||||
|
||||
test('Kommentare und Leerzeilen stoeren nicht', () => {
|
||||
const y = parseYaml('# Kopfzeile\n\nobs:\n # dazwischen\n url: ws://127.0.0.1:4455\n');
|
||||
assert.deepEqual(y, { obs: { url: 'ws://127.0.0.1:4455' } });
|
||||
});
|
||||
|
||||
test('Zeilenenden nach Windows-Art', () => {
|
||||
// config.yaml wird auf dem Stream-PC bearbeitet
|
||||
assert.deepEqual(parseYaml('a:\r\n b: 1\r\n'), { a: { b: 1 } });
|
||||
});
|
||||
|
||||
test('ein Byte Order Mark verstellt nicht den ersten Schluessel', () => {
|
||||
// Notepad und `Set-Content -Encoding utf8` unter Windows PowerShell 5.1
|
||||
// schreiben eines. Ohne Behandlung hiesse der erste Schluessel "channel",
|
||||
// und die Einstellung wuerde stillschweigend ignoriert - ohne Fehlermeldung.
|
||||
const y = parseYaml('\uFEFFchannel:\n login: abdulive_\n');
|
||||
assert.deepEqual(y, { channel: { login: 'abdulive_' } });
|
||||
assert.ok(!Object.keys(y).some((k) => k.charCodeAt(0) === 0xfeff));
|
||||
});
|
||||
@@ -51,7 +51,11 @@ function stripComment(line: string): string {
|
||||
type Frame = { indent: number; target: YamlMap; listKey?: string };
|
||||
|
||||
export function parseYaml(text: string): YamlMap {
|
||||
const lines = text.split(/\r?\n/);
|
||||
// Byte Order Mark abschneiden. Notepad und `Set-Content -Encoding utf8`
|
||||
// unter Windows PowerShell 5.1 schreiben eines an den Dateianfang; ohne
|
||||
// diese Zeile heisst der erste Schluessel dann "channel" und die
|
||||
// Einstellung wird stillschweigend ignoriert.
|
||||
const lines = text.replace(/^/, '').split(/\r?\n/);
|
||||
const root: YamlMap = {};
|
||||
const stack: Frame[] = [{ indent: -1, target: root }];
|
||||
let i = 0;
|
||||
|
||||
Reference in new issue
Block a user