semantic-release plugin to publish a GitHub release and comment on released Pull Requests/Issues
—
File asset handling system supporting glob patterns, metadata, and upload management for GitHub releases. The asset management system provides flexible file selection with powerful globbing capabilities and customizable metadata.
Resolves glob patterns and file paths into concrete asset objects ready for upload to GitHub releases.
/**
* Resolves glob patterns for asset files
* @param context - Context object containing current working directory
* @param assets - Array of asset configurations with paths/globs
* @returns Promise resolving to array of concrete asset file paths
*/
async function globAssets(
context: { cwd: string },
assets: AssetConfig[]
): Promise<ResolvedAsset[]>;
type AssetConfig = string | {
path: string | string[];
name?: string;
label?: string;
};
interface ResolvedAsset {
path: string;
name?: string;
label?: string;
}Glob Pattern Support:
*.js, **/*.json)["dist/*.js", "docs/*.pdf"])!**/*.test.js)Usage Examples:
import globAssets from "@semantic-release/github/lib/glob-assets.js";
// Simple glob patterns
const assets1 = await globAssets(
{ cwd: "/project" },
[
"dist/*.js",
"docs/*.pdf",
"build/assets/*.css"
]
);
// Returns: [{ path: "dist/app.js" }, { path: "docs/guide.pdf" }, ...]
// Complex asset configurations
const assets2 = await globAssets(
{ cwd: "/project" },
[
{
path: ["dist/*.js", "dist/*.js.map"],
label: "JavaScript Distribution"
},
{
path: "docs/**/*.pdf",
name: "documentation-${nextRelease.version}.pdf", // Template support
label: "Documentation Bundle"
}
]
);
// Mixed string and object configurations
const assets3 = await globAssets(
{ cwd: "/project" },
[
"README.md", // Simple file
"dist/*.zip", // Glob pattern
{
path: "build/**/*.{js,css}", // Complex glob with metadata
label: "Built Assets"
}
]
);Different ways to configure assets for release uploads.
// String configuration - simple file path or glob
type StringAsset = string;
// Object configuration - path with metadata
interface ObjectAsset {
/** File path or glob pattern(s) */
path: string | string[];
/** Custom filename for uploaded asset (supports templates) */
name?: string;
/** Human-readable label shown in GitHub release UI */
label?: string;
}
// Combined asset configuration type
type AssetConfig = StringAsset | ObjectAsset;Configuration Examples:
// String configurations
const stringAssets = [
"dist/app.js", // Single file
"build/*.zip", // Glob pattern
"docs/api.pdf" // Documentation file
];
// Object configurations with metadata
const objectAssets = [
{
path: "dist/app.min.js",
name: "app-${nextRelease.version}.min.js",
label: "Minified Application Bundle"
},
{
path: ["src/**/*.ts", "!src/**/*.test.ts"],
name: "source-code.zip",
label: "TypeScript Source Code"
},
{
path: "coverage/lcov-report/**/*",
label: "Test Coverage Report"
}
];
// Mixed configurations
const mixedAssets = [
"LICENSE", // Simple file
"CHANGELOG.md", // Another simple file
{
path: "dist/*.{js,css,map}",
label: "Distribution Files"
}
];The asset processing pipeline handles glob resolution, file validation, and metadata preparation.
/**
* Asset processing pipeline stages:
* 1. Glob expansion - Convert patterns to file lists
* 2. Directory handling - Expand directories to files
* 3. File validation - Check existence and accessibility
* 4. Deduplication - Remove duplicate file paths
* 5. Metadata preparation - Apply names and labels
* 6. Template processing - Resolve template variables
*/
interface ProcessingStages {
/** Expand glob patterns using globby library */
expandGlobs(patterns: string[], options: GlobOptions): Promise<string[]>;
/** Validate files exist and are readable */
validateFiles(paths: string[]): Promise<string[]>;
/** Remove duplicate file paths */
deduplicateFiles(paths: string[]): string[];
/** Apply asset metadata and templates */
prepareMetadata(assets: AssetConfig[], files: string[]): ResolvedAsset[];
}Processing Example:
// Input configuration
const assetConfig = {
path: ["dist/**/*.js", "!dist/**/*.test.js"],
name: "${basename}-${nextRelease.version}${extname}",
label: "JavaScript Distribution"
};
// Processing stages:
// 1. Glob expansion: ["dist/app.js", "dist/utils.js", "dist/app.test.js"]
// 2. Negation filtering: ["dist/app.js", "dist/utils.js"]
// 3. File validation: Check files exist and are readable
// 4. Metadata application: Add name templates and labels
// 5. Result: [
// { path: "dist/app.js", name: "app-1.0.0.js", label: "JavaScript Distribution" },
// { path: "dist/utils.js", name: "utils-1.0.0.js", label: "JavaScript Distribution" }
// ]Asset upload handling within the publish lifecycle function.
/**
* Asset upload process during release publication:
* 1. Create draft release
* 2. Resolve asset globs
* 3. Upload assets in parallel
* 4. Publish release (if not draft mode)
*/
interface UploadProcess {
/** File stat checking and validation */
validateAssetFile(filePath: string): Promise<FileStats>;
/** MIME type detection for proper Content-Type headers */
detectMimeType(filePath: string): string;
/** Asset upload with retry logic */
uploadAsset(asset: ResolvedAsset, uploadUrl: string): Promise<AssetResponse>;
/** Parallel upload coordination */
uploadAllAssets(assets: ResolvedAsset[]): Promise<AssetResponse[]>;
}
interface FileStats {
size: number;
isFile: boolean;
isDirectory: boolean;
}
interface AssetResponse {
id: number;
name: string;
size: number;
download_count: number;
browser_download_url: string;
}Upload Error Handling:
// The upload process includes comprehensive error handling:
try {
await uploadAsset(asset, uploadUrl);
} catch (error) {
if (error.code === 'ENOENT') {
logger.error(`Asset file not found: ${asset.path}`);
// File is skipped, upload continues
} else if (error.status === 422) {
logger.error(`Asset already exists: ${asset.name}`);
// GitHub rejects duplicate asset names
} else if (error.status === 413) {
logger.error(`Asset too large: ${asset.path}`);
// GitHub has file size limits
} else {
// Other errors are retried or escalated
throw error;
}
}Asset names and labels support Lodash templates for dynamic content generation.
// Available template variables in asset configurations:
interface AssetTemplateContext {
nextRelease: {
version: string; // "1.0.0"
gitTag: string; // "v1.0.0"
name: string; // "1.0.0"
channel?: string; // Release channel
};
branch: {
name: string; // "main"
};
// File path utilities (for name templates)
basename: string; // "app.js" from "dist/app.js"
extname: string; // ".js" from "dist/app.js"
dirname: string; // "dist" from "dist/app.js"
filename: string; // "app" from "dist/app.js"
}Template Examples:
{
// Version-based naming
name: "${basename}-v${nextRelease.version}${extname}",
// "app.js" becomes "app-v1.0.0.js"
// Channel-aware naming
name: "${filename}-${nextRelease.channel || 'stable'}${extname}",
// "app.js" becomes "app-stable.js" or "app-beta.js"
// Complex naming with metadata
name: "${dirname}-${filename}-${nextRelease.version}-${branch.name}${extname}",
// "dist/app.js" becomes "dist-app-1.0.0-main.js"
// Dynamic labels
label: "${basename} (${nextRelease.version}${nextRelease.channel ? ` - ${nextRelease.channel}` : ''})"
// "App Bundle (1.0.0)" or "App Bundle (1.0.0 - beta)"
}Complex asset management patterns for sophisticated release workflows.
Conditional Assets:
// Assets based on build configuration
const assets = [
// Always include core files
"LICENSE",
"README.md",
// Conditional inclusion based on environment
...(process.env.NODE_ENV === 'production' ? [
"dist/app.min.js",
"dist/app.min.css"
] : [
"dist/app.js",
"dist/app.css"
]),
// Platform-specific assets
{
path: "build/darwin/**/*",
label: "macOS Distribution"
},
{
path: "build/win32/**/*",
label: "Windows Distribution"
}
];Multi-Architecture Builds:
const architectures = ['x64', 'arm64', 'x86'];
const assets = architectures.flatMap(arch => [
{
path: `dist/${arch}/app`,
name: `app-${arch}-${nextRelease.version}`,
label: `Application (${arch.toUpperCase()})`
},
{
path: `dist/${arch}/app.exe`,
name: `app-${arch}-${nextRelease.version}.exe`,
label: `Application (${arch.toUpperCase()} Windows)`
}
]);Documentation Bundles:
const assets = [
// Individual documentation files
{
path: "docs/**/*.md",
label: "Individual Documentation Files"
},
// Generated documentation bundle
{
path: "docs-build/api-docs.zip",
name: "api-documentation-${nextRelease.version}.zip",
label: "Complete API Documentation"
},
// Coverage reports
{
path: "coverage/lcov.info",
name: "test-coverage-${nextRelease.version}.lcov",
label: "Test Coverage Data"
}
];Asset management includes several performance optimizations:
Install with Tessl CLI
npx tessl i tessl/npm-semantic-release--github