63 Commits

Author SHA1 Message Date
Anupam
0d39a63126 Add constraint on delete-only-untagged-versions 2023-03-09 15:37:07 +05:30
Anupam
8c3a64de32 Improve delete-only-untagged-versions flag and minor bug fix (#97)
Improve `delete-only-untagged-versions` flag and minor bug fix
2023-03-09 15:31:35 +05:30
Anupam
564c9d0297 Not currently supported in GHES (#95) 2023-03-06 13:11:48 +05:30
Anupam
48cd5a6793 Nit: Change dotcom to GitHub.com in README 2023-03-03 17:02:20 +05:30
Anupam
029d95066b 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.
2023-03-03 16:42:34 +05:30
Anupam
b9ed39f728 Configure baseUrl in octokit initialisation (#91)
Make the action work for GHES by passing `GITHUB_API_URL` environment variable to octokit initialisation.
2023-03-02 19:56:14 +05:30
Nishtha Gupta
ba4d22f251 Merge pull request #86 from actions/nishthaGupta-patch-1
Update version in readme
2023-01-05 17:20:20 +05:30
Nishtha Gupta
22f216e584 Update version in readme 2023-01-05 17:18:58 +05:30
Nishtha Gupta
9a08425079 Merge pull request #85 from actions/dependabot/npm_and_yarn/json5-1.0.2
Bump json5 from 1.0.1 to 1.0.2
2023-01-05 16:25:51 +05:30
dependabot[bot]
ac5977570e Bump json5 from 1.0.1 to 1.0.2
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 10:40:51 +00:00
Anupam
fe2a95a0cb Merge pull request #83 from actions/s-anupam-rest-api
Add support for deleting packages on new GitHub Packages architecture
2023-01-05 16:10:25 +05:30
Anupam
46653ba3cb restrict to RATE_LIMIT with package-version-ids 2023-01-05 10:25:21 +00:00
Anupam
79269eb4bf Update README 2023-01-05 10:08:06 +00:00
Nishtha Gupta
4833470995 Update README.md 2023-01-05 14:05:04 +05:30
Anupam
2de4236b17 Address review coments 2023-01-03 16:35:40 +00:00
Anupam
23073eb4a2 add more UTs, delete ununsed files 2022-12-30 20:03:11 +00:00
Anupam
faf614822d Update test.yml 2022-12-30 19:35:17 +05:30
Anupam
bde81b096d Add packages permission to workflow 2022-12-30 19:30:08 +05:30
Anupam
f1783122af Update test.yml 2022-12-30 19:15:21 +05:30
Nishtha Gupta
404c7849e4 Update rest.dep.yml 2022-12-30 18:49:00 +05:30
Anupam
0b99e5f60d Fix prettier errors 2022-12-30 13:10:17 +00:00
Nishtha Gupta
40030c0eb0 Add mandatory package-type param 2022-12-30 18:36:05 +05:30
Anupam
5e7113f543 update dev dependencies 2022-12-30 12:55:09 +00:00
Anupam
90c8dc6d0b better UT for deletePackageVersions 2022-12-30 08:13:23 +00:00
Anupam
98c1e7eab7 add api UTs 2022-12-30 05:12:10 +00:00
Nishtha Gupta
eaa1f7c20f Update action.yml 2022-12-29 17:43:25 +05:30
Nishtha Gupta
ce1d329288 Update readme: 2022-12-29 15:59:15 +05:30
Nishtha Gupta
9dbafb83da Remove repo param support & logs 2022-12-29 15:51:31 +05:30
Nishtha Gupta
64305dab94 Optimisation 2022-12-29 15:35:31 +05:30
Nishtha Gupta
e95cd87648 Fetch all versions at once 2022-12-29 14:43:48 +05:30
Anupam
7f4037254e add some logs 2022-12-28 18:32:12 +00:00
Anupam
e521d9e753 get versions using rest 2022-12-28 14:43:50 +00:00
Anupam
cc7196aa1d try to fix npm run pack? 2022-12-28 14:39:47 +00:00
Nishtha Gupta
6ce7577c5e Adding logs 2022-12-28 16:56:22 +05:30
Nishtha Gupta
92bd8a3cd0 First commit with delete version API 2022-12-28 16:33:42 +05:30
Anupam
280935cb1a add package-type param to action.yml 2022-12-27 15:11:20 +00:00
Anupam
272ad859dc add package type param 2022-12-27 14:35:56 +00:00
Shyam Grover
c5e64443ea Merge pull request #79 from actions/dependabot/npm_and_yarn/actions/core-1.9.1
Bump @actions/core from 1.6.0 to 1.9.1
2022-11-01 11:53:52 +05:30
Shyam Grover
9b1e76ea45 Update core.dep.yml 2022-11-01 11:51:12 +05:30
Shyam Grover
37b7aca7eb Update core.dep.yml 2022-11-01 11:50:38 +05:30
Shyam Grover
f87b4a0b81 Update README.md 2022-11-01 11:48:29 +05:30
Shyam Grover
6961202f36 Merge pull request #70 from actions/dependabot/npm_and_yarn/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6
2022-11-01 11:44:17 +05:30
Naveen Desu
4eab40be13 Merge pull request #82 from gaelgoth/fix_readme_typo
fix typo in readme
2022-09-15 12:02:36 +05:30
gael.gothuey
8701e46f41 fix typo in readme 2022-09-14 22:07:47 +02:00
dependabot[bot]
565ec2459a Bump @actions/core from 1.6.0 to 1.9.1
Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.6.0 to 1.9.1.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

---
updated-dependencies:
- dependency-name: "@actions/core"
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-18 19:22:50 +00:00
Tina Heidinger
ec4518c513 Merge pull request #75 from actions/74-add-support-for-deletion-of-container-and-npm-packages
Indicate missing support for GHCR and soon also npm in readme
2022-07-01 11:37:47 +02:00
tinaheidinger
33300d7916 Indicate missing support for GHCR and soon also npm in readme 2022-07-01 11:34:51 +02:00
dependabot[bot]
e048663cdf Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-26 06:17:25 +00:00
Namrata Jha
0e86ee6891 Update README.md 2022-03-02 13:36:45 +05:30
Namrata Jha
a31a43afa6 Merge pull request #69 from actions/Upgrade-to-node16
Upgrade to node16
2022-03-02 13:28:44 +05:30
Namrata Jha
56849b848a Upgrade to node16 2022-03-02 07:40:19 +00:00
Namrata Jha
000223ef3e Merge pull request #68 from actions/v2-patch-release
change node version for v2 patch release
2022-03-02 12:32:41 +05:30
Namrata Jha
df897cea27 change node version for v2 patch release 2022-03-02 06:11:51 +00:00
Namrata Jha
b77daeff53 Merge pull request #67 from ralfstuckert/main
#66 Type mismatch on variable $packageVersionId and argument packageV…
2022-03-02 11:26:17 +05:30
Ralf Stuckert
7515f7a4f9 #66 Type mismatch on variable $packageVersionId and argument packageVersionId (String! / ID!) 2022-03-01 07:44:01 +01:00
Namrata Jha
3e14cd73d4 Merge pull request #62 from actions/thboop/updatetonode16
Update default runtime to node16
2022-02-16 14:45:08 +05:30
Namrata Jha
5e4787da1c Merge pull request #64 from actions/fix-dependabot-alerts
Fix dependabot alerts
2022-02-16 14:43:28 +05:30
Namrata Jha
355299fa7d fix licensed 2022-02-15 15:21:28 +00:00
Namrata Jha
5263253279 upgrade eslint and fix breaking changes 2022-02-15 13:52:11 +00:00
Namrata Jha
d6bf5f6a97 update node-fetch and ws 2022-02-15 07:10:56 +00:00
Namrata Jha
4afcc8f713 Merge pull request #63 from actions/dependabot/npm_and_yarn/ajv-6.12.6
Bump ajv from 6.10.2 to 6.12.6
2022-02-15 12:23:29 +05:30
dependabot[bot]
1472d90599 Bump ajv from 6.10.2 to 6.12.6
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.10.2 to 6.12.6.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.10.2...v6.12.6)

---
updated-dependencies:
- dependency-name: ajv
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-13 02:52:00 +00:00
Thomas Boop
09f7b84634 Update default runtime to node16
Update default runtime to node16
2022-02-07 13:58:41 -05:00
22 changed files with 24399 additions and 16641 deletions

View File

@@ -1,6 +1,6 @@
{
"plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/es6"],
"extends": ["plugin:github/recommended","plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
@@ -17,13 +17,29 @@
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-ignore": "error",
"@typescript-eslint/ban-ts-comment": "error",
"camelcase": "off",
"@typescript-eslint/camelcase": "error",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "class",
"format": ["PascalCase"]
},
{
"selector": "interface",
"format": ["PascalCase"]
},
{
"selector": "typeProperty",
"format": [],
"custom": {
"regex": "^[A-Z][A-Za-z]*$",
"match": false
}
}
],
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
@@ -33,7 +49,7 @@
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-object-literal-type-assertion": "error",
"@typescript-eslint/consistent-type-assertions": "error",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
@@ -41,7 +57,7 @@
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-interface": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
@@ -50,7 +66,10 @@
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error",
"no-console": "off"
"no-console": "off",
"import/no-unresolved": "off",
"sort-imports": "off",
"filenames/match-regex": "off"
},
"env": {
"node": true,

View File

@@ -24,10 +24,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set Node.js 12.x
- name: Set Node.js 16.x
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: 16.x
- name: Install dependencies
run: npm ci

View File

@@ -7,6 +7,9 @@ on: # rebuild any PRs and main branch changes
- main
- 'releases/*'
permissions:
packages: write
jobs:
package: # make sure build/ci work properly
@@ -15,6 +18,9 @@ jobs:
steps:
- uses: actions/checkout@v2
name: Checkout Delete Package Versions Repo
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm install
name: NPM Install
- run: npm run pack
@@ -26,9 +32,12 @@ jobs:
steps:
- uses: actions/checkout@v2
name: Checkout Delete Package Versions Repo
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: ./
name: Smoke Test Delete Package Versions Action
with:
package-name: 'com.github.actions.test-package'
package-name: 'action-ci-test-package'
package-type: 'npm'
num-old-versions-to-delete: 0
min-versions-to-keep: 0

View File

@@ -1,6 +1,6 @@
---
name: "@actions/core"
version: 1.6.0
version: 1.9.1
type: npm
summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/master/packages/core

View File

@@ -1,6 +1,6 @@
---
name: "@octokit/rest"
version: 16.43.1
version: 19.0.5
type: npm
summary: GitHub REST API client for Node.js
homepage: https://github.com/octokit/rest.js#readme

View File

@@ -1,6 +1,6 @@
---
name: rxjs
version: 6.5.4
version: 6.6.7
type: npm
summary: Reactive Extensions for modern JavaScript
homepage: https://github.com/ReactiveX/RxJS

228
README.md
View File

@@ -1,6 +1,6 @@
# Delete Package Versions
This action deletes versions of a package from [GitHub Packages](https://github.com/features/packages) except ghcr packages. This action will only delete a maximum of 99 versions in one run.
This action deletes versions of a package from [GitHub Packages](https://github.com/features/packages). This action will only delete a maximum of 100 versions in one run.
### What It Can Do
@@ -8,8 +8,8 @@ This action deletes versions of a package from [GitHub Packages](https://github.
* Delete all package versions except n most recent versions
* Delete oldest version(s)
* Ignore version(s) from deletion through regex
* Delete version(s) of a package that is hosted in the same repo that is executing the workflow
* Delete version(s) of a package that is hosted in a different repo than the one executing the workflow
* Delete version(s) of a package that is hosted from a repo having access to package
* Delete version(s) of a package that is hosted from a repo not having access to package
* Delete a single version
* Delete multiple versions
* Delete specific version(s)
@@ -17,40 +17,38 @@ This action deletes versions of a package from [GitHub Packages](https://github.
# Usage
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
# Can be a single package version id, or a comma separated list of package version ids.
# Defaults to an empty string.
package-version-ids:
# Owner of the repo hosting the package.
# Owner of the package.
# Defaults to the owner of the repo executing the workflow.
# Required if deleting a version from a package hosted in a different repo than the one executing the workflow.
# Required if deleting a version from a package hosted in a different org than the one executing the workflow.
owner:
# Repo hosting the package.
# Defaults to the repo executing the workflow.
# Required if deleting a version from a package hosted in a different repo than the one executing the workflow.
repo:
# Name of the package.
# Defaults to an empty string.
# Required if `package-version-ids` input is not given.
# Required
package-name:
# Type of the package. Can be one of container, maven, npm, nuget, or rubygems.
# Required
package-type:
# The number of old versions to delete starting from the oldest version.
# Defaults to 1.
num-old-versions-to-delete:
# The number of latest versions to keep.
# This cannot be specified with `num-old-versions-to-delete`. By default, `num-old-versions-to-delete` takes precedence over `min-versions-to-keep`.
# This cannot be specified with `num-old-versions-to-delete`. By default, `min-versions-to-keep` takes precedence over `num-old-versions-to-delete`.
# When set to 0, all deletable versions will be deleted.
# When set greater than 0, all deletable package versions except the specified number will be deleted.
min-versions-to-keep:
# The package versions to exclude from deletion.
# Takes regex for the version name as input.
# By default nothing is ignored.
# By default nothing is ignored. This is ignored when `delete-only-pre-release-versions` is true
ignore-versions:
# If true it will delete only the pre-release versions.
@@ -60,9 +58,19 @@ 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.
# The number of untagged versions to keep can be set by using `min-versions-to-keep` value with this.
# When `min-versions-to-keep` is 0, all untagged versions get deleted.
# Defaults to false.
# Cannot be used with `num-old-versions-to-delete`.
delete-only-untagged-versions:
# The token used to authenticate with GitHub Packages.
# Defaults to github.token.
# Required if deleting a version from a package hosted in a different repo than the one executing the workflow.
# Required if the repo running the workflow does not have access to delete the package.
# For rubygems and maven package, repo has access if package is hosted in the same repo as the workflow.
# For container, npm and nuget package, repo has access if assigned **Admin** role under Package Settings > Manage Actions Access.
# If `package-version-ids` is given the token only needs the delete packages scope.
# If `package-version-ids` is not given the token needs the delete packages scope and the read packages scope
token:
@@ -70,7 +78,7 @@ This action deletes versions of a package from [GitHub Packages](https://github.
# Valid Input Combinations
`owner`, `repo`, `package-name` and `token` can be used with the following combinations in a workflow -
`owner`, `package-name`, `package-type` and `token` can be used with the following combinations in a workflow -
- `num-old-versions-to-delete`
- `min-versions-to-keep`
@@ -79,48 +87,56 @@ This action deletes versions of a package from [GitHub Packages](https://github.
- `num-old-versions-to-delete` + `ignore-versions`
- `min-versions-to-keep` + `ignore-versions`
- `min-versions-to-keep` + `delete-only-pre-release-versions`
- `delete-only-untagged-versions`
- `min-versions-to-keep` + `delete-only-untagged-versions`
# 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 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)
- [Delete oldest x number of versions of a package](#delete-oldest-x-number-of-versions-of-a-package)
- [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)
- [Delete Package Versions](#delete-package-versions)
- [What It Can Do](#what-it-can-do)
- [Usage](#usage)
- [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)
- [Delete oldest x number of versions of a package](#delete-oldest-x-number-of-versions-of-a-package)
- [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)
- [License](#license)
### Delete all pre-release versions except y latest pre-release package versions
To delete all pre release versions except y latest pre-release package versions in the same repo as the workflow the __package-name__, __min-versions-to-keep__ and __delete-only-pre-release-versions__ inputs are required.
To delete all pre release versions except y latest pre-release package versions, the __package-name__, __min-versions-to-keep__ and __delete-only-pre-release-versions__ inputs are required.
__Example__
Delete all pre-release package versions except latest 10 in the same repo as the workflow
Delete all pre-release package versions except latest 10
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-name: 'test-package'
package-type: 'npm'
min-versions-to-keep: 10
delete-only-pre-release-versions: "true"
```
To delete all pre release versions except y latest pre-release package versions in a different repo than the workflow the __owner__, __repo__, __package-name__, __token__, __min-versions-to-keep__ and __delete-only-pre-release-versions__ inputs are required.
The [token][token] needs the delete packages and read packages scope. It is recommended [to store the token as a secret][secret]. In this example the [token][token] was stored as a secret named __GITHUB_PAT__.
To delete all pre release versions except y latest pre-release package versions from a repo not having access to package, the __owner__, __package-name__, __token__, __min-versions-to-keep__ and __delete-only-pre-release-versions__ inputs are required.
__Example__
Delete all pre-release package versions except latest 10 in a different repo than the workflow
Delete all pre-release package versions except latest 10 from a repo not having access to package
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
owner: 'github'
repo: 'packages'
package-name: 'test-package'
package-type: 'npm'
token: ${{ secrets.GITHUB_PAT }}
min-versions-to-keep: 10
delete-only-pre-release-versions: "true"
@@ -128,36 +144,56 @@ This action deletes versions of a package from [GitHub Packages](https://github.
<br>
### Delete all except y latest versions while ignoring particular package versions
### Delete all untagged container versions except y latest untagged versions
To delete all except y latest versions while ignoring particular package versions in the same repo as the workflow the __package-name__, __min-versions-to-keep__ and __ignore-versions__ inputs are required.
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 except latest 3 package versions excluding major versions as per semver in the same repo as the workflow
Delete all untagged versions except latest 10
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-name: 'test-packae'
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.
__Example__
Delete all except latest 3 package versions excluding major versions as per semver
```yaml
- uses: actions/delete-package-versions@v4
with:
package-name: 'test-package'
package-type: 'npm'
min-versions-to-keep: 3
ignore-versions: '^(0|[1-9]\\d*)\\.0\\.0$'
```
To delete all except y latest versions while ignoring particular package versions in a different repo than the workflow the __owner__, __repo__, __package-name__, __token__, __min-versions-to-keep__ and __ignore-versions__ inputs are required.
To delete all except y latest versions while ignoring particular package versions from a repo not having access to package, the __owner__, __package-name__, __token__, __min-versions-to-keep__ and __ignore-versions__ inputs are required.
The [token][token] needs the delete packages and read packages scope. It is recommended [to store the token as a secret][secret]. In this example the [token][token] was stored as a secret named __GITHUB_PAT__.
__Example__
Delete all except latest 3 package versions excluding major versions as per semver in a different repo than the workflow
Delete all except latest 3 package versions excluding major versions as per semver from a repo not having access to package
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
owner: 'github'
repo: 'packages'
package-name: 'test-packae'
package-name: 'test-package'
package-type: 'npm'
token: ${{ secrets.GITHUB_PAT }}
min-versions-to-keep: 3
ignore-versions: '^(0|[1-9]\\d*)\\.0\\.0$'
@@ -167,38 +203,37 @@ This action deletes versions of a package from [GitHub Packages](https://github.
### Delete oldest x number of versions while ignoring particular package versions
To delete oldest x number of versions while ignoring all the major package versions in the same repo as the workflow the __package-name__, __num-oldest-versions-to-delete__ and __ignore-versions__ inputs are required.
To delete oldest x number of versions while ignoring all the major package versions, the __package-name__, __num-oldest-versions-to-delete__ and __ignore-versions__ inputs are required.
There is a possibility if the oldest x number of versions contain ignored package versions, actual package versions to get deleted will be less than x.
__Example__
Delete 3 oldest versions excluding major versions as per semver is the same repo as the workflow
Delete 3 oldest versions excluding major versions as per semver
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-name: 'test-packae'
package-name: 'test-package'
package-type: 'npm'
num-old-versions-to-delete: 3
ignore-versions: '^(0|[1-9]\\d*)\\.0\\.0$'
```
To delete oldest x number of versions while ignoring all the major package versions in a different repo than the workflow the __owner__, __repo__, __package-name__, __token__, __num-oldest-versions-to-delete__ and __ignore-versions__ inputs are required.
To delete oldest x number of versions while ignoring all the major package versions from a repo not having access to package, the __owner__, __package-name__, __token__, __num-oldest-versions-to-delete__ and __ignore-versions__ inputs are required.
There is a possibility if the oldest x number of versions contain ignored package versions, actual package versions to get deleted will be less than x.
The [token][token] needs the delete packages and read packages scope. It is recommended [to store the token as a secret][secret]. In this example the [token][token] was stored as a secret named __GITHUB_PAT__.
__Example__
Delete 3 oldest versions excluding major versions as per semver is a differernt repo than the workflow
Delete 3 oldest versions excluding major versions as per semver from a repo not having access to package
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
owner: 'github'
repo: 'packages'
package-name: 'test-packae'
package-name: 'test-package'
package-type: 'npm'
token: ${{ secrets.PAT }}
num-old-versions-to-delete: 3
ignore-versions: '^(0|[1-9]\\d*)\\.0\\.0$'
@@ -208,33 +243,34 @@ This action deletes versions of a package from [GitHub Packages](https://github.
### Delete all except y latest versions of a package
To delete all except y latest versions of a package hosted in the same repo as the workflow the __package-name__ and __min-versions-to-keep__ inputs are required.
To delete all except y latest versions of a package hosted, the __package-name__ and __min-versions-to-keep__ inputs are required.
__Example__
Delete all except latest 2 versions of a package hosted in the same repo as the workflow
Delete all except latest 2 versions of a package hosted
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-name: 'test-package'
package-type: 'npm'
min-versions-to-keep: 2
```
To delete all except y latest versions of a package hosted in a repo other than the workflow the __owner__, __repo__, __package-name__, __token__ and __min-versions-to-keep__ inputs are required.
To delete all except y latest versions of a package hosted from a repo not having access to package, the __owner__, __package-name__, __token__ and __min-versions-to-keep__ inputs are required.
The [token][token] needs the delete packages and read packages scope. It is recommended [to store the token as a secret][secret]. In this example the [token][token] was stored as a secret named __GITHUB_PAT__.
__Example__
Delete all except latest 2 versions of a package hosted in a repo other than the workflow
Delete all except latest 2 versions of a package hosted from a repo not having access to package
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
owner: 'github'
repo: 'packages'
package-name: 'test-package'
package-type: 'npm'
token: ${{ secrets.PAT }}
min-versions-to-keep: 2
```
@@ -243,33 +279,34 @@ This action deletes versions of a package from [GitHub Packages](https://github.
### Delete oldest x number of versions of a package
To delete the oldest x number of versions of a package hosted in the same repo as the workflow the __package-name__, and __num-old-versions-to-delete__ inputs are required.
To delete the oldest x number of versions of a package hosted, the __package-name__, and __num-old-versions-to-delete__ inputs are required.
__Example__
Delete the oldest 3 version of a package hosted in the same repo as the workflow
Delete the oldest 3 version of a package hosted
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-name: 'test-package'
package-type: 'npm'
num-old-versions-to-delete: 3
```
To delete the oldest x number of versions of a package hosted in a different repo than the workflow the __owner__, __repo__, __package-name__, __token__ and __num-old-versions-to-delete__ inputs are required.
To delete the oldest x number of versions of a package hosted from a repo not having access to package, the __owner__, __package-name__, __token__ and __num-old-versions-to-delete__ inputs are required.
The [token][token] needs the delete packages and read packages scope. It is recommended [to store the token as a secret][secret]. In this example the [token][token] was stored as a secret named __GITHUB_PAT__.
__Example__
Delete the oldest 3 version of a package hosted in a different repo than the one executing the workflow
Delete the oldest 3 version of a package hosted from a repo not having access to package
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
owner: 'github'
repo: 'packages'
package-name: 'test-package'
package-type: 'npm'
num-old-versions-to-delete: 3
token: ${{ secrets.GITHUB_PAT }}
```
@@ -278,28 +315,27 @@ This action deletes versions of a package from [GitHub Packages](https://github.
### Delete oldest version of a package
To delete the oldest version of a package that is hosted in the same repo as the workflow the __package-name__ input is required.
To delete the oldest version of a package that is hosted, the __package-name__ input is required.
__Example__
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-name: 'test-package'
package-type: 'npm'
```
To delete the oldest version of a package that is hosted in a different repo than the workflow the __owner__, __repo__, __package-name__, __token__ inputs are required.
The [token][token] needs the delete packages and read packages scope. It is recommended [to store the token as a secret][secret]. In this example the [token][token] was stored as a secret named __GITHUB_PAT__.
To delete the oldest version of a package that is hosted from a repo not having access to package, the __owner__, __package-name__, __token__ inputs are required.
__Example__
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
owner: 'github'
repo: 'packages'
package-name: 'test-package'
package-type: 'npm'
token: ${{ secrets.PAT }}
```
@@ -307,30 +343,32 @@ This action deletes versions of a package from [GitHub Packages](https://github.
### Delete a specific version of a package
To delete a specific version of a package that is hosted in the same repo as the workflow the __package-version-ids__ input is required.
To delete a specific version of a package that is hosted, the __package-version-ids__ input is required.
Package version ids can be retrieved via the [GitHub GraphQL API][api]
Package version ids can be retrieved via the [GitHub REST API][api]
__Example__
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-version-ids: 'MDE0OlBhY2thZ2VWZXJzaW9uOTcyMDY3'
package-name: 'test-package'
package-type: 'npm'
```
To delete a specific version of a package that is hosted in a different repo than the workflow the __package-version-ids__ and __token__ inputs are required.
To delete a specific version of a package that is hosted from a repo not having access to package, the __package-version-ids__ and __token__ inputs are required.
The [token][token] needs the delete packages and read packages scope. It is recommended [to store the token as a secret][secret]. In this example the [token][token] was stored as a secret named __GITHUB_PAT__.
Package version ids can be retrieved via the [GitHub GraphQL API][api]
Package version ids can be retrieved via the [GitHub REST API][api]
__Example__
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-version-ids: 'MDE0OlBhY2thZ2VWZXJzaW9uOTcyMDY3'
package-name: 'test-package'
package-type: 'npm'
token: ${{ secrets.PAT }}
```
@@ -338,30 +376,32 @@ This action deletes versions of a package from [GitHub Packages](https://github.
### Delete multiple specific versions of a package
To delete multiple specifc versions of a package that is hosted in the same repo as the workflow the __package-version-ids__ input is required.
To delete multiple specific versions of a package that is hosted, the __package-version-ids__ input is required.
The __package-version-ids__ input should be a comma separated string of package version ids. Package version ids can be retrieved via the [GitHub GraphQL API][api].
The __package-version-ids__ input should be a comma separated string of package version ids. Package version ids can be retrieved via the [GitHub REST API][api].
__Example__
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-version-ids: 'MDE0OlBhY2thZ2VWZXJzaW9uOTcyMDY3, MDE0OlBhY2thZ2VWZXJzaW9uOTcyMzQ5, MDE0OlBhY2thZ2VWZXJzaW9uOTcyMzUw'
package-name: 'test-package'
package-type: 'npm'
```
To delete multiple specifc versions of a package that is hosted in a repo other than the workflow the __package-version-ids__, __token__ inputs are required.
To delete multiple specific versions of a package that is hosted from a repo not having access to package, the __package-version-ids__, __token__ inputs are required.
The __package-version-ids__ input should be a comma separated string of package version ids. Package version ids can be retrieved via the [GitHub GraphQL API][api].
The [token][token] needs the delete packages and read packages scope. It is recommended [to store the token as a secret][secret]. In this example the [token][token] was stored as a secret named __GITHUB_PAT__.
The __package-version-ids__ input should be a comma separated string of package version ids. Package version ids can be retrieved via the [GitHub REST API][api].
__Example__
```yaml
- uses: actions/delete-package-versions@v2
- uses: actions/delete-package-versions@v4
with:
package-version-ids: 'MDE0OlBhY2thZ2VWZXJzaW9uOTcyMDY3, MDE0OlBhY2thZ2VWZXJzaW9uOTcyMzQ5, MDE0OlBhY2thZ2VWZXJzaW9uOTcyMzUw'
package-name: 'test-package'
package-type: 'npm'
token: ${{ secrets.PAT }}
```
@@ -369,6 +409,6 @@ This action deletes versions of a package from [GitHub Packages](https://github.
The scripts and documentation in this project are released under the [MIT License](https://github.com/actions/delete-package-versions/blob/main/LICENSE)
[api]: https://developer.github.com/v4/previews/#github-packages
[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

View File

@@ -1,46 +1,347 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import {rest} from 'msw'
import {setupServer} from 'msw/node'
import {Input, InputParams} from '../src/input'
import {deleteVersions, finalIds} from '../src/delete'
import {deleteVersions, finalIds, RATE_LIMIT} from '../src/delete'
import {getMockedVersionsResponse} from './version/rest.mock'
import {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types'
describe.skip('index tests -- call graphql', () => {
it('finalIds test -- get oldest version', done => {
const numVersions = 1
type GetVersionsResponseData =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['response']['data']
finalIds(getInput({numOldVersionsToDelete: numVersions})).subscribe(ids => {
expect(ids.length).toBe(numVersions)
done()
})
describe('index tests -- call rest', () => {
let server = setupServer()
beforeEach(() => {
server = setupServer()
server.listen()
})
it.skip('finalIds test -- get oldest 3 versions', done => {
const numVersions = 3
finalIds(getInput({numOldVersionsToDelete: numVersions})).subscribe(ids => {
expect(ids.length).toBe(numVersions)
done()
})
afterEach(() => {
server.close()
})
it.skip('finalIds test -- get oldest 110 versions', done => {
const numVersions = 110
finalIds(getInput({numOldVersionsToDelete: numVersions})).subscribe(ids => {
expect(ids.length).toBe(99), async () => done()
})
})
it('finalIds test -- supplied package version id', done => {
const suppliedIds = [
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB',
'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC'
]
it('finalIds test - supplied package version id', done => {
const suppliedIds = ['123', '456', '789']
finalIds(getInput({packageVersionIds: suppliedIds})).subscribe(ids => {
expect(ids).toBe(suppliedIds)
expect(ids).toStrictEqual(suppliedIds)
done()
})
})
it('deleteVersions test -- missing token', done => {
it('finalIDs test - success', done => {
const numVersions = 10
let apiCalled = 0
const versions = getMockedVersionsResponse(numVersions)
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
apiCalled++
return res(ctx.status(200), ctx.json(versions))
}
)
)
finalIds(getInput()).subscribe(ids => {
expect(apiCalled).toBe(1)
expect(ids.length).toBe(numVersions)
for (let i = 0; i < numVersions; i++) {
expect(ids[i]).toBe(versions[i].id.toString())
}
done()
})
})
it('finalIDs test - success - GHES', done => {
process.env.GITHUB_API_URL = 'https://github.someghesinstance.com/api/v3'
const numVersions = 10
let apiCalled = 0
const versions = getMockedVersionsResponse(numVersions)
server.use(
rest.get(
'https://github.someghesinstance.com/api/v3/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
apiCalled++
return res(ctx.status(200), ctx.json(versions))
}
)
)
finalIds(getInput()).subscribe(ids => {
expect(apiCalled).toBe(1)
expect(ids.length).toBe(numVersions)
for (let i = 0; i < numVersions; i++) {
expect(ids[i]).toBe(versions[i].id.toString())
}
delete process.env.GITHUB_API_URL
done()
})
})
it('finalIDs test - success - pagination', done => {
const numVersions = RATE_LIMIT * 2
let apiCalled = 0
const versions = getMockedVersionsResponse(numVersions)
const firstPage = versions.slice(0, RATE_LIMIT)
const secondPage = versions.slice(RATE_LIMIT)
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
apiCalled++
const page = req.url.searchParams.get('page')
if (page === '1') {
return res(ctx.status(200), ctx.json(firstPage))
} else if (page === '2') {
return res(ctx.status(200), ctx.json(secondPage))
} else {
return res(ctx.status(200), ctx.json([]))
}
}
)
)
finalIds(getInput()).subscribe(ids => {
expect(apiCalled).toBe(3) // 2 full pages + 1 empty page
// never returns more than RATE_LIMIT versions
expect(ids.length).toBe(RATE_LIMIT)
for (let i = 0; i < RATE_LIMIT; i++) {
expect(ids[i]).toBe(versions[i].id.toString())
}
done()
})
})
it('finalIDs test - success - sorting accross pages', done => {
const numVersions = RATE_LIMIT * 2
let apiCalled = 0
// versions is in ascending order of created_at
const versions = getMockedVersionsResponse(numVersions)
// return newer versions on first page to test sorting
const firstPage = versions.slice(RATE_LIMIT).reverse()
const secondPage = versions.slice(0, RATE_LIMIT).reverse()
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
apiCalled++
const page = req.url.searchParams.get('page')
if (page === '1') {
return res(ctx.status(200), ctx.json(firstPage))
} else if (page === '2') {
return res(ctx.status(200), ctx.json(secondPage))
} else {
return res(ctx.status(200), ctx.json([]))
}
}
)
)
finalIds(getInput()).subscribe(ids => {
expect(apiCalled).toBe(3) // 2 full pages + 1 empty page
expect(ids.length).toBe(RATE_LIMIT)
for (let i = 0; i < RATE_LIMIT; i++) {
expect(ids[i]).toBe(versions[i].id.toString())
}
done()
})
})
it('finalIds test - do not delete more than numOldVersionsToDelete', done => {
const numVersions = 50
let apiCalled = 0
const versions = getMockedVersionsResponse(numVersions)
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
apiCalled++
return res(ctx.status(200), ctx.json(versions))
}
)
)
const numOldVersionsToDelete = 10
finalIds(getInput({numOldVersionsToDelete})).subscribe(ids => {
expect(apiCalled).toBe(1)
expect(ids.length).toBe(numOldVersionsToDelete)
for (let i = 0; i < numOldVersionsToDelete; i++) {
expect(ids[i]).toBe(versions[i].id.toString())
}
done()
})
})
it('finalIds test - keep minVersionsToKeep', done => {
const numVersions = 50
let apiCalled = 0
const versions = getMockedVersionsResponse(numVersions)
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
apiCalled++
return res(ctx.status(200), ctx.json(versions))
}
)
)
const minVersionsToKeep = 10
finalIds(getInput({minVersionsToKeep})).subscribe(ids => {
expect(apiCalled).toBe(1)
expect(ids.length).toBe(numVersions - minVersionsToKeep)
for (let i = 0; i < numVersions - minVersionsToKeep; i++) {
expect(ids[i]).toBe(versions[i].id.toString())
}
done()
})
})
it('finalIds test - delete only prerelease versions with minVersionsToKeep', done => {
const numVersions = 50
let apiCalled = 0
const versions = getMockedVersionsResponse(numVersions)
// make half versions prerelease
for (let i = 0; i < numVersions; i++) {
if (i % 2 === 0) {
versions[i].name += '-alpha'
}
}
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
apiCalled++
return res(ctx.status(200), ctx.json(versions))
}
)
)
const toDelete = numVersions / 2 - 10
finalIds(
getInput({
ignoreVersions: RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$'),
minVersionsToKeep: 10
})
).subscribe(ids => {
expect(apiCalled).toBe(1)
expect(ids.length).toBe(toDelete)
for (let i = 0; i < toDelete; i++) {
expect(ids[i]).toBe(versions[i * 2].id.toString())
}
done()
})
})
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
const versions = getMockedVersionsResponse(numVersions)
const firstPage = versions.slice(0, RATE_LIMIT)
const secondPage = versions.slice(RATE_LIMIT)
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
apiCalled++
const page = req.url.searchParams.get('page')
if (page === '1') {
return res(ctx.status(200), ctx.json(firstPage))
} else if (page === '2') {
return res(ctx.status(500), ctx.json([]))
} else {
return res(ctx.status(200), ctx.json([]))
}
}
)
)
finalIds(getInput()).subscribe(
() => {
done.fail('should not complete')
},
err => {
expect(apiCalled).toBe(2) // 1 full page + 1 error page
expect(err).toBeTruthy()
expect(err).toContain('get versions API failed.')
done()
}
)
})
it('deleteVersions test - missing token', done => {
deleteVersions(getInput({token: ''})).subscribe({
error: err => {
expect(err).toBeTruthy()
@@ -50,7 +351,7 @@ describe.skip('index tests -- call graphql', () => {
})
})
it('deleteVersions test -- missing packageName', done => {
it('deleteVersions test - missing packageName', done => {
deleteVersions(getInput({packageName: ''})).subscribe({
error: err => {
expect(err).toBeTruthy()
@@ -60,41 +361,123 @@ describe.skip('index tests -- call graphql', () => {
})
})
it.skip('deleteVersions test -- delete oldest version', done => {
deleteVersions(getInput({numOldVersionsToDelete: 1})).subscribe(
isSuccess => {
expect(isSuccess)
it('deleteVersions test - missing packageType', done => {
deleteVersions(getInput({packageType: ''})).subscribe({
error: err => {
expect(err).toBeTruthy()
done()
},
async () => done()
)
complete: async () => done.fail('no error thrown')
})
})
it.skip('deleteVersions test -- delete 3 oldest versions', done => {
deleteVersions(getInput({numOldVersionsToDelete: 3})).subscribe(
isSuccess => {
expect(isSuccess)
},
async () => done()
)
it('deleteVersions test - zero numOldVersionsToDelete', done => {
deleteVersions(getInput({numOldVersionsToDelete: 0})).subscribe(result => {
expect(result).toBe(true)
done()
})
})
it.skip('deleteVersions test -- keep 5 versions', done => {
deleteVersions(getInput({minVersionsToKeep: 100})).subscribe(isSuccess => {
expect(isSuccess).toBe(true)
}),
async () => done()
it('deleteVersions test - success complete flow', done => {
const numVersions = 10
let getApiCalled = 0
let deleteApiCalled = 0
const versions = getMockedVersionsResponse(numVersions)
const versionsDeleted: string[] = []
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
getApiCalled++
return res(ctx.status(200), ctx.json(versions))
}
)
)
server.use(
rest.delete(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions/:versionId',
(req, res, ctx) => {
deleteApiCalled++
versionsDeleted.push(req.params.versionId as string)
return res(ctx.status(204))
}
)
)
deleteVersions(getInput())
.subscribe(result => {
expect(result).toBe(true)
})
.add(() => {
expect(getApiCalled).toBe(1)
expect(deleteApiCalled).toBe(numVersions)
for (let i = 0; i < numVersions; i++) {
expect(versionsDeleted[i]).toBe(versions[i].id.toString())
}
done()
})
})
it('deleteVersions test - success complete flow - GHES', done => {
process.env.GITHUB_API_URL = 'https://github.someghesinstance.com/api/v3'
const numVersions = 10
let getApiCalled = 0
let deleteApiCalled = 0
const versions = getMockedVersionsResponse(numVersions)
const versionsDeleted: string[] = []
server.use(
rest.get(
'https://github.someghesinstance.com/api/v3/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
getApiCalled++
return res(ctx.status(200), ctx.json(versions))
}
)
)
server.use(
rest.delete(
'https://github.someghesinstance.com/api/v3/users/test-owner/packages/npm/test-package/versions/:versionId',
(req, res, ctx) => {
deleteApiCalled++
versionsDeleted.push(req.params.versionId as string)
return res(ctx.status(204))
}
)
)
deleteVersions(getInput())
.subscribe(result => {
expect(result).toBe(true)
})
.add(() => {
expect(getApiCalled).toBe(1)
expect(deleteApiCalled).toBe(numVersions)
for (let i = 0; i < numVersions; i++) {
expect(versionsDeleted[i]).toBe(versions[i].id.toString())
}
delete process.env.GITHUB_API_URL
done()
})
})
})
const defaultInput: InputParams = {
packageVersionIds: [],
owner: 'namratajha',
repo: 'only-pkg',
packageName: 'only-pkg',
numOldVersionsToDelete: 1,
owner: 'test-owner',
packageName: 'test-package',
packageType: 'npm',
numOldVersionsToDelete: RATE_LIMIT,
minVersionsToKeep: -1,
ignoreVersions: RegExp('^$'),
token: process.env.GITHUB_TOKEN as string
token: 'test-token'
}
function getInput(params?: InputParams): Input {

View File

@@ -1,25 +1,179 @@
import {rest} from 'msw'
import {setupServer} from 'msw/node'
import {deletePackageVersion, deletePackageVersions} from '../../src/version'
const githubToken = process.env.GITHUB_TOKEN as string
describe('delete tests - mock rest', () => {
let server = setupServer()
describe.skip('delete tests', () => {
it('deletePackageVersion', async () => {
const response = await deletePackageVersion(
'PV_lADOGReZt84AEI7FzgDSHEI',
githubToken
).toPromise()
expect(response).toBe(true)
beforeEach(() => {
server = setupServer()
server.listen()
})
it('deletePackageVersions', async () => {
const response = await deletePackageVersions(
[
'PV_lADOGReZt84AEI7FzgDSHDs',
'PV_lADOGReZt84AEI7FzgDSHDY',
'PV_lADOGReZt84AEI7FzgDSHC8'
],
githubToken
).toPromise()
expect(response).toBe(true)
afterEach(() => {
server.close()
})
it('deletePackageVersion', done => {
server.use(
rest.delete(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions/123',
(req, res, ctx) => {
return res(ctx.status(204))
}
)
)
deletePackageVersion(
'123',
'test-owner',
'test-package',
'npm',
'test-token'
).subscribe(result => {
expect(result).toBe(true)
done()
})
})
it('deletePackageVersions', done => {
let success = 0
server.use(
rest.delete(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions/*',
(req, res, ctx) => {
return res(ctx.status(204))
}
)
)
deletePackageVersions(
['123', '456', '789'],
'test-owner',
'test-package',
'npm',
'test-token'
)
.subscribe(
result => {
expect(result).toBe(true)
success++
},
err => {
// should not get here
done.fail(err)
}
)
.add(() => {
expect(success).toBe(3)
done()
})
})
it('deletePackageVersions - GHES', done => {
process.env.GITHUB_API_URL = 'https://github.someghesinstance.com/api/v3'
let success = 0
server.use(
rest.delete(
'https://github.someghesinstance.com/api/v3/users/test-owner/packages/npm/test-package/versions/*',
(req, res, ctx) => {
return res(ctx.status(204))
}
)
)
deletePackageVersions(
['123', '456', '789'],
'test-owner',
'test-package',
'npm',
'test-token'
)
.subscribe(
result => {
expect(result).toBe(true)
success++
},
err => {
// should not get here
done.fail(err)
}
)
.add(() => {
expect(success).toBe(3)
delete process.env.GITHUB_API_URL
done()
})
})
it('deletePackageVersion - API error', done => {
server.use(
rest.delete(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions/123',
(req, res, ctx) => {
return res(ctx.status(500))
}
)
)
deletePackageVersion(
'123',
'test-owner',
'test-package',
'npm',
'test-token'
).subscribe(
() => {
done.fail('should not get here.')
},
err => {
expect(err).toContain('delete version API failed.')
done()
}
)
})
it('deletePackageVersions - API error for some versions', done => {
let success = 0
let failed = 0
server.use(
rest.delete(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions/:versionId',
(req, res, ctx) => {
if (req.params.versionId === '456') {
return res(ctx.status(500))
}
return res(ctx.status(204))
}
)
)
deletePackageVersions(
['123', '456', '789'],
'test-owner',
'test-package',
'npm',
'test-token'
)
.subscribe(
result => {
expect(result).toBe(true)
success++
},
err => {
expect(err).toContain('delete version API failed.')
failed++
}
)
.add(() => {
expect(success).toBe(2)
expect(failed).toBe(1)
done()
})
})
})

View File

@@ -1,68 +1,200 @@
import {mockOldestQueryResponse} from './graphql.mock'
import {rest} from 'msw'
import {setupServer} from 'msw/node'
import {
getOldestVersions as _getOldestVersions,
QueryInfo
RestQueryInfo
} from '../../src/version'
import {Observable} from 'rxjs'
import {getMockedVersionsResponse} from './rest.mock'
import {RATE_LIMIT} from '../../src/delete'
describe.skip('get versions tests -- call graphql', () => {
it('getOldestVersions -- succeeds', done => {
const numVersions = 1
getOldestVersions({numVersions}).subscribe(result => {
expect(result.versions.length).toBe(numVersions)
done()
})
describe('get versions tests -- mock rest', () => {
let server = setupServer()
beforeEach(() => {
server = setupServer()
server.listen()
})
it('getOldestVersions -- fails for invalid repo', done => {
getOldestVersions({repo: 'actions-testin'}).subscribe({
error: err => {
expect(err).toBeTruthy()
done()
},
complete: async () => done.fail('no error thrown')
})
afterEach(() => {
server.close()
})
})
describe('get versions tests -- mock graphql', () => {
it('getOldestVersions -- success', done => {
const numVersions = 5
mockOldestQueryResponse(numVersions)
const numVersions = RATE_LIMIT
const resp = getMockedVersionsResponse(numVersions)
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
return res(ctx.status(200), ctx.json(resp))
}
)
)
getOldestVersions({numVersions}).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)
}
expect(result.paginate).toBe(true)
expect(result.totalCount).toBe(numVersions)
done()
})
})
it('getOldestVersions -- success - GHES', done => {
const numVersions = RATE_LIMIT
const resp = getMockedVersionsResponse(numVersions)
// set GITHUB_API_URL to a different base url
process.env.GITHUB_API_URL = 'https://github.someghesinstance.com/api/v3'
server.use(
rest.get(
'https://github.someghesinstance.com/api/v3/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
return res(ctx.status(200), ctx.json(resp))
}
)
)
getOldestVersions({numVersions}).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)
}
expect(result.paginate).toBe(true)
expect(result.totalCount).toBe(numVersions)
delete process.env.GITHUB_API_URL
done()
})
})
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
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
return res(
ctx.status(200),
ctx.json(getMockedVersionsResponse(numVersions))
)
}
)
)
// In the call numVersions is set to RATE_LIMIT, but the response has only 5 versions.
getOldestVersions().subscribe(result => {
expect(result.paginate).toBe(false)
done()
})
})
it('getOldestVersions -- API error', done => {
server.use(
rest.get(
'https://api.github.com/users/test-owner/packages/npm/test-package/versions',
(req, res, ctx) => {
return res(ctx.status(500))
}
)
)
getOldestVersions().subscribe(
() => {
done.fail('should not get here.')
},
err => {
expect(err).toContain('get versions API failed.')
done()
}
)
})
})
interface Params {
owner?: string
repo?: string
packageName?: string
packageType?: string
numVersions?: number
startCursor?: string
page?: number
token?: string
}
const defaultParams = {
owner: 'namratajha',
repo: 'test-repo',
packageName: 'test-repo',
numVersions: 1,
startCursor: '',
token: process.env.GITHUB_TOKEN as string
owner: 'test-owner',
packageName: 'test-package',
packageType: 'npm',
numVersions: RATE_LIMIT,
page: 1,
token: 'test-token'
}
function getOldestVersions(params?: Params): Observable<QueryInfo> {
function getOldestVersions(params?: Params): Observable<RestQueryInfo> {
const p: Required<Params> = {...defaultParams, ...params}
return _getOldestVersions(
p.owner,
p.repo,
p.packageName,
p.packageType,
p.numVersions,
p.startCursor,
p.page,
p.token
)
}

View File

@@ -1,55 +0,0 @@
import {
GraphQlQueryResponseData,
RequestParameters
} from '@octokit/graphql/dist-types/types'
import * as Graphql from '../../src/version/graphql'
import {GetVersionsQueryResponse} from '../../src/version'
export function getMockedOldestQueryResponse(
numVersions: number
): GetVersionsQueryResponse {
const versions = []
numVersions = numVersions < 100 ? numVersions : numVersions
for (let i = 1; i <= numVersions; ++i) {
versions.push({
node: {
id: i.toString(),
version: `${i}.0.0`
}
})
}
return {
repository: {
packages: {
edges: [
{
node: {
name: 'test',
versions: {
totalCount: 200,
edges: versions.reverse(),
pageInfo: {
startCursor: 'AAA',
hasPreviousPage: false
}
}
}
}
]
}
}
}
}
export function mockOldestQueryResponse(numVersions: number): void {
const response = new Promise<GetVersionsQueryResponse>(resolve => {
resolve(getMockedOldestQueryResponse(numVersions))
}) as Promise<GraphQlQueryResponseData>
jest
.spyOn(Graphql, 'graphql')
.mockImplementation(
(token: string, query: string, parameters: RequestParameters) => response
)
}

View File

@@ -0,0 +1,45 @@
import {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types'
type GetVersionsResponseData =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['response']['data']
type PackageType =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['parameters']['package_type']
export function getMockedVersionsResponse(
numVersions: number,
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)
let version = {
id: i,
name: `${i}.0.0`,
url: '',
created_at: created_at.toUTCString(),
package_html_url: '',
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
}

View File

@@ -15,17 +15,15 @@ inputs:
Defaults to the owner of the repo running the action.
required: false
repo:
description: >
Repo containing the package version to delete.
Defaults to the repo running the action.
required: false
package-name:
description: >
Name of the package containing the version to delete.
Required if dynamically deleting oldest versions.
required: false
required: true
package-type:
description: >
Type of package. Can be one of container, maven, npm, nuget, or rubygems.
required: true
num-old-versions-to-delete:
description: >
@@ -57,6 +55,15 @@ 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.
The number of untagged versions to keep can be specified by min-versions-to-keep.
When this is set num-old-versions-to-delete will not be taken into account.
By default this is set to false
required: false
default: "false"
token:
description: >
Token with the necessary scopes to delete package versions.
@@ -67,9 +74,9 @@ inputs:
default: ${{ github.token }}
runs:
using: node12
using: node16
main: dist/index.js
branding:
icon: package
color: blue
color: blue

19319
dist/index.js vendored

File diff suppressed because one or more lines are too long

19890
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -25,23 +25,24 @@
"author": "YourNameOrOrganization",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.6.0",
"@actions/core": "^1.9.1",
"@actions/github": "^2.1.1",
"@octokit/rest": "^19.0.5",
"rxjs": "^6.5.4"
},
"devDependencies": {
"@types/jest": "^24.0.23",
"@types/node": "^12.7.12",
"@typescript-eslint/parser": "^2.8.0",
"@vercel/ncc": "^0.31.1",
"eslint": "^5.16.0",
"eslint-plugin-github": "^2.0.0",
"eslint-plugin-jest": "^22.21.0",
"jest": "^27.3.1",
"jest-circus": "^27.3.1",
"js-yaml": "^3.13.1",
"prettier": "^1.19.1",
"ts-jest": "^27.0.7",
"typescript": "^3.6.4"
"@types/jest": "^27.5.2",
"@types/node": "^18.11.18",
"@typescript-eslint/parser": "^5.47.1",
"@vercel/ncc": "^0.36.0",
"eslint": "^8.30.0",
"eslint-plugin-github": "^4.6.0",
"eslint-plugin-jest": "^27.1.7",
"jest": "^29.3.1",
"jest-circus": "^29.3.1",
"js-yaml": "^4.1.0",
"msw": "^0.49.2",
"prettier": "^2.8.1",
"ts-jest": "^29.0.3"
}
}

View File

@@ -1,127 +1,101 @@
/* eslint-disable i18n-text/no-en */
import {Input} from './input'
import {EMPTY, Observable, of, throwError} from 'rxjs'
import {deletePackageVersions, getOldestVersions, VersionInfo} from './version'
import {concatMap, map, expand, tap} from 'rxjs/operators'
import {reduce, concatMap, map, expand, tap} from 'rxjs/operators'
import {
deletePackageVersions,
getOldestVersions,
RestVersionInfo
} from './version'
const RATE_LIMIT = 99
export const RATE_LIMIT = 100
let totalCount = 0
export function getVersionIds(
owner: string,
repo: string,
packageName: string,
packageType: string,
numVersions: number,
cursor: string,
page: number,
token: string
): Observable<VersionInfo[]> {
): Observable<RestVersionInfo[]> {
return getOldestVersions(
owner,
repo,
packageName,
packageType,
numVersions,
cursor,
page,
token
).pipe(
expand(value =>
value.paginate
? getOldestVersions(
owner,
repo,
packageName,
packageType,
numVersions,
value.cursor,
value.page + 1,
token
)
: EMPTY
),
tap(
value => (totalCount = totalCount === 0 ? value.totalCount : totalCount)
),
map(value => value.versions)
tap(value => (totalCount = totalCount + value.totalCount)),
reduce((acc, value) => acc.concat(value.versions), [] as RestVersionInfo[])
)
}
export function finalIds(input: Input): Observable<string[]> {
if (input.packageVersionIds.length > 0) {
return of(input.packageVersionIds)
const toDelete = Math.min(input.packageVersionIds.length, RATE_LIMIT)
return of(input.packageVersionIds.slice(0, toDelete))
}
if (input.hasOldestVersionQueryInfo()) {
if (input.minVersionsToKeep < 0) {
// This code block is when num-old-versions-to-delete is specified.
// Setting input.numOldVersionsToDelete is set as minimum of input.numOldVersionsToDelete and RATE_LIMIT
input.numOldVersionsToDelete =
input.numOldVersionsToDelete < RATE_LIMIT
? input.numOldVersionsToDelete
: RATE_LIMIT
return getVersionIds(
input.owner,
input.repo,
input.packageName,
RATE_LIMIT,
'',
input.token
).pipe(
// This code block executes on batches of 100 versions starting from oldest
map(value => {
/*
return getVersionIds(
input.owner,
input.packageName,
input.packageType,
RATE_LIMIT,
1,
input.token
).pipe(
// This code block executes on all versions of a package starting from oldest
map(value => {
// we need to delete oldest versions first
value.sort((a, b) => {
if (a.created_at === b.created_at) {
return a.id - b.id
}
return (
new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
)
})
/*
Here first filter out the versions that are to be ignored.
Then update input.numOldeVersionsToDelete to the no of versions deleted from the next 100 versions batch.
Then compute number of versions to delete (toDelete) based on the inputs.
*/
value = value.filter(info => !input.ignoreVersions.test(info.version))
const temp = input.numOldVersionsToDelete
input.numOldVersionsToDelete =
input.numOldVersionsToDelete - value.length <= 0
? 0
: input.numOldVersionsToDelete - value.length
return value.map(info => info.id).slice(0, temp)
})
)
} else {
// This code block is when min-versions-to-keep is specified.
return getVersionIds(
input.owner,
input.repo,
input.packageName,
RATE_LIMIT,
'',
input.token
).pipe(
// This code block executes on batches of 100 versions starting from oldest
map(value => {
/*
Here totalCount is the total no of versions in the package.
First we update totalCount by removing no of ignored versions from it and also filter them out from value.
toDelete is the no of versions that need to be deleted and input.numDeleted is the total no of versions deleted before this batch.
We calculate this from total no of versions in the package, the min no of versions to keep and the no of versions we have deleted in earlier batch.
Then we update toDelete to not exceed the length of current batch of versions.
Now toDelete holds the no of versions to be deleted from the current batch of versions.
*/
totalCount =
totalCount -
value.filter(info => input.ignoreVersions.test(info.version)).length
value = value.filter(info => !input.ignoreVersions.test(info.version))
let toDelete = totalCount - input.minVersionsToKeep - input.numDeleted
toDelete = toDelete > value.length ? value.length : toDelete
//Checking here if we have any versions to delete and whether we are within the RATE_LIMIT.
if (toDelete > 0 && input.numDeleted < RATE_LIMIT) {
/*
Checking here if we can delete all the versions left in the current batch.
input.numDeleted + toDelete should not exceed RATE_LIMIT.
If it is exceeding we only delete the no of versions from this batch that are allowed within the RATE_LIMIT.
i.e. diff between RATE_LIMIT and versions deleted till now (input.numDeleted)
input.numDeleted is updated accordingly.
*/
if (input.numDeleted + toDelete > RATE_LIMIT) {
toDelete = RATE_LIMIT - input.numDeleted
input.numDeleted = RATE_LIMIT
} else {
input.numDeleted = input.numDeleted + toDelete
}
return value.map(info => info.id).slice(0, toDelete)
} else return []
})
)
}
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, RATE_LIMIT)
)
} else {
toDelete = Math.min(
value.length - input.minVersionsToKeep,
RATE_LIMIT
)
}
if (toDelete < 0) return []
return value.map(info => info.id.toString()).slice(0, toDelete)
})
)
}
return throwError(
"Could not get packageVersionIds. Explicitly specify using the 'package-version-ids' input"
@@ -146,5 +120,15 @@ export function deleteVersions(input: Input): Observable<boolean> {
const result = finalIds(input)
return result.pipe(concatMap(ids => deletePackageVersions(ids, input.token)))
return result.pipe(
concatMap(ids =>
deletePackageVersions(
ids,
input.owner,
input.packageName,
input.packageType,
input.token
)
)
)
}

View File

@@ -1,58 +1,61 @@
export interface InputParams {
packageVersionIds?: string[]
owner?: string
repo?: string
packageName?: string
packageType?: string
numOldVersionsToDelete?: number
minVersionsToKeep?: number
ignoreVersions?: RegExp
token?: string
deletePreReleaseVersions?: string
deleteUntaggedVersions?: string
}
const defaultParams = {
packageVersionIds: [],
owner: '',
repo: '',
packageName: '',
packageType: '',
numOldVersionsToDelete: 0,
minVersionsToKeep: 0,
ignoreVersions: new RegExp(''),
deletePreReleaseVersions: '',
token: ''
token: '',
deleteUntaggedVersions: ''
}
export class Input {
packageVersionIds: string[]
owner: string
repo: string
packageName: string
packageType: string
numOldVersionsToDelete: number
minVersionsToKeep: number
ignoreVersions: RegExp
deletePreReleaseVersions: string
token: string
numDeleted: number
deleteUntaggedVersions: string
constructor(params?: InputParams) {
const validatedParams: Required<InputParams> = {...defaultParams, ...params}
this.packageVersionIds = validatedParams.packageVersionIds
this.owner = validatedParams.owner
this.repo = validatedParams.repo
this.packageName = validatedParams.packageName
this.packageType = validatedParams.packageType
this.numOldVersionsToDelete = validatedParams.numOldVersionsToDelete
this.minVersionsToKeep = validatedParams.minVersionsToKeep
this.ignoreVersions = validatedParams.ignoreVersions
this.deletePreReleaseVersions = validatedParams.deletePreReleaseVersions
this.token = validatedParams.token
this.numDeleted = 0
this.deleteUntaggedVersions = validatedParams.deleteUntaggedVersions
}
hasOldestVersionQueryInfo(): boolean {
return !!(
this.owner &&
this.repo &&
this.packageName &&
this.numOldVersionsToDelete >= 0 &&
this.token
@@ -60,19 +63,34 @@ export class Input {
}
checkInput(): boolean {
if (this.packageType.toLowerCase() !== 'container') {
this.deleteUntaggedVersions = 'false'
}
if (
this.numOldVersionsToDelete > 1 &&
(this.minVersionsToKeep >= 0 || this.deletePreReleaseVersions === 'true')
(this.minVersionsToKeep >= 0 ||
this.deletePreReleaseVersions === 'true' ||
this.deleteUntaggedVersions === 'true')
) {
return false
}
if (this.packageType === '' || this.packageName === '') {
return false
}
if (this.deletePreReleaseVersions === 'true') {
this.minVersionsToKeep =
this.minVersionsToKeep > 0 ? this.minVersionsToKeep : 0
this.ignoreVersions = new RegExp('^(0|[1-9]\\d*)((\\.(0|[1-9]\\d*))*)$')
}
if (this.deleteUntaggedVersions === 'true') {
this.minVersionsToKeep =
this.minVersionsToKeep > 0 ? this.minVersionsToKeep : 0
}
if (this.minVersionsToKeep >= 0) {
this.numOldVersionsToDelete = 0
}

View File

@@ -11,15 +11,18 @@ function getActionInput(): Input {
? getInput('package-version-ids').split(',')
: [],
owner: getInput('owner') ? getInput('owner') : context.repo.owner,
repo: getInput('repo') ? getInput('repo') : context.repo.repo,
packageName: getInput('package-name'),
packageType: getInput('package-type'),
numOldVersionsToDelete: Number(getInput('num-old-versions-to-delete')),
minVersionsToKeep: Number(getInput('min-versions-to-keep')),
ignoreVersions: RegExp(getInput('ignore-versions')),
deletePreReleaseVersions: getInput(
'delete-only-pre-release-versions'
).toLowerCase(),
token: getInput('token')
token: getInput('token'),
deleteUntaggedVersions: getInput(
'delete-only-untagged-versions'
).toLowerCase()
})
}
@@ -29,7 +32,10 @@ function run(): Observable<boolean> {
catchError(err => throwError(err))
)
} catch (error) {
return throwError(error.message)
if (error instanceof Error) {
return throwError(error.message)
}
return throwError(error)
}
}

View File

@@ -1,49 +1,52 @@
import {from, Observable, merge, throwError, of} from 'rxjs'
import {catchError, map, tap} from 'rxjs/operators'
import {graphql} from './graphql'
import {Octokit} from '@octokit/rest'
import {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types'
let deleted = 0
export interface DeletePackageVersionMutationResponse {
deletePackageVersion: {
success: boolean
}
}
const mutation = `
mutation deletePackageVersion($packageVersionId: String!) {
deletePackageVersion(input: {packageVersionId: $packageVersionId}) {
success
}
}`
type PackageType =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['parameters']['package_type']
export function deletePackageVersion(
packageVersionId: string,
owner: string,
packageName: string,
packageType: string,
token: string
): Observable<boolean> {
const octokit = new Octokit({
auth: token,
baseUrl: process.env.GITHUB_API_URL || 'https://api.github.com'
})
const package_version_id = +packageVersionId
const package_type: PackageType = packageType as PackageType
deleted += 1
return from(
graphql(token, mutation, {
packageVersionId,
headers: {
Accept: 'application/vnd.github.package-deletes-preview+json'
}
}) as Promise<DeletePackageVersionMutationResponse>
octokit.rest.packages.deletePackageVersionForUser({
package_type,
package_name: packageName,
username: owner,
package_version_id
})
).pipe(
catchError(err => {
const msg = 'delete version mutation failed.'
const msg = 'delete version API failed.'
return throwError(
err.errors && err.errors.length > 0
? `${msg} ${err.errors[0].message}`
: `${msg} ${err.message} \n${deleted - 1} versions deleted till now.`
)
}),
map(response => response.deletePackageVersion.success)
map(response => response.status === 204)
)
}
export function deletePackageVersions(
packageVersionIds: string[],
owner: string,
packageName: string,
packageType: string,
token: string
): Observable<boolean> {
if (packageVersionIds.length === 0) {
@@ -51,7 +54,7 @@ export function deletePackageVersions(
}
const deletes = packageVersionIds.map(id =>
deletePackageVersion(id, token).pipe(
deletePackageVersion(id, owner, packageName, packageType, token).pipe(
tap(result => {
if (!result) {
console.log(`version with id: ${id}, not deleted`)

View File

@@ -1,191 +1,83 @@
import {GraphQlQueryResponse} from '@octokit/graphql/dist-types/types'
import {Observable, from, throwError} from 'rxjs'
/* eslint-disable @typescript-eslint/no-unused-vars */
import {from, Observable, merge, throwError, of} from 'rxjs'
import {catchError, map} from 'rxjs/operators'
import {graphql} from './graphql'
import {Octokit} from '@octokit/rest'
import {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types'
export interface VersionInfo {
id: string
export interface RestVersionInfo {
id: number
version: string
created_at: string
tagged: boolean
}
export interface QueryInfo {
versions: VersionInfo[]
cursor: string
export interface RestQueryInfo {
versions: RestVersionInfo[]
page: number
paginate: boolean
totalCount: number
}
export interface GetVersionsQueryResponse {
repository: {
packages: {
edges: {
node: {
name: string
versions: {
totalCount: number
edges: {node: VersionInfo}[]
pageInfo: {
startCursor: string
hasPreviousPage: boolean
}
}
}
}[]
}
}
}
const query = `
query getVersions($owner: String!, $repo: String!, $package: String!, $last: Int!) {
repository(owner: $owner, name: $repo) {
packages(first: 1, names: [$package]) {
edges {
node {
name
versions(last: $last) {
totalCount
edges {
node {
id
version
}
}
pageInfo {
startCursor
hasPreviousPage
}
}
}
}
}
}
}`
const Paginatequery = `
query getVersions($owner: String!, $repo: String!, $package: String!, $last: Int!, $before: String!) {
repository(owner: $owner, name: $repo) {
packages(first: 1, names: [$package]) {
edges {
node {
name
versions(last: $last, before: $before) {
totalCount
edges {
node {
id
version
}
}
pageInfo{
startCursor
hasPreviousPage
}
}
}
}
}
}
}`
export function queryForOldestVersions(
owner: string,
repo: string,
packageName: string,
numVersions: number,
startCursor: string,
token: string
): Observable<GetVersionsQueryResponse> {
if (startCursor === '') {
return from(
graphql(token, query, {
owner,
repo,
package: packageName,
last: numVersions,
headers: {
Accept: 'application/vnd.github.packages-preview+json'
}
}) as Promise<GetVersionsQueryResponse>
).pipe(
catchError((err: GraphQlQueryResponse) => {
const msg = 'query for oldest version failed.'
return throwError(
err.errors && err.errors.length > 0
? `${msg} ${err.errors[0].message}`
: `${msg} verify input parameters are correct`
)
})
)
} else {
return from(
graphql(token, Paginatequery, {
owner,
repo,
package: packageName,
last: numVersions,
before: startCursor,
headers: {
Accept: 'application/vnd.github.packages-preview+json'
}
}) as Promise<GetVersionsQueryResponse>
).pipe(
catchError((err: GraphQlQueryResponse) => {
const msg = 'query for oldest version failed.'
return throwError(
err.errors && err.errors.length > 0
? `${msg} ${err.errors[0].message}`
: `${msg} verify input parameters are correct`
)
})
)
}
}
type PackageType =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['parameters']['package_type']
type GetVersionsResponse =
RestEndpointMethodTypes['packages']['getAllPackageVersionsForPackageOwnedByUser']['response']['data']
export function getOldestVersions(
owner: string,
repo: string,
packageName: string,
packageType: string,
numVersions: number,
startCursor: string,
page: number,
token: string
): Observable<QueryInfo> {
return queryForOldestVersions(
owner,
repo,
packageName,
numVersions,
startCursor,
token
): Observable<RestQueryInfo> {
const octokit = new Octokit({
auth: token,
baseUrl: process.env.GITHUB_API_URL || 'https://api.github.com'
})
const package_type: PackageType = packageType as PackageType
return from(
octokit.rest.packages.getAllPackageVersionsForPackageOwnedByUser({
package_type,
package_name: packageName,
username: owner,
per_page: numVersions,
page
})
).pipe(
map(result => {
let r: QueryInfo
if (result.repository.packages.edges.length < 1) {
console.log(
`package: ${packageName} not found for owner: ${owner} in repo: ${repo}`
)
r = {
versions: <VersionInfo[]>[],
cursor: '',
paginate: false,
totalCount: 0
}
return r
catchError(err => {
const msg = 'get versions API failed.'
return throwError(
err.errors && err.errors.length > 0
? `${msg} ${err.errors[0].message}`
: `${msg} ${err.message}`
)
}),
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,
tagged
}
}),
page,
paginate: response.data.length === numVersions,
totalCount: response.data.length
}
const versions = result.repository.packages.edges[0].node.versions.edges
const pages = result.repository.packages.edges[0].node.versions.pageInfo
const count = result.repository.packages.edges[0].node.versions.totalCount
r = {
versions: versions
.map(value => ({id: value.node.id, version: value.node.version}))
.reverse(),
cursor: pages.startCursor,
paginate: pages.hasPreviousPage,
totalCount: count
}
return r
return resp
})
)
}

View File

@@ -1,19 +0,0 @@
import {GitHub} from '@actions/github'
import {GraphQlQueryResponseData} from '@octokit/graphql/dist-types/types'
import {RequestParameters} from '@octokit/types/dist-types/RequestParameters'
/**
* Sends a GraphQL query request based on endpoint options
*
* @param {string} token Auth token
* @param {string} query GraphQL query. Example: `'query { viewer { login } }'`.
* @param {object} parameters URL, query or body parameters, as well as `headers`, `mediaType.{format|previews}`, `request`, or `baseUrl`.
*/
export async function graphql(
token: string,
query: string,
parameters: RequestParameters
): Promise<GraphQlQueryResponseData> {
const github = new GitHub(token)
return await github.graphql(query, parameters)
}