mirror of
https://github.com/awalsh128/cache-apt-pkgs-action.git
synced 2026-11-14 05:46:50 +00:00
* fix: find install scripts for package names with regex metacharacters findInstallScript built a regex from the raw package name, so names like g++ or libstdc++6 threw "Nothing to repeat" on cache save and on restore with install scripts, and a '.' in a name could match another package's script. Compare file names as plain strings instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf: speed up cache restores - Upload the diagnostic artifact only when debug is on or the action fails. The upload took ~1.4s of a ~3.7s restore in CI. - Extract all package archives in one sudo shell when install scripts are off, instead of a sudo and tar process per package. Per-package extraction stays when scripts run, to keep install ordering (#196). - Skip the apt-fast availability probe on a cache hit, where nothing is installed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: document Manifest and ManifestEntry public members Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
151 lines
4.6 KiB
TypeScript
151 lines
4.6 KiB
TypeScript
import {
|
|
createPackageManager,
|
|
type CommandRunner,
|
|
type PackageName,
|
|
} from "ts-apt";
|
|
import { Cache, CacheKey } from "./cache.js";
|
|
import { Manifest, ManifestEntry } from "./manifest.js";
|
|
import { ActionPackageNames, buildFileList } from "./packages.js";
|
|
import winston from "winston";
|
|
|
|
type EmptyPackageBehavior = "error" | "warn" | "ignore";
|
|
|
|
const FORCE_UPDATE_INCREMENT = "0";
|
|
|
|
/** Inputs accepted by the GitHub Action runtime. */
|
|
export interface ActionInputs {
|
|
readonly packages: string;
|
|
readonly version: string;
|
|
readonly executeInstallScripts: boolean;
|
|
readonly emptyPackagesBehavior: EmptyPackageBehavior;
|
|
readonly debug: boolean;
|
|
}
|
|
|
|
/** Outputs emitted by the GitHub Action runtime. */
|
|
export interface ActionOutputs {
|
|
readonly cacheHit: boolean;
|
|
readonly packageVersionList: string;
|
|
readonly allPackageVersionList: string;
|
|
}
|
|
|
|
/** Orchestrates package normalization, cache restore/save, install, and outputs. */
|
|
export class ActionRunner {
|
|
private readonly cache: Cache;
|
|
private readonly commandRunner: CommandRunner;
|
|
private readonly logger: winston.Logger;
|
|
|
|
constructor(
|
|
cache: Cache,
|
|
commandRunner: CommandRunner,
|
|
logger: winston.Logger,
|
|
) {
|
|
this.cache = cache;
|
|
this.commandRunner = commandRunner;
|
|
this.logger = logger;
|
|
}
|
|
|
|
/**
|
|
* Converts package names into the action output CSV format.
|
|
*
|
|
* Arch is shown only for foreign-arch packages (e.g. `libc6:i386=2.39`), so
|
|
* native output matches the v1 format while multi-arch installs stay
|
|
* unambiguous. Entries are sorted with `PackageName.compareTo` so the output
|
|
* is stable across install order.
|
|
*
|
|
* @param packageNames Installed package names.
|
|
* @param nativeArch Runner architecture as reported by dpkg (e.g. amd64).
|
|
* @returns Comma-delimited name[:arch]=version list.
|
|
*/
|
|
private toCsv(
|
|
packageNames: readonly PackageName[],
|
|
nativeArch: string,
|
|
): string {
|
|
return [...packageNames]
|
|
.sort((a, b) => a.compareTo(b))
|
|
.map((name) => {
|
|
const foreignArch =
|
|
name.arch !== undefined && name.arch !== nativeArch
|
|
? `:${name.arch}`
|
|
: "";
|
|
const version = name.version === undefined ? "" : `=${name.version}`;
|
|
return `${name.name}${foreignArch}${version}`;
|
|
})
|
|
.join(",");
|
|
}
|
|
|
|
/**
|
|
* Executes the end-to-end action flow and returns action outputs.
|
|
*
|
|
* @param inputs Validated action inputs.
|
|
* @returns Action outputs consumed by the workflow runtime.
|
|
*/
|
|
async runAction(inputs: ActionInputs): Promise<ActionOutputs> {
|
|
const packageNames = ActionPackageNames.fromInput(inputs.packages);
|
|
const cacheKey = new CacheKey(
|
|
inputs.version,
|
|
FORCE_UPDATE_INCREMENT,
|
|
process.arch,
|
|
packageNames,
|
|
);
|
|
|
|
let manifest = await this.cache.loadAndRestore(
|
|
cacheKey,
|
|
inputs.executeInstallScripts,
|
|
);
|
|
const cacheHit = manifest !== undefined;
|
|
|
|
// Needed on a cache hit too, to resolve the native arch for outputs.
|
|
// apt-fast only speeds up installs, so skip probing for it on a hit.
|
|
const installManager = await createPackageManager(
|
|
!cacheHit,
|
|
this.logger,
|
|
this.logger,
|
|
);
|
|
|
|
if (!cacheHit) {
|
|
const packageInfos = await installManager.install(packageNames.toArray());
|
|
// Collect each package's installed files so the archives have content.
|
|
const entries = await Promise.all(
|
|
packageInfos.map(
|
|
async (info) =>
|
|
new ManifestEntry(
|
|
info.name,
|
|
await buildFileList(info.name, installManager),
|
|
),
|
|
),
|
|
);
|
|
manifest = new Manifest(new Date(), entries, cacheKey);
|
|
await this.cache.archiveAndSave(manifest);
|
|
}
|
|
|
|
const nativeArch = await installManager.getNativeArchitecture();
|
|
const installed = manifest!.entries.map((entry) => entry.packageName);
|
|
return {
|
|
cacheHit,
|
|
packageVersionList: this.toCsv(
|
|
installed.filter((name) => packageNames.includes(name)),
|
|
nativeArch,
|
|
),
|
|
allPackageVersionList: this.toCsv(installed, nativeArch),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Public entrypoint used by src/index.ts and tests.
|
|
*
|
|
* @param inputs Validated action inputs.
|
|
* @param commandRunner Command runner used by package operations.
|
|
* @param logger Logger instance used for command and restore diagnostics.
|
|
* @returns Action outputs consumed by the workflow runtime.
|
|
*/
|
|
export async function runAction(
|
|
inputs: ActionInputs,
|
|
commandRunner: CommandRunner,
|
|
cache: Cache,
|
|
logger: winston.Logger,
|
|
): Promise<ActionOutputs> {
|
|
const actionRunner = new ActionRunner(cache, commandRunner, logger);
|
|
return await actionRunner.runAction(inputs);
|
|
}
|