From ac21fa84b73f8c1e2663c030fcb983f3def912e8 Mon Sep 17 00:00:00 2001 From: Markus Heberling Date: Thu, 26 Oct 2023 06:32:30 +0000 Subject: [PATCH] Delete whole package if last version will be deleted Fixes #33 --- src/action.ts | 20 +++++++++---- .../organization.delete.strategy.ts | 8 +++++ src/delete/strategies/user.delete.strategy.ts | 8 +++++ src/process/process.ts | 10 +++++-- src/types.ts | 2 ++ test/action.spec.ts | 30 +++++++++++++++++++ test/process/process.spec.ts | 18 +++++++++++ 7 files changed, 89 insertions(+), 7 deletions(-) diff --git a/src/action.ts b/src/action.ts index c4a3255..658acdb 100644 --- a/src/action.ts +++ b/src/action.ts @@ -39,12 +39,22 @@ export async function executeAction(input: Input, queryStrategy: QueryStrategy, await group("Deleting packages", async () => { await Promise.all( processedPackages - .flatMap((pkg) => pkg.versions.map((version) => ({ name: pkg.name, version }))) - .map(async ({ name, version }) => { - info(`Deleting version ${version.names.join(", ")} of package ${name}`) + .flatMap((pkg) => + pkg.versions.map((version) => ({ name: pkg.name, version, totalVersions: pkg.totalVersions })) + ) + .map(async ({ name, version, totalVersions }) => { + if (totalVersions > 1) { + info(`Deleting version ${version.names.join(", ")} of package ${name}`) - if (!input.dryRun) { - await deleteStrategy.deletePackageVersion(input, name, version.id) + if (!input.dryRun) { + await deleteStrategy.deletePackageVersion(input, name, version.id) + } + } else { + info(`Deleting package ${name} since ${version.names.join(", ")} is the last version`) + + if (!input.dryRun) { + await deleteStrategy.deletePackage(input, name) + } } }) ) diff --git a/src/delete/strategies/organization.delete.strategy.ts b/src/delete/strategies/organization.delete.strategy.ts index 58bea7b..475245d 100644 --- a/src/delete/strategies/organization.delete.strategy.ts +++ b/src/delete/strategies/organization.delete.strategy.ts @@ -12,4 +12,12 @@ export default class OrganizationDeleteStrategy implements DeleteStrategy { org: input.organization, }) } + + async deletePackage(input: RestInput, name: string): Promise { + await this.octokit.rest.packages.deletePackageForOrg({ + package_name: name, + package_type: input.type, + org: input.organization, + }) + } } diff --git a/src/delete/strategies/user.delete.strategy.ts b/src/delete/strategies/user.delete.strategy.ts index b3f598d..a973c4b 100644 --- a/src/delete/strategies/user.delete.strategy.ts +++ b/src/delete/strategies/user.delete.strategy.ts @@ -12,4 +12,12 @@ export default class UserDeleteStrategy implements DeleteStrategy { username: input.user, }) } + + async deletePackage(input: RestInput, name: string): Promise { + await this.octokit.rest.packages.deletePackageForUser({ + package_name: name, + package_type: input.type, + username: input.user, + }) + } } diff --git a/src/process/process.ts b/src/process/process.ts index f8719a2..a371511 100644 --- a/src/process/process.ts +++ b/src/process/process.ts @@ -12,7 +12,11 @@ type RestVersion = components["schemas"]["package-version"] export function processPackages(input: Input, packages: Package[]): Package[] { return packages - .map(({ name, versions }) => ({ name, versions: findVersionsToDelete(input, versions).slice(input.keep) })) + .map(({ name, versions, totalVersions }) => ({ + name, + versions: findVersionsToDelete(input, versions).slice(input.keep), + totalVersions, + })) .filter((it) => it.versions.length >= 1) } @@ -35,9 +39,11 @@ export function findVersionsToDelete(input: Input, versions: PackageVersion[]): } export function processResponse(name: string, response: RestResponse): Package { + const versions = response.data.map((version) => processVersion(version)).filter((it) => it.names.length >= 1) return { name, - versions: response.data.map((version) => processVersion(version)).filter((it) => it.names.length >= 1), + versions: versions, + totalVersions: versions.length, } } diff --git a/src/types.ts b/src/types.ts index 4fa278e..2e7ea3e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,6 +29,7 @@ export type RestInput = Input & { export type Package = { name: string versions: PackageVersion[] + totalVersions: number } export type PackageVersion = { @@ -42,4 +43,5 @@ export interface QueryStrategy { export interface DeleteStrategy { deletePackageVersion(input: Input, name: string, id: string): Promise + deletePackage(input: Input, name: string): Promise } diff --git a/test/action.spec.ts b/test/action.spec.ts index ab8c009..0e234e2 100644 --- a/test/action.spec.ts +++ b/test/action.spec.ts @@ -10,6 +10,15 @@ const packages = [ { id: "a", names: ["1.0.0"] }, { id: "b", names: ["docker-base-layer"] }, ], + totalVersions: 2, + }, +] + +const packages_single = [ + { + name: "test", + versions: [{ id: "a", names: ["1.0.0"] }], + totalVersions: 1, }, ] @@ -76,6 +85,27 @@ test("filters by version-pattern", async () => { expect(deleteStrategy.deletePackageVersion).toHaveBeenNthCalledWith(1, input, "test", "a") }) +test("deletes whole package if last version", async () => { + const input: Input = { + names: ["test", "test2"], + versionPattern: /^\d+\.\d+\.\d+$/, + keep: 0, + token: "token", + dryRun: false, + user: "user", + organization: "", + type: PackageType.Npm, + } + + const queryStrategy = mock({ queryPackages: () => Promise.resolve(packages_single) }) + const deleteStrategy = mock() + + await executeAction(input, queryStrategy, deleteStrategy) + + expect(deleteStrategy.deletePackage).toHaveBeenCalledTimes(1) + expect(deleteStrategy.deletePackage).toHaveBeenNthCalledWith(1, input, "test") +}) + test("Does nothing when empty packages are returned", async () => { const input: Input = { names: ["test", "test2"], diff --git a/test/process/process.spec.ts b/test/process/process.spec.ts index 9881c28..93204cc 100644 --- a/test/process/process.spec.ts +++ b/test/process/process.spec.ts @@ -22,6 +22,7 @@ test("filters correctly", () => { { id: "a", names: ["2"] }, { id: "b", names: ["1"] }, ], + totalVersions: 2, }, { name: "test2", @@ -29,6 +30,7 @@ test("filters correctly", () => { { id: "c", names: ["2"] }, { id: "d", names: ["1"] }, ], + totalVersions: 2, }, ] ) @@ -36,8 +38,10 @@ test("filters correctly", () => { expect(result).toHaveLength(2) expect(result[0].name).toEqual("test") expect(result[0].versions.map((it) => it.id)).toEqual(["a", "b"]) + expect(result[0].totalVersions).toEqual(2) expect(result[1].name).toEqual("test2") expect(result[1].versions.map((it) => it.id)).toEqual(["c", "d"]) + expect(result[1].totalVersions).toEqual(2) }) test("filters based on semver", () => { @@ -61,12 +65,14 @@ test("filters based on semver", () => { { id: "c", names: ["3.0.1-alpha01"] }, { id: "d", names: ["v3.10.2"] }, ], + totalVersions: 4, }, ] ) expect(result[0].versions).toHaveLength(3) expect(result[0].versions.map((it) => it.id)).toEqual(["a", "c", "d"]) + expect(result[0].totalVersions).toEqual(4) }) test("filters based on regex", () => { @@ -88,6 +94,7 @@ test("filters based on regex", () => { { id: "a", names: ["2-test"] }, { id: "b", names: ["1"] }, ], + totalVersions: 2, }, { name: "test2", @@ -95,6 +102,7 @@ test("filters based on regex", () => { { id: "c", names: ["2-test"] }, { id: "d", names: ["1-test"] }, ], + totalVersions: 2, }, ] ) @@ -102,8 +110,10 @@ test("filters based on regex", () => { expect(result).toHaveLength(2) expect(result[0].name).toEqual("test") expect(result[0].versions.map((it) => it.id)).toEqual(["a"]) + expect(result[0].totalVersions).toEqual(2) expect(result[1].name).toEqual("test2") expect(result[1].versions.map((it) => it.id)).toEqual(["c", "d"]) + expect(result[1].totalVersions).toEqual(2) }) test("respects keep", () => { @@ -126,6 +136,7 @@ test("respects keep", () => { { id: "b", names: ["2"] }, { id: "c", names: ["1"] }, ], + totalVersions: 3, }, { name: "test2", @@ -133,6 +144,7 @@ test("respects keep", () => { { id: "d", names: ["1"] }, { id: "e", names: ["1"] }, ], + totalVersions: 2, }, ] ) @@ -140,6 +152,7 @@ test("respects keep", () => { expect(result).toHaveLength(1) expect(result[0].name).toEqual("test") expect(result[0].versions.map((it) => it.id)).toEqual(["c"]) + expect(result[0].totalVersions).toEqual(3) }) test("filters with multiple names", () => { @@ -161,6 +174,7 @@ test("filters with multiple names", () => { { id: "a", names: ["2"] }, { id: "b", names: ["1", "1-test"] }, ], + totalVersions: 2, }, { name: "test2", @@ -168,6 +182,7 @@ test("filters with multiple names", () => { { id: "c", names: ["2"] }, { id: "d", names: ["1"] }, ], + totalVersions: 2, }, ] ) @@ -175,6 +190,7 @@ test("filters with multiple names", () => { expect(result).toHaveLength(1) expect(result[0].name).toEqual("test") expect(result[0].versions.map((it) => it.id)).toEqual(["b"]) + expect(result[0].totalVersions).toEqual(2) }) const containerTestResponse: RestEndpointMethodTypes["packages"]["getAllPackageVersionsForPackageOwnedByOrg"]["response"] = @@ -314,6 +330,7 @@ test("process rest container response", () => { names: ["1.0.0-RC1", "1.0.0-beta1"], }, ], + totalVersions: 3, }) }) @@ -340,5 +357,6 @@ test("process rest npm response", () => { names: ["1.0.0"], }, ], + totalVersions: 4, }) })