mirror of
https://github.com/awalsh128/cache-apt-pkgs-action.git
synced 2026-11-13 21:36:56 +00:00
fix: report requested and dependency package versions separately
package-version-list held every installed package and both outputs were identical. Outputs also carried arch suffixes inconsistently and followed install order. - package-version-list lists only requested packages; all-package-version-list lists every installed package. - Entries are name=version, with :arch only for packages installed for a non-native architecture (e.g. libc6:i386), so native output matches v1. - Entries are sorted with PackageName.compareTo. - Requested packages are matched by name, and by arch when both specify one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3c0664f99f
commit
17178469db
|
|
@ -50,8 +50,8 @@ There are three kinds of version labels you can use.
|
|||
### Outputs
|
||||
|
||||
- `cache-hit` - A boolean value to indicate a cache was found for the packages requested.
|
||||
- `package-version-list` - The main requested packages and versions that are installed. Represented as a comma delimited list with equals delimit on the package version (i.e. \<package1>=<version1\>,\<package2>=\<version2>,...).
|
||||
- `all-package-version-list` - All the pulled in packages and versions, including dependencies, that are installed. Represented as a comma delimited list with equals delimit on the package version (i.e. \<package1>=<version1\>,\<package2>=\<version2>,...).
|
||||
- `package-version-list` - The main requested packages and versions that are installed. Represented as a sorted comma delimited list with equals delimit on the package version (i.e. \<package1>=\<version1>,\<package2>=\<version2>,...). Packages installed for a non-native architecture include it (i.e. \<package>:\<arch>=\<version>).
|
||||
- `all-package-version-list` - All the pulled in packages and versions, including dependencies, that are installed. Represented as a sorted comma delimited list with equals delimit on the package version (i.e. \<package1>=\<version1>,\<package2>=\<version2>,...). Packages installed for a non-native architecture include it (i.e. \<package>:\<arch>=\<version>).
|
||||
|
||||
### Security Compliance
|
||||
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ outputs:
|
|||
cache-hit:
|
||||
description: "A boolean value to indicate a cache was found for the packages requested."
|
||||
package-version-list:
|
||||
description: "The main requested packages and versions that are installed. Represented as a comma delimited list with equals delimit on the package version (i.e. <package>:<version,<package>:<version>)."
|
||||
description: "The main requested packages and versions that are installed. Represented as a sorted comma delimited list with equals delimit on the package version (i.e. <package1>=<version1>,<package2>=<version2>,...). Packages installed for a non-native architecture include it (i.e. <package>:<arch>=<version>)."
|
||||
all-package-version-list:
|
||||
description: "All the pulled in packages and versions, including dependencies, that are installed. Represented as a comma delimited list with equals delimit on the package version (i.e. <package>:<version,<package>:<version>)."
|
||||
description: "All the pulled in packages and versions, including dependencies, that are installed. Represented as a sorted comma delimited list with equals delimit on the package version (i.e. <package1>=<version1>,<package2>=<version2>,...). Packages installed for a non-native architecture include it (i.e. <package>:<arch>=<version>)."
|
||||
|
||||
runs:
|
||||
using: "node24"
|
||||
|
|
|
|||
|
|
@ -45,14 +45,31 @@ export class ActionRunner {
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts manifest entries into the action output CSV format.
|
||||
* Converts package names into the action output CSV format.
|
||||
*
|
||||
* @param manifest Parsed manifest.
|
||||
* @returns Comma-delimited name=version list.
|
||||
* 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(manifest: Manifest): string {
|
||||
return manifest.entries
|
||||
.map((entry) => entry.packageName.serialize())
|
||||
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(",");
|
||||
}
|
||||
|
||||
|
|
@ -77,12 +94,14 @@ export class ActionRunner {
|
|||
);
|
||||
const cacheHit = manifest !== undefined;
|
||||
|
||||
// Needed on a cache hit too, to resolve the native arch for outputs.
|
||||
const installManager = await createPackageManager(
|
||||
true,
|
||||
this.logger,
|
||||
this.logger,
|
||||
);
|
||||
|
||||
if (!cacheHit) {
|
||||
const installManager = await createPackageManager(
|
||||
true,
|
||||
this.logger,
|
||||
this.logger,
|
||||
);
|
||||
const packageInfos = await installManager.install(packageNames.toArray());
|
||||
// Collect each package's installed files so the archives have content.
|
||||
const entries = await Promise.all(
|
||||
|
|
@ -98,10 +117,15 @@ export class ActionRunner {
|
|||
await this.cache.archiveAndSave(manifest);
|
||||
}
|
||||
|
||||
const nativeArch = await installManager.getNativeArchitecture();
|
||||
const installed = manifest!.entries.map((entry) => entry.packageName);
|
||||
return {
|
||||
cacheHit,
|
||||
packageVersionList: this.toCsv(manifest!),
|
||||
allPackageVersionList: this.toCsv(manifest!),
|
||||
packageVersionList: this.toCsv(
|
||||
installed.filter((name) => packageNames.includes(name)),
|
||||
nativeArch,
|
||||
),
|
||||
allPackageVersionList: this.toCsv(installed, nativeArch),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,6 +171,25 @@ export class ActionPackageNames {
|
|||
toArray(): readonly PackageName[] {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an installed package was one of the requested packages.
|
||||
*
|
||||
* Matches on name, and on arch only when both sides specify one. Version is
|
||||
* ignored because requests usually omit it and apt resolves the installed one.
|
||||
*
|
||||
* @param installed Package name as reported by apt after install.
|
||||
* @returns True when the package was requested.
|
||||
*/
|
||||
includes(installed: PackageName): boolean {
|
||||
return this.items.some(
|
||||
(requested) =>
|
||||
requested.name === installed.name &&
|
||||
(requested.arch === undefined ||
|
||||
installed.arch === undefined ||
|
||||
requested.arch === installed.arch),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ vi.mock("ts-apt", async (importOriginal) => {
|
|||
};
|
||||
});
|
||||
|
||||
const NATIVE_ARCH = "amd64";
|
||||
|
||||
describe("ActionRunner", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
|
|
@ -45,6 +47,9 @@ describe("ActionRunner", () => {
|
|||
const cacheKey = new CacheKey("v1", "0", process.arch, packageNames);
|
||||
const restored = new Manifest(new Date(), [], cacheKey);
|
||||
cache.loadAndRestore.mockResolvedValue(restored);
|
||||
vi.mocked(createPackageManager).mockResolvedValue({
|
||||
getNativeArchitecture: vi.fn().mockResolvedValue(NATIVE_ARCH),
|
||||
} as never);
|
||||
|
||||
const result = await runner.runAction({
|
||||
packages: "curl git",
|
||||
|
|
@ -68,6 +73,7 @@ describe("ActionRunner", () => {
|
|||
vi.mocked(createPackageManager).mockResolvedValue({
|
||||
install: vi.fn().mockResolvedValue([{ name: installedPackage }]),
|
||||
listInstalledFiles: vi.fn().mockResolvedValue([installedFile]),
|
||||
getNativeArchitecture: vi.fn().mockResolvedValue(NATIVE_ARCH),
|
||||
} as never);
|
||||
|
||||
await runner.runAction({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
runAction,
|
||||
type ActionInputs,
|
||||
|
|
@ -9,6 +9,19 @@ import { CacheKey } from "../src/cache.js";
|
|||
import { createPackageName } from "ts-apt";
|
||||
import { ActionPackageNames } from "../src/packages.js";
|
||||
|
||||
const NATIVE_ARCH = "amd64";
|
||||
const FOREIGN_ARCH = "i386";
|
||||
|
||||
vi.mock("ts-apt", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("ts-apt")>();
|
||||
return {
|
||||
...actual,
|
||||
createPackageManager: async () => ({
|
||||
getNativeArchitecture: async () => NATIVE_ARCH,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe("runAction", () => {
|
||||
const PKG_NAME = "curl";
|
||||
const PKG_VER = "1.2.3";
|
||||
|
|
@ -71,4 +84,45 @@ describe("runAction", () => {
|
|||
`${PKG_NAME}=${PKG_VER},${PKG2_NAME}=${PKG_VER},${PKG3_NAME}=${PKG_VER}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("runAction cache hit with dependencies lists requested and all packages sorted with foreign arch only", async () => {
|
||||
const inputs: ActionInputs = {
|
||||
packages: `${PKG2_NAME} ${PKG_NAME}`,
|
||||
version: PKG_VER,
|
||||
executeInstallScripts: false,
|
||||
emptyPackagesBehavior: "error",
|
||||
debug: false,
|
||||
};
|
||||
const cacheKey = new CacheKey(
|
||||
inputs.version,
|
||||
"0",
|
||||
process.arch,
|
||||
ActionPackageNames.fromInput(inputs.packages),
|
||||
);
|
||||
// Install order, with a native-arch dependency and a foreign-arch request.
|
||||
const manifest = new Manifest(
|
||||
new Date(),
|
||||
[
|
||||
new ManifestEntry(createPackageName(PKG2_NAME, PKG_VER, FOREIGN_ARCH)),
|
||||
new ManifestEntry(createPackageName(PKG3_NAME, PKG_VER, NATIVE_ARCH)),
|
||||
new ManifestEntry(createPackageName(PKG_NAME, PKG_VER, undefined)),
|
||||
],
|
||||
cacheKey,
|
||||
);
|
||||
const mockCache = { loadAndRestore: async () => manifest };
|
||||
|
||||
const outputs = await runAction(
|
||||
inputs,
|
||||
{} as never,
|
||||
mockCache as never,
|
||||
{} as never,
|
||||
);
|
||||
|
||||
expect(outputs.packageVersionList).toBe(
|
||||
`${PKG_NAME}=${PKG_VER},${PKG2_NAME}:${FOREIGN_ARCH}=${PKG_VER}`,
|
||||
);
|
||||
expect(outputs.allPackageVersionList).toBe(
|
||||
`${PKG_NAME}=${PKG_VER},${PKG2_NAME}:${FOREIGN_ARCH}=${PKG_VER},${PKG3_NAME}=${PKG_VER}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -170,5 +170,18 @@ describe("packages", () => {
|
|||
createPackageName("test-package", "1.0.0", "amd64"),
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["same name without arch returns true", "xdot", createPackageName("xdot", "1.3-1"), true],
|
||||
["requested without arch returns true", "xdot", createPackageName("xdot", "1.3-1", "amd64"), true],
|
||||
["installed without arch returns true", "xdot:amd64", createPackageName("xdot", "1.3-1"), true],
|
||||
["matching arch returns true", "xdot:amd64", createPackageName("xdot", "1.3-1", "amd64"), true],
|
||||
["different arch returns false", "xdot:i386", createPackageName("xdot", "1.3-1", "amd64"), false],
|
||||
["different name returns false", "xdot", createPackageName("rolldice", "1.16"), false],
|
||||
])("includes %s", (_case, input, installed, expected) => {
|
||||
expect(ActionPackageNames.fromInput(input).includes(installed)).toBe(
|
||||
expected,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue