mirror of
https://github.com/awalsh128/cache-apt-pkgs-action.git
synced 2026-07-07 09:34:38 +00:00
docs: add examples and field docs in action module
This commit is contained in:
parent
effb2413be
commit
442edf5a7a
256
src/action.ts
256
src/action.ts
|
|
@ -15,40 +15,108 @@ import { readManifestAsCsv, writeManifest } from "./manifest.js";
|
|||
import * as tar from "tar";
|
||||
import winston from "winston";
|
||||
|
||||
/**
|
||||
* Tar module contract used by {@link ActionRunner}.
|
||||
*
|
||||
* @example
|
||||
* const tarModule = await import("tar");
|
||||
*/
|
||||
type TarModule = typeof import("tar");
|
||||
|
||||
/**
|
||||
* Strategy for handling empty package inputs.
|
||||
*
|
||||
* @example
|
||||
* const behavior: EmptyPackageBehavior = "warn";
|
||||
*/
|
||||
type EmptyPackageBehavior = "error" | "warn" | "ignore";
|
||||
|
||||
const FORCE_UPDATE_INCREMENT = "4";
|
||||
const CACHE_DIRNAME = "cache-apt-pkgs";
|
||||
const CACHE_PREFIX = "cache-apt-pkgs_";
|
||||
|
||||
/**
|
||||
* Inputs accepted by the GitHub Action runtime.
|
||||
*
|
||||
* @example
|
||||
* const inputs: ActionInputs = {
|
||||
* packages: "curl git",
|
||||
* version: "v1",
|
||||
* executeInstallScripts: false,
|
||||
* emptyPackagesBehavior: "error",
|
||||
* debug: false,
|
||||
* };
|
||||
*/
|
||||
export interface ActionInputs {
|
||||
/** Raw package input from action YAML. */
|
||||
readonly packages: string;
|
||||
/** User-controlled cache salt/version. */
|
||||
readonly version: string;
|
||||
/** Whether preinst/postinst scripts are executed on restore. */
|
||||
readonly executeInstallScripts: boolean;
|
||||
/** Behavior when normalized package input is empty. */
|
||||
readonly emptyPackagesBehavior: EmptyPackageBehavior;
|
||||
/** Enables verbose logging when true. */
|
||||
readonly debug: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs emitted by the GitHub Action runtime.
|
||||
*
|
||||
* @example
|
||||
* const outputs: ActionOutputs = {
|
||||
* cacheHit: true,
|
||||
* packageVersionList: "curl=8.5.0",
|
||||
* allPackageVersionList: "curl=8.5.0,libcurl4=8.5.0",
|
||||
* };
|
||||
*/
|
||||
export interface ActionOutputs {
|
||||
/** True when cache restore key exactly matches requested key. */
|
||||
readonly cacheHit: boolean;
|
||||
/** CSV list of requested package versions. */
|
||||
readonly packageVersionList: string;
|
||||
/** CSV list of all installed package versions including dependencies. */
|
||||
readonly allPackageVersionList: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializable package specifier understood by ts-apt.
|
||||
*
|
||||
* @example
|
||||
* const p = new ActionPackageName("curl", "8.5.0");
|
||||
* const value = p.serialize(); // curl=8.5.0
|
||||
*/
|
||||
export class ActionPackageName implements PackageName {
|
||||
constructor(
|
||||
/** Package name, for example "curl". */
|
||||
readonly name: string,
|
||||
/** Optional version pin, for example "8.5.0". */
|
||||
readonly version?: string,
|
||||
/** Optional distro qualifier used by ts-apt types. */
|
||||
readonly distro?: string,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Serializes the package descriptor for ts-apt APIs.
|
||||
*
|
||||
* @returns Serialized package specifier, including version when provided.
|
||||
*/
|
||||
serialize(): string {
|
||||
return this.version ? `${this.name}=${this.version}` : this.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a user package specifier into a typed package descriptor.
|
||||
*
|
||||
* @param packageSpecifier Specifier in name or name=version format.
|
||||
* @returns Typed package descriptor.
|
||||
* @throws Error when package name is empty.
|
||||
*
|
||||
* @example
|
||||
* const pkg = toPackageName("curl=8.5.0");
|
||||
* const value = pkg.serialize(); // curl=8.5.0
|
||||
*/
|
||||
function toPackageName(packageSpecifier: string): ActionPackageName {
|
||||
const [name, version] = packageSpecifier.split("=");
|
||||
if (!name) {
|
||||
|
|
@ -58,6 +126,17 @@ function toPackageName(packageSpecifier: string): ActionPackageName {
|
|||
return new ActionPackageName(name, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a strict boolean string used by Action inputs.
|
||||
*
|
||||
* @param value Raw input value.
|
||||
* @param fieldName Input field name used in validation errors.
|
||||
* @returns Parsed boolean value.
|
||||
* @throws Error when value is not "true" or "false".
|
||||
*
|
||||
* @example
|
||||
* const debug = parseBoolean("true", "debug"); // true
|
||||
*/
|
||||
export function parseBoolean(value: string, fieldName: string): boolean {
|
||||
if (value === "true") {
|
||||
return true;
|
||||
|
|
@ -71,6 +150,16 @@ export function parseBoolean(value: string, fieldName: string): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes package input by splitting on comma, backslash, and whitespace.
|
||||
*
|
||||
* @param inputPackages Raw package input from action configuration.
|
||||
* @returns Sorted list of normalized package specifiers.
|
||||
*
|
||||
* @example
|
||||
* const packages = normalizeInputPackages("git, curl\\vim");
|
||||
* // ["curl", "git", "vim"]
|
||||
*/
|
||||
export function normalizeInputPackages(inputPackages: string): string[] {
|
||||
return inputPackages
|
||||
.replace(/[,\\]/g, " ")
|
||||
|
|
@ -82,9 +171,19 @@ export function normalizeInputPackages(inputPackages: string): string[] {
|
|||
.sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Orchestrates package normalization, cache restore/save, install, and outputs.
|
||||
*
|
||||
* @example
|
||||
* const runner = new ActionRunner(commandRunner, tar, logger);
|
||||
* const outputs = await runner.runAction(inputs);
|
||||
*/
|
||||
export class ActionRunner {
|
||||
/** Shell command runner used for architecture and script execution. */
|
||||
private readonly commandRunner: CommandRunner;
|
||||
/** Tar adapter used for archive create/extract operations. */
|
||||
private readonly tar: TarModule;
|
||||
/** Logger used for restore/install script tracing. */
|
||||
private readonly logger: winston.Logger;
|
||||
|
||||
constructor(
|
||||
|
|
@ -97,14 +196,44 @@ export class ActionRunner {
|
|||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance wrapper over {@link parseBoolean} for easier unit testing.
|
||||
*
|
||||
* @param value Raw input value.
|
||||
* @param fieldName Input field name used in validation errors.
|
||||
* @returns Parsed boolean value.
|
||||
*
|
||||
* @example
|
||||
* const enabled = runner.parseBoolean("false", "debug");
|
||||
*/
|
||||
parseBoolean(value: string, fieldName: string): boolean {
|
||||
return parseBoolean(value, fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instance wrapper over {@link normalizeInputPackages}.
|
||||
*
|
||||
* @param inputPackages Raw package input from action configuration.
|
||||
* @returns Sorted list of normalized package specifiers.
|
||||
*
|
||||
* @example
|
||||
* const normalized = runner.normalizeInputPackages("curl git");
|
||||
*/
|
||||
normalizeInputPackages(inputPackages: string): string[] {
|
||||
return normalizeInputPackages(inputPackages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a concrete version for an unpinned package name.
|
||||
*
|
||||
* @param packageManager ts-apt package manager instance used for metadata lookup.
|
||||
* @param packageName Package name without a version pin.
|
||||
* @returns Resolved package version.
|
||||
* @throws Error when no version can be resolved.
|
||||
*
|
||||
* @example
|
||||
* const version = await runner.resolvePackageVersion(manager, "curl");
|
||||
*/
|
||||
async resolvePackageVersion(
|
||||
packageManager: PackageManager,
|
||||
packageName: string,
|
||||
|
|
@ -122,6 +251,16 @@ export class ActionRunner {
|
|||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures each package has a pinned version to make cache behavior deterministic.
|
||||
*
|
||||
* @param packageManager ts-apt package manager instance used for version resolution.
|
||||
* @param inputPackages Raw package input string.
|
||||
* @returns Sorted list of package specifiers in name=version form.
|
||||
*
|
||||
* @example
|
||||
* const packages = await runner.normalizePackagesWithVersions(manager, "curl git=2.39");
|
||||
*/
|
||||
async normalizePackagesWithVersions(
|
||||
packageManager: PackageManager,
|
||||
inputPackages: string,
|
||||
|
|
@ -140,6 +279,17 @@ export class ActionRunner {
|
|||
return packages.sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles behavior when package input resolves to an empty list.
|
||||
*
|
||||
* @param behavior Empty-package handling strategy.
|
||||
* @param packages Normalized package list.
|
||||
* @returns Nothing.
|
||||
* @throws Error when behavior is "error" and packages is empty.
|
||||
*
|
||||
* @example
|
||||
* runner.validateEmptyPackages("warn", []);
|
||||
*/
|
||||
validateEmptyPackages(
|
||||
behavior: EmptyPackageBehavior,
|
||||
packages: string[],
|
||||
|
|
@ -160,10 +310,28 @@ export class ActionRunner {
|
|||
throw new Error("Packages argument is empty.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local cache root used by this action.
|
||||
*
|
||||
* @returns Absolute cache directory path.
|
||||
*
|
||||
* @example
|
||||
* const cacheRoot = runner.getCacheRoot();
|
||||
*/
|
||||
getCacheRoot(): string {
|
||||
return path.join(os.homedir(), CACHE_DIRNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a stable cache key from packages, user version, force bump, and arch.
|
||||
*
|
||||
* @param normalizedPackages Sorted package specifiers.
|
||||
* @param version User-provided cache version salt.
|
||||
* @returns Cache key with action-specific prefix.
|
||||
*
|
||||
* @example
|
||||
* const key = await runner.getCacheKey(["curl=8.5.0"], "v1");
|
||||
*/
|
||||
async getCacheKey(
|
||||
normalizedPackages: string[],
|
||||
version: string,
|
||||
|
|
@ -179,6 +347,17 @@ export class ActionRunner {
|
|||
return `${CACHE_PREFIX}${hash}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds dpkg lifecycle scripts for a package when present.
|
||||
*
|
||||
* @param packageName Package name.
|
||||
* @param extension Script extension to resolve.
|
||||
* @param root Root filesystem path used to resolve dpkg metadata.
|
||||
* @returns Absolute script path when found.
|
||||
*
|
||||
* @example
|
||||
* const preinst = runner.findInstallScript("curl", "preinst", "/");
|
||||
*/
|
||||
findInstallScript(
|
||||
packageName: string,
|
||||
extension: "preinst" | "postinst",
|
||||
|
|
@ -202,10 +381,29 @@ export class ActionRunner {
|
|||
return path.join(scriptsDir, candidate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts absolute paths to tar-relative paths.
|
||||
*
|
||||
* @param filePath Absolute or relative file path.
|
||||
* @returns Path relative to tar root.
|
||||
*
|
||||
* @example
|
||||
* const relative = runner.tarRelativePath("/usr/bin/curl"); // usr/bin/curl
|
||||
*/
|
||||
tarRelativePath(filePath: string): string {
|
||||
return filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects installed file and lifecycle script paths for archive creation.
|
||||
*
|
||||
* @param packageManager ts-apt package manager instance.
|
||||
* @param packageName Package name.
|
||||
* @returns Sorted unique file list suitable for tar archiving.
|
||||
*
|
||||
* @example
|
||||
* const files = await runner.buildFileListForPackage(manager, "curl");
|
||||
*/
|
||||
async buildFileListForPackage(
|
||||
packageManager: PackageManager,
|
||||
packageName: string,
|
||||
|
|
@ -236,6 +434,15 @@ export class ActionRunner {
|
|||
return Array.from(new Set(files)).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates apt lists only when the local lists directory appears stale.
|
||||
*
|
||||
* @param packageManager ts-apt package manager instance.
|
||||
* @returns Nothing.
|
||||
*
|
||||
* @example
|
||||
* await runner.updateAptLists(manager);
|
||||
*/
|
||||
async updateAptLists(packageManager: PackageManager): Promise<void> {
|
||||
if (isAptListsFresh()) {
|
||||
return;
|
||||
|
|
@ -244,10 +451,30 @@ export class ActionRunner {
|
|||
await packageManager.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a versioned specifier like "pkg=1.2" into "pkg".
|
||||
*
|
||||
* @param packageSpecifier Versioned or unversioned package specifier.
|
||||
* @returns Package name only.
|
||||
*
|
||||
* @example
|
||||
* const name = runner.packageSpecifierToName("curl=8.5.0"); // curl
|
||||
*/
|
||||
packageSpecifierToName(packageSpecifier: string): string {
|
||||
return packageSpecifier.split("=")[0] ?? packageSpecifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs packages, archives installed files, and writes manifest files.
|
||||
*
|
||||
* @param cacheDir Cache directory path.
|
||||
* @param packages Package names to install.
|
||||
* @param packageManager ts-apt package manager instance configured for install.
|
||||
* @returns Nothing.
|
||||
*
|
||||
* @example
|
||||
* await runner.installAndCachePackages("/tmp/cache", ["curl"], installManager);
|
||||
*/
|
||||
async installAndCachePackages(
|
||||
cacheDir: string,
|
||||
packages: string[],
|
||||
|
|
@ -296,6 +523,16 @@ export class ActionRunner {
|
|||
writeManifest(path.join(cacheDir, "manifest_all.log"), manifestAll);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores archived files and optionally executes package install scripts.
|
||||
*
|
||||
* @param cacheDir Cache directory path.
|
||||
* @param executeInstallScripts Whether to execute preinst/postinst scripts.
|
||||
* @returns Nothing.
|
||||
*
|
||||
* @example
|
||||
* await runner.restorePackages("/tmp/cache", false);
|
||||
*/
|
||||
async restorePackages(
|
||||
cacheDir: string,
|
||||
executeInstallScripts: boolean,
|
||||
|
|
@ -341,6 +578,15 @@ export class ActionRunner {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the end-to-end action flow and returns action outputs.
|
||||
*
|
||||
* @param inputs Validated action inputs.
|
||||
* @returns Action outputs consumed by the workflow runtime.
|
||||
*
|
||||
* @example
|
||||
* const outputs = await runner.runAction(inputs);
|
||||
*/
|
||||
async runAction(inputs: ActionInputs): Promise<ActionOutputs> {
|
||||
if (/\s/.test(inputs.version)) {
|
||||
throw new Error(
|
||||
|
|
@ -408,6 +654,16 @@ export class ActionRunner {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public entrypoint used by src/index.ts and tests.
|
||||
*
|
||||
* @param inputs Validated action inputs.
|
||||
* @param logger Logger instance used for command and restore diagnostics.
|
||||
* @returns Action outputs consumed by the workflow runtime.
|
||||
*
|
||||
* @example
|
||||
* const outputs = await runAction(inputs, logger);
|
||||
*/
|
||||
export async function runAction(
|
||||
inputs: ActionInputs,
|
||||
logger: winston.Logger,
|
||||
|
|
|
|||
Loading…
Reference in a new issue