Asset Pipeline Hooks
The asset pipeline runs after the build step and before GitHub Release upload. Plugins can intercept every stage through six hooks that mirror the pipeline structure.
Pipeline stages
Section titled “Pipeline stages”releaseAssets config → glob matching → ResolvedAsset[] → [resolveAssets] : filter, add, or replace the asset list → [transformAsset] : sign, strip, or split each asset (1 → N) → [compressAsset] : replace default tar.gz / zip compression → [nameAsset] : override the name template with dynamic logic → sha256 hash → [generateChecksums] : append checksum files to the asset list → GitHub Release upload (built-in) → [uploadAssets] : upload to additional targets (S3, R2, CDN, etc.) → ReleaseContext assembled → [afterRelease] : consume final asset list (brew, notify, etc.)Hooks interface
Section titled “Hooks interface”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[];}All hooks are added to PluginHooks and registered through the standard plugins array in pubm.config.ts.
Hook reference
Section titled “Hook reference”resolveAssets
Section titled “resolveAssets”Called once with the full list of glob-matched assets. Use this hook to filter assets, add programmatically discovered files, or attach metadata.
Input: ResolvedAsset[]: assets from glob matching with parsed platform info
Output: ResolvedAsset[]: the new asset list (replaces the previous list)
const filterPlugin: PubmPlugin = { name: "filter-assets", hooks: { resolveAssets(resolved, ctx) { // Only include assets for supported platforms return resolved.filter( (a) => (a.platform.os === "darwin" || a.platform.os === "linux") && a.platform.arch === "arm64", ); }, },};transformAsset
Section titled “transformAsset”Called once per asset before compression. Use this hook to sign binaries, run strip, add sidecar files to an archive, or split one input into multiple outputs.
Input: ResolvedAsset: one asset
Output: TransformedAsset | TransformedAsset[]: one or more transformed assets
TransformedAsset extends ResolvedAsset with an optional extraFiles?: string[] field. Files listed in extraFiles are bundled into the archive alongside the primary file during compression.
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; }, },};Splitting an asset into multiple outputs
Section titled “Splitting an asset into multiple outputs”Return an array to produce multiple assets from one input. Each element proceeds through the rest of the pipeline independently.
hooks: { async transformAsset(asset, ctx) { if (asset.platform.os === "windows") { // Produce both a regular binary and a debug build return [ asset, { ...asset, filePath: asset.filePath.replace(".exe", "-debug.exe") }, ]; } return asset; },},compressAsset
Section titled “compressAsset”Replaces the built-in compression logic entirely. When set, the hook is responsible for producing a CompressedAsset with the correct filePath and compressFormat.
Input: TransformedAsset
Output: CompressedAsset
Use this when you need a format that pubm does not support natively, or when you want to bundle additional files into the archive.
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, }; } // Fall back to default for other platforms return defaultCompress(asset); }, },};nameAsset
Section titled “nameAsset”Returns the final upload filename without the extension. The extension is appended from compressFormat. Use this hook when the name template system is not expressive enough.
Input: CompressedAsset
Output: string: the filename without extension
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}`; }, },};generateChecksums
Section titled “generateChecksums”Called with the complete list of prepared assets. Use this hook to append a checksum manifest file.
Input: PreparedAsset[]: all assets including their sha256 values
Output: PreparedAsset[]: original assets plus any new checksum files
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" }, }, ]; }, },};uploadAssets
Section titled “uploadAssets”Called after GitHub Release upload with the same PreparedAsset[]. Use this hook to mirror artifacts to additional targets. Each plugin’s results are collected and concatenated. All plugins receive the same input list.
Input: PreparedAsset[]
Output: UploadedAsset[]: uploaded assets with url and target fields
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", }; }), ); }, },};Multi-plugin composition
Section titled “Multi-plugin composition”When multiple plugins register the same hook, they are composed in registration order:
| Hook | Composition rule |
|---|---|
resolveAssets | Chained: output of plugin N is input of plugin N+1 |
transformAsset | Chained: output of plugin N is input of plugin N+1 |
compressAsset | Chained: output of plugin N is input of plugin N+1 |
nameAsset | Last-wins: each plugin receives the same (asset, ctx) arguments; the last registered plugin’s return value is used |
generateChecksums | Chained: output of plugin N is input of plugin N+1 |
uploadAssets | Additive: each plugin receives the original PreparedAsset[] independently; all results are concatenated |
Any uncaught error in a hook chain aborts the pipeline and routes to the onError hook.
Complete example: code signing + checksums + S3
Section titled “Complete example: code signing + checksums + S3”import { defineConfig } from "@pubm/core";
export default defineConfig({ releaseAssets: ["platforms/*/bin/mytool"],
plugins: [ // Sign macOS binaries { 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; }, }, },
// Append SHA256SUMS.txt checksumsPlugin,
// Mirror to S3 s3Plugin, ],});Related docs
Section titled “Related docs”- Release Assets: declarative config reference
- Platform Detection: OS and arch tables
- Plugins API Reference: full plugin interface