Zum Inhalt springen

Asset Pipeline Hooks

Die Asset-Pipeline laeuft nach dem Build-Schritt und vor dem Upload in den GitHub Release. Plugins koennen jede Stufe über sechs Hooks abfangen, die die interne Struktur der Pipeline spiegeln.

releaseAssets config
→ Glob-Matching → ResolvedAsset[]
→ [resolveAssets] : Asset-Liste filtern, ergaenzen oder ersetzen
→ [transformAsset] : jedes Asset signieren, strippen oder aufteilen (1 → N)
→ [compressAsset] : die Standard-Komprimierung tar.gz / zip ersetzen
→ [nameAsset] : die Name-Vorlage mit dynamischer Logik überschreiben
→ sha256 hash
→ [generateChecksums] : Prüfsummen-Dateien an die Asset-Liste anhängen
→ GitHub-Release-Upload (integriert)
→ [uploadAssets] : an zusätzliche Ziele hochladen (S3, R2, CDN usw.)
→ ReleaseContext zusammengebaut
→ [afterRelease] : finale Asset-Liste konsumieren (brew, notify usw.)
interface AssetPipelineHooks {
resolveAssets?(
resolved: ResolvedAsset[],
ctx: PubmContext,
): Promise<ResolvedAsset[]> | ResolvedAsset[];
transformAsset?(
asset: ResolvedAsset,
ctx: PubmContext,
): Promise<TransformedAsset | TransformedAsset[]>
| TransformedAsset
| TransformedAsset[];
compressAsset?(
asset: TransformedAsset,
ctx: PubmContext,
): Promise<CompressedAsset> | CompressedAsset;
nameAsset?(
asset: CompressedAsset,
ctx: PubmContext,
): string;
generateChecksums?(
assets: PreparedAsset[],
ctx: PubmContext,
): Promise<PreparedAsset[]> | PreparedAsset[];
uploadAssets?(
assets: PreparedAsset[],
ctx: PubmContext,
): Promise<UploadedAsset[]> | UploadedAsset[];
}

Alle Hooks werden zu PluginHooks hinzugefuegt und über das normale plugins-Array in pubm.config.ts registriert.

Wird einmal mit der vollständigen Liste der per Glob gefundenen Assets aufgerufen. Verwende diesen Hook, um Assets zu filtern, programmgesteuert gefundene Dateien hinzuzufuegen oder Metadaten anzuhängen.

Input: ResolvedAsset[] - Assets aus dem Glob-Matching mit geparsten Plattforminformationen Output: ResolvedAsset[] - die neue Asset-Liste (ersetzt die vorherige Liste)

const filterPlugin: PubmPlugin = {
name: "filter-assets",
hooks: {
resolveAssets(resolved, ctx) {
// Nur Assets für unterstützte Plattformen aufnehmen
return resolved.filter(
(a) =>
(a.platform.os === "darwin" || a.platform.os === "linux") &&
a.platform.arch === "arm64",
);
},
},
};

Wird vor der Komprimierung einmal pro Asset aufgerufen. Verwende diesen Hook, um Binaries zu signieren, strip auszufuehren, Nebendateien in ein Archiv aufzunehmen oder ein Eingabe-Asset in mehrere Ausgaben aufzuteilen.

Input: ResolvedAsset - ein Asset Output: TransformedAsset | TransformedAsset[] - ein oder mehrere transformierte Assets

TransformedAsset erweitert ResolvedAsset um das optionale Feld extraFiles?: string[]. Dateien aus extraFiles werden während der Komprimierung zusammen mit der Hauptdatei ins Archiv gepackt.

const codeSignPlugin: PubmPlugin = {
name: "code-sign",
hooks: {
async transformAsset(asset, ctx) {
if (asset.platform.os === "darwin") {
await exec("codesign", [
"--sign", process.env.SIGNING_IDENTITY,
"--options", "runtime",
asset.filePath,
]);
}
return asset;
},
},
};

Gib ein Array zurück, um aus einem Eingang mehrere Assets zu erzeugen. Jedes Element durchlaeuft den Rest der Pipeline unabhängig.

hooks: {
async transformAsset(asset, ctx) {
if (asset.platform.os === "windows") {
// Sowohl ein normales Binary als auch ein Debug-Build erzeugen
return [
asset,
{ ...asset, filePath: asset.filePath.replace(".exe", "-debug.exe") },
];
}
return asset;
},
},

Ersetzt die eingebaute Komprimierungslogik vollständig. Wenn gesetzt, ist der Hook dafür verantwortlich, ein CompressedAsset mit dem richtigen filePath und compressFormat zu erzeugen.

Input: TransformedAsset Output: CompressedAsset

Verwende das, wenn du ein Format brauchst, das pubm nicht nativ unterstützt, oder wenn du zusätzliche Dateien in das Archiv packen willst.

const customArchivePlugin: PubmPlugin = {
name: "deb-package",
hooks: {
async compressAsset(asset, ctx) {
if (asset.platform.os === "linux") {
const debPath = await buildDebPackage(asset.filePath, ctx.version);
return {
...asset,
filePath: debPath,
originalPath: asset.filePath,
compressFormat: false,
};
}
// Für andere Plattformen auf den Standard zurückfallen
return defaultCompress(asset);
},
},
};

Gibt den finalen Upload-Dateinamen zurück, ohne Erweiterung. Die Erweiterung wird aus compressFormat angehaengt. Verwende diesen Hook, wenn das Name-Vorlagensystem nicht ausdrucksstark genug ist.

Input: CompressedAsset Output: string - der Dateiname ohne Erweiterung

const dateStampPlugin: PubmPlugin = {
name: "date-stamp",
hooks: {
nameAsset(asset, ctx) {
const date = new Date().toISOString().slice(0, 10).replace(/-/g, "");
return `${ctx.packageName}-${ctx.version}-${date}-${asset.platform.raw}`;
},
},
};

Wird mit der vollständigen Liste vorbereiteter Assets aufgerufen. Verwende diesen Hook, um eine Prüfsummen-Manifestdatei anzuhaengen.

Input: PreparedAsset[] - alle Assets inklusive ihrer sha256-Werte Output: PreparedAsset[] - urspruengliche Assets plus zusätzliche Prüfsummen-Dateien

import { writeFileSync } from "node:fs";
import { join } from "node:path";
const checksumsPlugin: PubmPlugin = {
name: "checksums",
hooks: {
async generateChecksums(assets, ctx) {
const lines = assets
.map((a) => `${a.sha256} ${a.name}`)
.join("\n");
const checksumPath = join(ctx.runtime.tempDir, "SHA256SUMS.txt");
writeFileSync(checksumPath, lines + "\n");
const sha256 = await computeSha256(checksumPath);
return [
...assets,
{
filePath: checksumPath,
originalPath: checksumPath,
name: "SHA256SUMS.txt",
sha256,
platform: { raw: "" },
compressFormat: false,
config: { path: "", compress: false, name: "SHA256SUMS.txt" },
},
];
},
},
};

Wird nach dem Upload in den GitHub Release mit derselben PreparedAsset[]-Liste aufgerufen. Verwende diesen Hook, um Artefakte an weitere Ziele zu spiegeln. Die Ergebnisse jedes Plugins werden gesammelt und zusammengefuehrt - alle Plugins erhalten dieselbe Eingabeliste.

Input: PreparedAsset[] Output: UploadedAsset[] - hochgeladene Assets mit den Feldern url und target

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
const s3Plugin: PubmPlugin = {
name: "s3-mirror",
hooks: {
async uploadAssets(assets, ctx) {
const s3 = new S3Client({ region: "us-east-1" });
const bucket = process.env.RELEASE_BUCKET;
return Promise.all(
assets.map(async (asset) => {
const key = `releases/${ctx.version}/${asset.name}`;
await s3.send(
new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: readFileSync(asset.filePath),
}),
);
return {
...asset,
url: `https://${bucket}.s3.amazonaws.com/${key}`,
target: "s3",
};
}),
);
},
},
};

Wenn mehrere Plugins denselben Hook registrieren, werden sie in Registrierungsreihenfolge zusammengesetzt:

HookZusammensetzungsregel
resolveAssetsKette - die Ausgabe von Plugin N ist der Input von Plugin N+1
transformAssetKette - die Ausgabe von Plugin N ist der Input von Plugin N+1
compressAssetKette - die Ausgabe von Plugin N ist der Input von Plugin N+1
nameAssetLetzter gewinnt - jedes Plugin erhält dieselben (asset, ctx)-Argumente; der Rueckgabewert des zuletzt registrierten Plugins wird verwendet
generateChecksumsKette - die Ausgabe von Plugin N ist der Input von Plugin N+1
uploadAssetsAdditiv - jedes Plugin erhält die urspruengliche PreparedAsset[]-Liste unabhängig; alle Ergebnisse werden zusammengefuehrt

Jeder nicht abgefangene Fehler in einer Hook-Kette bricht die Pipeline ab und leitet an den onError-Hook weiter.

Vollstaendiges Beispiel: Code-Signing + Prüfsummen + S3

Abschnitt betitelt „Vollstaendiges Beispiel: Code-Signing + Prüfsummen + S3“
import { defineConfig } from "@pubm/core";
export default defineConfig({
releaseAssets: ["platforms/*/bin/mytool"],
plugins: [
// macOS-Binaries signieren
{
name: "code-sign",
hooks: {
async transformAsset(asset, ctx) {
if (asset.platform.os === "darwin") {
await exec("codesign", ["--sign", process.env.SIGNING_IDENTITY, asset.filePath]);
}
return asset;
},
},
},
// SHA256SUMS.txt anhängen
checksumsPlugin,
// Nach S3 spiegeln
s3Plugin,
],
});