add delete-only-untagged-versions param (#94)
Add a new boolean parameter to action `delete-only-untagged-versions`. When true, only versions that do not have any tags will be deleted.
This commit is contained in:
33
README.md
33
README.md
@@ -58,6 +58,11 @@ This action deletes versions of a package from [GitHub Packages](https://github.
|
||||
# Cannot be used with `num-old-versions-to-delete` and `ignore-versions`.
|
||||
delete-only-pre-release-versions:
|
||||
|
||||
# If true it will delete only the untagged versions in case of container package.
|
||||
# Does not work for other package types and will be ignored.
|
||||
# Defaults to false.
|
||||
delete-only-untagged-versions:
|
||||
|
||||
# The token used to authenticate with GitHub Packages.
|
||||
# Defaults to github.token.
|
||||
# Required if the repo running the workflow does not have access to delete the package.
|
||||
@@ -88,6 +93,7 @@ This action deletes versions of a package from [GitHub Packages](https://github.
|
||||
- [Valid Input Combinations](#valid-input-combinations)
|
||||
- [Scenarios](#scenarios)
|
||||
- [Delete all pre-release versions except y latest pre-release package versions](#delete-all-pre-release-versions-except-y-latest-pre-release-package-versions)
|
||||
- [Delete all untagged container versions except y latest untagged versions](#delete-all-untagged-container-versions-except-y-latest-untagged-versions)
|
||||
- [Delete all except y latest versions while ignoring particular package versions](#delete-all-except-y-latest-versions-while-ignoring-particular-package-versions)
|
||||
- [Delete oldest x number of versions while ignoring particular package versions](#delete-oldest-x-number-of-versions-while-ignoring-particular-package-versions)
|
||||
- [Delete all except y latest versions of a package](#delete-all-except-y-latest-versions-of-a-package)
|
||||
@@ -95,8 +101,9 @@ This action deletes versions of a package from [GitHub Packages](https://github.
|
||||
- [Delete oldest version of a package](#delete-oldest-version-of-a-package)
|
||||
- [Delete a specific version of a package](#delete-a-specific-version-of-a-package)
|
||||
- [Delete multiple specific versions of a package](#delete-multiple-specific-versions-of-a-package)
|
||||
- [GitHub Enterprise Server](#github-enterprise-server)
|
||||
- [License](#license)
|
||||
|
||||
|
||||
|
||||
### Delete all pre-release versions except y latest pre-release package versions
|
||||
|
||||
@@ -133,6 +140,25 @@ This action deletes versions of a package from [GitHub Packages](https://github.
|
||||
|
||||
<br>
|
||||
|
||||
### Delete all untagged container versions except y latest untagged versions
|
||||
|
||||
To delete all untagged versions of a container package except y latest untagged versions, the __package-name__, __package-type__, __min-versions-to-keep__ and __delete-only-untagged-versions__ inputs are required. __package-type__ must be container for this scenario.
|
||||
|
||||
__Example__
|
||||
|
||||
Delete all untagged versions except latest 10
|
||||
|
||||
```yaml
|
||||
- uses: actions/delete-package-versions@v4
|
||||
with:
|
||||
package-name: 'test-package'
|
||||
package-type: 'container'
|
||||
min-versions-to-keep: 10
|
||||
delete-only-untagged-versions: 'true'
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### Delete all except y latest versions while ignoring particular package versions
|
||||
|
||||
To delete all except y latest versions while ignoring particular package versions, the __package-name__, __min-versions-to-keep__ and __ignore-versions__ inputs are required.
|
||||
@@ -375,6 +401,10 @@ This action deletes versions of a package from [GitHub Packages](https://github.
|
||||
token: ${{ secrets.PAT }}
|
||||
```
|
||||
|
||||
# GitHub Enterprise Server
|
||||
|
||||
This action works with GitHub Enterprise Server. It uses the `GITHUB_API_URL` environment variable to determine the GitHub Enterprise Server URL. The environment variable is automatically set in workflows. GitHub Connect should be configured on the server to allow using the action from dotcom. See - [Enabling automatic access to GitHub.com actions using GitHub Connect](https://docs.github.com/en/enterprise-server/admin/github-actions/managing-access-to-actions-from-githubcom/enabling-automatic-access-to-githubcom-actions-using-github-connect)
|
||||
|
||||
# License
|
||||
|
||||
The scripts and documentation in this project are released under the [MIT License](https://github.com/actions/delete-package-versions/blob/main/LICENSE)
|
||||
@@ -382,4 +412,3 @@ The scripts and documentation in this project are released under the [MIT Licens
|
||||
[api]: https://docs.github.com/en/rest/packages
|
||||
[token]: https://help.github.com/en/packages/publishing-and-managing-packages/about-github-packages#about-tokens
|
||||
[secret]: https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets
|
||||
|
||||
|
||||
@@ -255,6 +255,53 @@ describe('index tests -- call rest', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('finalIds test - delete only untagged versions with minVersionsToKeep', done => {
|
||||
const numVersions = 50
|
||||
const numTaggedVersions = 20
|
||||
const numUntaggedVersions = numVersions - numTaggedVersions
|
||||
|
||||
const taggedVersions = getMockedVersionsResponse(
|
||||
numTaggedVersions,
|
||||
0,
|
||||
'container',
|
||||
true
|
||||
)
|
||||
const untaggedVersions = getMockedVersionsResponse(
|
||||
numUntaggedVersions,
|
||||
numTaggedVersions,
|
||||
'container',
|
||||
false
|
||||
)
|
||||
const versions = taggedVersions.concat(untaggedVersions)
|
||||
|
||||
let apiCalled = 0
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
'https://api.github.com/users/test-owner/packages/container/test-package/versions',
|
||||
(req, res, ctx) => {
|
||||
apiCalled++
|
||||
return res(ctx.status(200), ctx.json(versions))
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
finalIds(
|
||||
getInput({
|
||||
minVersionsToKeep: 10,
|
||||
deleteUntaggedVersions: 'true',
|
||||
packageType: 'container'
|
||||
})
|
||||
).subscribe(ids => {
|
||||
expect(apiCalled).toBe(1)
|
||||
expect(ids.length).toBe(numUntaggedVersions - 10)
|
||||
for (let i = 0; i < numUntaggedVersions - 10; i++) {
|
||||
expect(ids[i]).toBe(untaggedVersions[i].id.toString())
|
||||
}
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('finalIds test - no versions deleted if API error even once', done => {
|
||||
const numVersions = RATE_LIMIT * 2
|
||||
let apiCalled = 0
|
||||
|
||||
@@ -77,6 +77,54 @@ describe('get versions tests -- mock rest', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('getOldestVersions -- success - container tagged versions', done => {
|
||||
const numVersions = 6
|
||||
const numTaggedVersions = 3
|
||||
const numUntaggedVersions = numVersions - numTaggedVersions
|
||||
|
||||
const respTagged = getMockedVersionsResponse(
|
||||
numTaggedVersions,
|
||||
0,
|
||||
'container',
|
||||
true
|
||||
)
|
||||
const respUntagged = getMockedVersionsResponse(
|
||||
numUntaggedVersions,
|
||||
numTaggedVersions,
|
||||
'container',
|
||||
false
|
||||
)
|
||||
const resp = respTagged.concat(respUntagged)
|
||||
|
||||
server.use(
|
||||
rest.get(
|
||||
'https://api.github.com/users/test-owner/packages/container/test-package/versions',
|
||||
(req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(resp))
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
getOldestVersions({numVersions, packageType: 'container'}).subscribe(
|
||||
result => {
|
||||
expect(result.versions.length).toBe(numVersions)
|
||||
for (let i = 0; i < numVersions; i++) {
|
||||
expect(result.versions[i].id).toBe(resp[i].id)
|
||||
expect(result.versions[i].version).toBe(resp[i].name)
|
||||
expect(result.versions[i].created_at).toBe(resp[i].created_at)
|
||||
if (i < numTaggedVersions) {
|
||||
expect(result.versions[i].tagged).toBe(true)
|
||||
} else {
|
||||
expect(result.versions[i].tagged).toBe(false)
|
||||
}
|
||||
}
|
||||
expect(result.paginate).toBe(true)
|
||||
expect(result.totalCount).toBe(numVersions)
|
||||
done()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('getOldestVersions -- paginate is false when fetched versions is less than page size', done => {
|
||||
const numVersions = 5
|
||||
|
||||
|
||||
@@ -2,23 +2,44 @@ import {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dis
|
||||
|
||||
type GetVersionsResponseData =
|
||||
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['response']['data']
|
||||
type PackageType =
|
||||
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['parameters']['package_type']
|
||||
|
||||
export function getMockedVersionsResponse(
|
||||
numVersions: number,
|
||||
offset = 0
|
||||
offset = 0,
|
||||
packageType = 'npm',
|
||||
tagged = false
|
||||
): GetVersionsResponseData {
|
||||
const versions: GetVersionsResponseData = []
|
||||
for (let i = 1 + offset; i <= numVersions + offset; ++i) {
|
||||
const created_at = new Date()
|
||||
created_at.setUTCFullYear(2000 + Number(i), 1, 1)
|
||||
versions.push({
|
||||
let version = {
|
||||
id: i,
|
||||
name: `${i}.0.0`,
|
||||
url: '',
|
||||
created_at: created_at.toUTCString(),
|
||||
package_html_url: '',
|
||||
updated_at: ''
|
||||
})
|
||||
updated_at: '',
|
||||
metadata: {
|
||||
package_type: packageType as PackageType
|
||||
}
|
||||
} as GetVersionsResponseData[0]
|
||||
|
||||
if (packageType === 'container' && tagged) {
|
||||
version = {
|
||||
...version,
|
||||
metadata: {
|
||||
package_type: packageType as PackageType,
|
||||
container: {
|
||||
tags: [`latest${i}`] as string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
versions.push(version)
|
||||
}
|
||||
return versions
|
||||
}
|
||||
|
||||
@@ -55,6 +55,13 @@ inputs:
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
delete-only-untagged-versions:
|
||||
description: >
|
||||
Deletes only untagged versions in case of a container package. Does not work for other package types.
|
||||
By default this is set to false
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
token:
|
||||
description: >
|
||||
Token with the necessary scopes to delete package versions.
|
||||
|
||||
22
dist/index.js
vendored
22
dist/index.js
vendored
@@ -38,6 +38,9 @@ function finalIds(input) {
|
||||
Then compute number of versions to delete (toDelete) based on the inputs.
|
||||
*/
|
||||
value = value.filter(info => !input.ignoreVersions.test(info.version));
|
||||
if (input.deleteUntaggedVersions === 'true') {
|
||||
value = value.filter(info => !info.tagged);
|
||||
}
|
||||
let toDelete = 0;
|
||||
if (input.minVersionsToKeep < 0) {
|
||||
toDelete = Math.min(value.length, Math.min(input.numOldVersionsToDelete, exports.RATE_LIMIT));
|
||||
@@ -88,7 +91,8 @@ const defaultParams = {
|
||||
minVersionsToKeep: 0,
|
||||
ignoreVersions: new RegExp(''),
|
||||
deletePreReleaseVersions: '',
|
||||
token: ''
|
||||
token: '',
|
||||
deleteUntaggedVersions: ''
|
||||
};
|
||||
class Input {
|
||||
constructor(params) {
|
||||
@@ -103,6 +107,7 @@ class Input {
|
||||
this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions;
|
||||
this.token = validatedParams.token;
|
||||
this.numDeleted = 0;
|
||||
this.deleteUntaggedVersions = validatedParams.deleteUntaggedVersions;
|
||||
}
|
||||
hasOldestVersionQueryInfo() {
|
||||
return !!(this.owner &&
|
||||
@@ -123,6 +128,9 @@ class Input {
|
||||
this.minVersionsToKeep > 0 ? this.minVersionsToKeep : 0;
|
||||
this.ignoreVersions = new RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$');
|
||||
}
|
||||
if (this.packageType.toLowerCase() !== 'container') {
|
||||
this.deleteUntaggedVersions = 'false';
|
||||
}
|
||||
if (this.minVersionsToKeep >= 0) {
|
||||
this.numOldVersionsToDelete = 0;
|
||||
}
|
||||
@@ -214,10 +222,17 @@ function getOldestVersions(owner, packageName, packageType, numVersions, page, t
|
||||
}), (0, operators_1.map)(response => {
|
||||
const resp = {
|
||||
versions: response.data.map((version) => {
|
||||
let tagged = false;
|
||||
if (package_type === 'container' &&
|
||||
version.metadata &&
|
||||
version.metadata.container) {
|
||||
tagged = version.metadata.container.tags.length > 0;
|
||||
}
|
||||
return {
|
||||
id: version.id,
|
||||
version: version.name,
|
||||
created_at: version.created_at
|
||||
created_at: version.created_at,
|
||||
tagged
|
||||
};
|
||||
}),
|
||||
page,
|
||||
@@ -43916,7 +43931,8 @@ function getActionInput() {
|
||||
minVersionsToKeep: Number((0, core_1.getInput)('min-versions-to-keep')),
|
||||
ignoreVersions: RegExp((0, core_1.getInput)('ignore-versions')),
|
||||
deletePreReleaseVersions: (0, core_1.getInput)('delete-only-pre-release-versions').toLowerCase(),
|
||||
token: (0, core_1.getInput)('token')
|
||||
token: (0, core_1.getInput)('token'),
|
||||
deleteUntaggedVersions: (0, core_1.getInput)('delete-only-untagged-versions').toLowerCase()
|
||||
});
|
||||
}
|
||||
function run() {
|
||||
|
||||
@@ -72,6 +72,11 @@ export function finalIds(input: Input): Observable<string[]> {
|
||||
Then compute number of versions to delete (toDelete) based on the inputs.
|
||||
*/
|
||||
value = value.filter(info => !input.ignoreVersions.test(info.version))
|
||||
|
||||
if (input.deleteUntaggedVersions === 'true') {
|
||||
value = value.filter(info => !info.tagged)
|
||||
}
|
||||
|
||||
let toDelete = 0
|
||||
if (input.minVersionsToKeep < 0) {
|
||||
toDelete = Math.min(
|
||||
|
||||
10
src/input.ts
10
src/input.ts
@@ -8,6 +8,7 @@ export interface InputParams {
|
||||
ignoreVersions?: RegExp
|
||||
token?: string
|
||||
deletePreReleaseVersions?: string
|
||||
deleteUntaggedVersions?: string
|
||||
}
|
||||
|
||||
const defaultParams = {
|
||||
@@ -19,7 +20,8 @@ const defaultParams = {
|
||||
minVersionsToKeep: 0,
|
||||
ignoreVersions: new RegExp(''),
|
||||
deletePreReleaseVersions: '',
|
||||
token: ''
|
||||
token: '',
|
||||
deleteUntaggedVersions: ''
|
||||
}
|
||||
|
||||
export class Input {
|
||||
@@ -33,6 +35,7 @@ export class Input {
|
||||
deletePreReleaseVersions: string
|
||||
token: string
|
||||
numDeleted: number
|
||||
deleteUntaggedVersions: string
|
||||
|
||||
constructor(params?: InputParams) {
|
||||
const validatedParams: Required<InputParams> = {...defaultParams, ...params}
|
||||
@@ -47,6 +50,7 @@ export class Input {
|
||||
this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions
|
||||
this.token = validatedParams.token
|
||||
this.numDeleted = 0
|
||||
this.deleteUntaggedVersions = validatedParams.deleteUntaggedVersions
|
||||
}
|
||||
|
||||
hasOldestVersionQueryInfo(): boolean {
|
||||
@@ -76,6 +80,10 @@ export class Input {
|
||||
this.ignoreVersions = new RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$')
|
||||
}
|
||||
|
||||
if (this.packageType.toLowerCase() !== 'container') {
|
||||
this.deleteUntaggedVersions = 'false'
|
||||
}
|
||||
|
||||
if (this.minVersionsToKeep >= 0) {
|
||||
this.numOldVersionsToDelete = 0
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ function getActionInput(): Input {
|
||||
deletePreReleaseVersions: getInput(
|
||||
'delete-only-pre-release-versions'
|
||||
).toLowerCase(),
|
||||
token: getInput('token')
|
||||
token: getInput('token'),
|
||||
deleteUntaggedVersions: getInput(
|
||||
'delete-only-untagged-versions'
|
||||
).toLowerCase()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface RestVersionInfo {
|
||||
id: number
|
||||
version: string
|
||||
created_at: string
|
||||
tagged: boolean
|
||||
}
|
||||
|
||||
export interface RestQueryInfo {
|
||||
@@ -56,10 +57,20 @@ export function getOldestVersions(
|
||||
map(response => {
|
||||
const resp = {
|
||||
versions: response.data.map((version: GetVersionsResponse[0]) => {
|
||||
let tagged = false
|
||||
if (
|
||||
package_type === 'container' &&
|
||||
version.metadata &&
|
||||
version.metadata.container
|
||||
) {
|
||||
tagged = version.metadata.container.tags.length > 0
|
||||
}
|
||||
|
||||
return {
|
||||
id: version.id,
|
||||
version: version.name,
|
||||
created_at: version.created_at
|
||||
created_at: version.created_at,
|
||||
tagged
|
||||
}
|
||||
}),
|
||||
page,
|
||||
|
||||
Reference in New Issue
Block a user