diff --git a/README.md b/README.md
index ffa908dd..36a7c732 100644
--- a/README.md
+++ b/README.md
@@ -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 packages that are hosted in the same repo that is executing the workflow
+* Delete version(s) of packages that are hosted in a different repo than the one executing the workflow
* Delete a single version
* Delete multiple versions
* Delete specific version(s)
@@ -35,9 +35,20 @@ This action deletes versions of a package from [GitHub Packages](https://github.
# Name of the package.
# Defaults to an empty string.
- # Required if `package-version-ids` input is not given.
+ # Required if `package-version-ids` or `package-names` input is not given.
package-name:
+ # Names of the package.
+ # Can be one of the following:
+ # - a single package name
+ # - a group of packages that matches a wildcard at start, end, both sides, or all packages (e.g. "package*")
+ # - a group of packages that matches a regex, must start with slash at the beginning and end (e.g. "/package.*/")
+ # - a comma separated list of the previous cases (e.g. "package-lorem, *-ipsum, /.*dolor/")
+ # Defaults to an empty string.
+ # Required if `package-version-ids` or `package-name` input is not given.
+ package-names:
+
+
# The number of old versions to delete starting from the oldest version.
# Defaults to 1.
num-old-versions-to-delete:
@@ -70,7 +81,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`, `repo`, `package-name` (or `package-names`) and `token` can be used with the following combinations in a workflow -
- `num-old-versions-to-delete`
- `min-versions-to-keep`
@@ -86,12 +97,13 @@ This action deletes versions of a package from [GitHub Packages](https://github.
- [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 all except y latest versions of all packages in a repo](#delete-all-except-y-latest-versions-of-all-packages-in-a-repo)
- [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 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.
@@ -241,6 +253,41 @@ 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 all packages hosted in the same repo as the workflow the __package-names__ 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
+
+ ```yaml
+ - uses: actions/delete-package-versions@v3
+ with:
+ package-names: '*'
+ min-versions-to-keep: 2
+ ```
+
+ To delete all except y latest versions of all packages hosted in a repo other than the workflow the __owner__, __repo__, __package-names__, __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
+
+ ```yaml
+ - uses: actions/delete-package-versions@v3
+ with:
+ owner: 'github'
+ repo: 'packages'
+ package-names: '*'
+ token: ${{ secrets.PAT }}
+ min-versions-to-keep: 2
+ ```
+
+
+
### 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.
diff --git a/__tests__/packages/get-packages.test.ts b/__tests__/packages/get-packages.test.ts
new file mode 100644
index 00000000..27672257
--- /dev/null
+++ b/__tests__/packages/get-packages.test.ts
@@ -0,0 +1,66 @@
+import {mockPackagesQueryResponse} from './graphql.mock'
+import {
+ getRepoPackages as _getRepoPackages,
+ QueryInfo
+} from '../../src/packages'
+import {Observable} from 'rxjs'
+
+describe.skip('get versions tests -- call graphql', () => {
+ it('getRepoPackages -- succeeds', done => {
+ const numPackages = 1
+ getRepoPackages({numPackages}).subscribe(result => {
+ expect(result.packages.length).toBe(numPackages)
+ done()
+ })
+ })
+
+ it('getRepoPackages -- fails for invalid repo', done => {
+ getRepoPackages({repo: 'actions-testin'}).subscribe({
+ error: err => {
+ expect(err).toBeTruthy()
+ done()
+ },
+ complete: async () => done.fail('no error thrown')
+ })
+ })
+ })
+
+ describe('get versions tests -- mock graphql', () => {
+ it('getRepoPackages -- success', done => {
+ const numPackages = 5
+ mockPackagesQueryResponse(numPackages)
+
+ getRepoPackages({numPackages}).subscribe(result => {
+ expect(result.packages.length).toBe(numPackages)
+ done()
+ })
+ })
+ })
+
+ interface Params {
+ owner?: string
+ repo?: string
+ numPackages?: number
+ startCursor?: string
+ token?: string
+ }
+
+ const defaultParams = {
+ owner: 'namratajha',
+ repo: 'test-repo',
+ packageName: 'test-repo',
+ numPackages: 1,
+ startCursor: '',
+ token: process.env.GITHUB_TOKEN as string
+ }
+
+ function getRepoPackages(params?: Params): Observable {
+ const p: Required = {...defaultParams, ...params}
+ return _getRepoPackages(
+ p.owner,
+ p.repo,
+ p.numPackages,
+ p.startCursor,
+ p.token
+ )
+ }
\ No newline at end of file
diff --git a/__tests__/packages/graphql.mock.ts b/__tests__/packages/graphql.mock.ts
new file mode 100644
index 00000000..181434c7
--- /dev/null
+++ b/__tests__/packages/graphql.mock.ts
@@ -0,0 +1,44 @@
+import {
+ GraphQlQueryResponseData,
+ RequestParameters
+} from '@octokit/graphql/dist-types/types'
+
+import * as Graphql from '../../src/common/graphql'
+import {GetPackagesQueryResponse} from '../../src/packages'
+
+export function getMockedPackagesQueryResponse(
+ numPackages: number
+): GetPackagesQueryResponse {
+ const packages = []
+ for (let i = 1; i <= numPackages; ++i) {
+ packages.push({
+ node: {
+ id: i.toString(),
+ name: `package${i}`
+ }
+ })
+ }
+
+ return {
+ repository: {
+ packages: {
+ pageInfo: {
+ endCursor: 'AAA',
+ hasNextPage: false
+ },
+ edges: packages
+ }
+ }
+ }
+}
+
+export function mockPackagesQueryResponse(numVersions: number): void {
+ const response = new Promise(resolve => {
+ resolve(getMockedPackagesQueryResponse(numVersions))
+ }) as Promise
+ jest
+ .spyOn(Graphql, 'graphql')
+ .mockImplementation(
+ (token: string, query: string, parameters: RequestParameters) => response
+ )
+}
diff --git a/__tests__/packages/package-name-filter.test.ts b/__tests__/packages/package-name-filter.test.ts
new file mode 100644
index 00000000..781d7c74
--- /dev/null
+++ b/__tests__/packages/package-name-filter.test.ts
@@ -0,0 +1,114 @@
+import { getPackageNameFilter } from '../../src/packages'
+
+describe('package name filter -- create filter', () => {
+
+ const packageNameList = [
+ 'com.company.project.module1.package1',
+ 'com.company.project.module1.package2',
+ 'com.company.project.module2.package1',
+ 'com.company.project.module2.package2',
+ 'com.company.project.module3.package-name-lorem',
+ 'com.company.project.module3.package-name-ipsum',
+ 'com.company.project.module3.package-name-dolor',
+ ]
+
+ it('getPackageNameFilter -- wildcard end filter', done => {
+ const filter = getPackageNameFilter('com.company.project.module1.*')
+
+ const result = packageNameList.filter(filter.apply);
+
+ expect(filter.subfilters[0].type).toBe('wildcard')
+ expect(result).toEqual([
+ 'com.company.project.module1.package1',
+ 'com.company.project.module1.package2',
+ ])
+ done()
+ })
+
+ it('getPackageNameFilter -- wildcard start filter', done => {
+ const filter = getPackageNameFilter('*.package1')
+
+ const result = packageNameList.filter(filter.apply);
+
+ expect(filter.subfilters[0].type).toBe('wildcard')
+ expect(result).toEqual([
+ 'com.company.project.module1.package1',
+ 'com.company.project.module2.package1',
+ ])
+ done()
+ })
+
+ it('getPackageNameFilter -- wildcard both sides filter', done => {
+ const filter = getPackageNameFilter('*.project.module3.*')
+
+ const result = packageNameList.filter(filter.apply);
+
+ expect(filter.subfilters[0].type).toBe('wildcard')
+ expect(result).toEqual([
+ 'com.company.project.module3.package-name-lorem',
+ 'com.company.project.module3.package-name-ipsum',
+ 'com.company.project.module3.package-name-dolor'
+ ])
+ done()
+ })
+
+
+ it('getPackageNameFilter -- wildcard all filter', done => {
+ const filter = getPackageNameFilter('*')
+
+ const result = packageNameList.filter(filter.apply);
+
+ expect(filter.subfilters[0].type).toBe('wildcard')
+ expect(result).toEqual(packageNameList.slice())
+ done()
+ })
+
+ it('getPackageNameFilter -- regex filter', done => {
+ const filter = getPackageNameFilter('/com\\.company\\.project\\.module.*\\.package1/')
+ const result = packageNameList.filter(filter.apply);
+
+ expect(filter.subfilters[0].type).toBe('regex')
+ expect(result).toEqual([
+ 'com.company.project.module1.package1',
+ 'com.company.project.module2.package1',
+ ])
+ done()
+ })
+
+ it('getPackageNameFilter -- exact match filter', done => {
+ const filter = getPackageNameFilter('com.company.project.module1.package1')
+ const result = packageNameList.filter(filter.apply);
+
+ expect(filter.subfilters[0].type).toBe('string')
+ expect(result).toEqual(['com.company.project.module1.package1'])
+ done()
+ })
+
+
+ it('getPackageNameFilter -- multiple filters', done => {
+ const filter = getPackageNameFilter('com.company.project.module1.package1, com.company.project.module2.*, /.*module3.*-ipsum/')
+ const result = packageNameList.filter(filter.apply);
+
+ expect(filter.subfilters.length).toBe(3)
+ expect(filter.subfilters[0].type).toBe('string')
+ expect(filter.subfilters[1].type).toBe('wildcard')
+ expect(filter.subfilters[2].type).toBe('regex')
+ expect(result).toEqual([
+ 'com.company.project.module1.package1',
+ 'com.company.project.module2.package1',
+ 'com.company.project.module2.package2',
+ 'com.company.project.module3.package-name-ipsum'
+ ])
+ done()
+ })
+
+
+ it('getPackageNameFilter -- memoization, same input shoud return same output', done => {
+ const filterText = 'com.company.project.module1.package1, com.company.project.module2.*, /.*module3.*-dolor/, *.-lorem'
+ const filter1 = getPackageNameFilter(filterText)
+ const filter2 = getPackageNameFilter(filterText)
+ expect(filter1).toBe(filter2)
+ expect(filter1.subfilters.length).toBe(4)
+ done()
+ })
+ })
\ No newline at end of file
diff --git a/__tests__/version/graphql.mock.ts b/__tests__/version/graphql.mock.ts
index c2fb04e7..0e47abe7 100644
--- a/__tests__/version/graphql.mock.ts
+++ b/__tests__/version/graphql.mock.ts
@@ -3,7 +3,7 @@ import {
RequestParameters
} from '@octokit/graphql/dist-types/types'
-import * as Graphql from '../../src/version/graphql'
+import * as Graphql from '../../src/common/graphql'
import {GetVersionsQueryResponse} from '../../src/version'
export function getMockedOldestQueryResponse(
diff --git a/action.yml b/action.yml
index 519a3b8f..2837de93 100644
--- a/action.yml
+++ b/action.yml
@@ -27,6 +27,19 @@ inputs:
Required if dynamically deleting oldest versions.
required: false
+
+ package-names:
+ description: >
+ Names of the package.
+ Can be one of the following:
+ - a single package name
+ - a group of packages that matches a wildcard at start, end, both sides, or all packages (e.g. "package*")
+ - a group of packages that matches a regex, must start with slash at the beginning and end (e.g. "/package.*/")
+ - a comma separated list of the previous cases (e.g. "package-lorem, *-ipsum, /.*dolor/")
+ Defaults to an empty string.
+ Required if `package-version-ids` or `package-name` input is not given.
+ required: false
+
num-old-versions-to-delete:
description: >
Number of versions to delete starting with the oldest version.
diff --git a/dist/index.js b/dist/index.js
index ee3c486f..79fbfcc6 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -5,7 +5,43 @@
/***/ ((module) => {
"use strict";
-module.exports = JSON.parse('{"_args":[["@octokit/rest@16.43.2","/workspaces/delete-package-versions"]],"_from":"@octokit/rest@16.43.2","_id":"@octokit/rest@16.43.2","_inBundle":false,"_integrity":"sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ==","_location":"/@octokit/rest","_phantomChildren":{"deprecation":"2.3.1","once":"1.4.0","os-name":"3.1.0"},"_requested":{"type":"version","registry":true,"raw":"@octokit/rest@16.43.2","name":"@octokit/rest","escapedName":"@octokit%2frest","scope":"@octokit","rawSpec":"16.43.2","saveSpec":null,"fetchSpec":"16.43.2"},"_requiredBy":["/@actions/github"],"_resolved":"https://registry.npmjs.org/@octokit/rest/-/rest-16.43.2.tgz","_spec":"16.43.2","_where":"/workspaces/delete-package-versions","author":{"name":"Gregor Martynus","url":"https://github.com/gr2m"},"bugs":{"url":"https://github.com/octokit/rest.js/issues"},"bundlesize":[{"path":"./dist/octokit-rest.min.js.gz","maxSize":"33 kB"}],"contributors":[{"name":"Mike de Boer","email":"info@mikedeboer.nl"},{"name":"Fabian Jakobs","email":"fabian@c9.io"},{"name":"Joe Gallo","email":"joe@brassafrax.com"},{"name":"Gregor Martynus","url":"https://github.com/gr2m"}],"dependencies":{"@octokit/auth-token":"^2.4.0","@octokit/plugin-paginate-rest":"^1.1.1","@octokit/plugin-request-log":"^1.0.0","@octokit/plugin-rest-endpoint-methods":"2.4.0","@octokit/request":"^5.2.0","@octokit/request-error":"^1.0.2","atob-lite":"^2.0.0","before-after-hook":"^2.0.0","btoa-lite":"^1.0.0","deprecation":"^2.0.0","lodash.get":"^4.4.2","lodash.set":"^4.3.2","lodash.uniq":"^4.5.0","octokit-pagination-methods":"^1.1.0","once":"^1.4.0","universal-user-agent":"^4.0.0"},"description":"GitHub REST API client for Node.js","devDependencies":{"@gimenete/type-writer":"^0.1.3","@octokit/auth":"^1.1.1","@octokit/fixtures-server":"^5.0.6","@octokit/graphql":"^4.2.0","@types/node":"^13.1.0","bundlesize":"^0.18.0","chai":"^4.1.2","compression-webpack-plugin":"^3.1.0","cypress":"^4.0.0","glob":"^7.1.2","http-proxy-agent":"^4.0.0","lodash.camelcase":"^4.3.0","lodash.merge":"^4.6.1","lodash.upperfirst":"^4.3.1","lolex":"^6.0.0","mkdirp":"^1.0.0","mocha":"^7.0.1","mustache":"^4.0.0","nock":"^11.3.3","npm-run-all":"^4.1.2","nyc":"^15.0.0","prettier":"^1.14.2","proxy":"^1.0.0","semantic-release":"^17.0.0","sinon":"^8.0.0","sinon-chai":"^3.0.0","sort-keys":"^4.0.0","string-to-arraybuffer":"^1.0.0","string-to-jsdoc-comment":"^1.0.0","typescript":"^3.3.1","webpack":"^4.0.0","webpack-bundle-analyzer":"^3.0.0","webpack-cli":"^3.0.0"},"files":["index.js","index.d.ts","lib","plugins"],"homepage":"https://github.com/octokit/rest.js#readme","keywords":["octokit","github","rest","api-client"],"license":"MIT","name":"@octokit/rest","nyc":{"ignore":["test"]},"publishConfig":{"access":"public"},"release":{"publish":["@semantic-release/npm",{"path":"@semantic-release/github","assets":["dist/*","!dist/*.map.gz"]}]},"repository":{"type":"git","url":"git+https://github.com/octokit/rest.js.git"},"scripts":{"build":"npm-run-all build:*","build:browser":"npm-run-all build:browser:*","build:browser:development":"webpack --mode development --entry . --output-library=Octokit --output=./dist/octokit-rest.js --profile --json > dist/bundle-stats.json","build:browser:production":"webpack --mode production --entry . --plugin=compression-webpack-plugin --output-library=Octokit --output-path=./dist --output-filename=octokit-rest.min.js --devtool source-map","build:ts":"npm run -s update-endpoints:typescript","coverage":"nyc report --reporter=html && open coverage/index.html","generate-bundle-report":"webpack-bundle-analyzer dist/bundle-stats.json --mode=static --no-open --report dist/bundle-report.html","lint":"prettier --check \'{lib,plugins,scripts,test}/**/*.{js,json,ts}\' \'docs/*.{js,json}\' \'docs/src/**/*\' index.js README.md package.json","lint:fix":"prettier --write \'{lib,plugins,scripts,test}/**/*.{js,json,ts}\' \'docs/*.{js,json}\' \'docs/src/**/*\' index.js README.md package.json","postvalidate:ts":"tsc --noEmit --target es6 test/typescript-validate.ts","prebuild:browser":"mkdirp dist/","pretest":"npm run -s lint","prevalidate:ts":"npm run -s build:ts","start-fixtures-server":"octokit-fixtures-server","test":"nyc mocha test/mocha-node-setup.js \\"test/*/**/*-test.js\\"","test:browser":"cypress run --browser chrome","update-endpoints":"npm-run-all update-endpoints:*","update-endpoints:fetch-json":"node scripts/update-endpoints/fetch-json","update-endpoints:typescript":"node scripts/update-endpoints/typescript","validate:ts":"tsc --target es6 --noImplicitAny index.d.ts"},"types":"index.d.ts","version":"16.43.2"}');
+module.exports = JSON.parse('{"_args":[["@octokit/rest@16.43.2","/home/omar/workspace/git/delete-package-versions"]],"_from":"@octokit/rest@16.43.2","_id":"@octokit/rest@16.43.2","_inBundle":false,"_integrity":"sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ==","_location":"/@octokit/rest","_phantomChildren":{"deprecation":"2.3.1","once":"1.4.0","os-name":"3.1.0"},"_requested":{"type":"version","registry":true,"raw":"@octokit/rest@16.43.2","name":"@octokit/rest","escapedName":"@octokit%2frest","scope":"@octokit","rawSpec":"16.43.2","saveSpec":null,"fetchSpec":"16.43.2"},"_requiredBy":["/@actions/github"],"_resolved":"https://registry.npmjs.org/@octokit/rest/-/rest-16.43.2.tgz","_spec":"16.43.2","_where":"/home/omar/workspace/git/delete-package-versions","author":{"name":"Gregor Martynus","url":"https://github.com/gr2m"},"bugs":{"url":"https://github.com/octokit/rest.js/issues"},"bundlesize":[{"path":"./dist/octokit-rest.min.js.gz","maxSize":"33 kB"}],"contributors":[{"name":"Mike de Boer","email":"info@mikedeboer.nl"},{"name":"Fabian Jakobs","email":"fabian@c9.io"},{"name":"Joe Gallo","email":"joe@brassafrax.com"},{"name":"Gregor Martynus","url":"https://github.com/gr2m"}],"dependencies":{"@octokit/auth-token":"^2.4.0","@octokit/plugin-paginate-rest":"^1.1.1","@octokit/plugin-request-log":"^1.0.0","@octokit/plugin-rest-endpoint-methods":"2.4.0","@octokit/request":"^5.2.0","@octokit/request-error":"^1.0.2","atob-lite":"^2.0.0","before-after-hook":"^2.0.0","btoa-lite":"^1.0.0","deprecation":"^2.0.0","lodash.get":"^4.4.2","lodash.set":"^4.3.2","lodash.uniq":"^4.5.0","octokit-pagination-methods":"^1.1.0","once":"^1.4.0","universal-user-agent":"^4.0.0"},"description":"GitHub REST API client for Node.js","devDependencies":{"@gimenete/type-writer":"^0.1.3","@octokit/auth":"^1.1.1","@octokit/fixtures-server":"^5.0.6","@octokit/graphql":"^4.2.0","@types/node":"^13.1.0","bundlesize":"^0.18.0","chai":"^4.1.2","compression-webpack-plugin":"^3.1.0","cypress":"^4.0.0","glob":"^7.1.2","http-proxy-agent":"^4.0.0","lodash.camelcase":"^4.3.0","lodash.merge":"^4.6.1","lodash.upperfirst":"^4.3.1","lolex":"^6.0.0","mkdirp":"^1.0.0","mocha":"^7.0.1","mustache":"^4.0.0","nock":"^11.3.3","npm-run-all":"^4.1.2","nyc":"^15.0.0","prettier":"^1.14.2","proxy":"^1.0.0","semantic-release":"^17.0.0","sinon":"^8.0.0","sinon-chai":"^3.0.0","sort-keys":"^4.0.0","string-to-arraybuffer":"^1.0.0","string-to-jsdoc-comment":"^1.0.0","typescript":"^3.3.1","webpack":"^4.0.0","webpack-bundle-analyzer":"^3.0.0","webpack-cli":"^3.0.0"},"files":["index.js","index.d.ts","lib","plugins"],"homepage":"https://github.com/octokit/rest.js#readme","keywords":["octokit","github","rest","api-client"],"license":"MIT","name":"@octokit/rest","nyc":{"ignore":["test"]},"publishConfig":{"access":"public"},"release":{"publish":["@semantic-release/npm",{"path":"@semantic-release/github","assets":["dist/*","!dist/*.map.gz"]}]},"repository":{"type":"git","url":"git+https://github.com/octokit/rest.js.git"},"scripts":{"build":"npm-run-all build:*","build:browser":"npm-run-all build:browser:*","build:browser:development":"webpack --mode development --entry . --output-library=Octokit --output=./dist/octokit-rest.js --profile --json > dist/bundle-stats.json","build:browser:production":"webpack --mode production --entry . --plugin=compression-webpack-plugin --output-library=Octokit --output-path=./dist --output-filename=octokit-rest.min.js --devtool source-map","build:ts":"npm run -s update-endpoints:typescript","coverage":"nyc report --reporter=html && open coverage/index.html","generate-bundle-report":"webpack-bundle-analyzer dist/bundle-stats.json --mode=static --no-open --report dist/bundle-report.html","lint":"prettier --check \'{lib,plugins,scripts,test}/**/*.{js,json,ts}\' \'docs/*.{js,json}\' \'docs/src/**/*\' index.js README.md package.json","lint:fix":"prettier --write \'{lib,plugins,scripts,test}/**/*.{js,json,ts}\' \'docs/*.{js,json}\' \'docs/src/**/*\' index.js README.md package.json","postvalidate:ts":"tsc --noEmit --target es6 test/typescript-validate.ts","prebuild:browser":"mkdirp dist/","pretest":"npm run -s lint","prevalidate:ts":"npm run -s build:ts","start-fixtures-server":"octokit-fixtures-server","test":"nyc mocha test/mocha-node-setup.js \\"test/*/**/*-test.js\\"","test:browser":"cypress run --browser chrome","update-endpoints":"npm-run-all update-endpoints:*","update-endpoints:fetch-json":"node scripts/update-endpoints/fetch-json","update-endpoints:typescript":"node scripts/update-endpoints/typescript","validate:ts":"tsc --target es6 --noImplicitAny index.d.ts"},"types":"index.d.ts","version":"16.43.2"}');
+
+/***/ }),
+
+/***/ 9057:
+/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
+
+"use strict";
+
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.graphql = void 0;
+/* eslint-disable @typescript-eslint/no-unused-vars */
+const github_1 = __nccwpck_require__(5438);
+/**
+ * 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`.
+ */
+function graphql(token, query, parameters) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const github = new github_1.GitHub(token);
+ return yield github.graphql(query, parameters);
+ });
+}
+exports.graphql = graphql;
+
/***/ }),
@@ -15,9 +51,11 @@ module.exports = JSON.parse('{"_args":[["@octokit/rest@16.43.2","/workspaces/del
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.deleteVersions = exports.finalIds = exports.getVersionIds = void 0;
+exports.deleteVersions = exports.finalIds = exports.getPackageNames = exports.getVersionIds = void 0;
+const input_1 = __nccwpck_require__(8657);
const rxjs_1 = __nccwpck_require__(5805);
const version_1 = __nccwpck_require__(4428);
+const packages_1 = __nccwpck_require__(221);
const operators_1 = __nccwpck_require__(7801);
const RATE_LIMIT = 99;
let totalCount = 0;
@@ -27,11 +65,27 @@ function getVersionIds(owner, repo, packageName, numVersions, cursor, token) {
: rxjs_1.EMPTY), operators_1.tap(value => (totalCount = totalCount === 0 ? value.totalCount : totalCount)), operators_1.map(value => value.versions));
}
exports.getVersionIds = getVersionIds;
+function getPackageNames(owner, repo, numPackages, cursor, token) {
+ return packages_1.getRepoPackages(owner, repo, numPackages, cursor, token).pipe(operators_1.expand(value => value.paginate
+ ? packages_1.getRepoPackages(owner, repo, numPackages, value.cursor, token)
+ : rxjs_1.EMPTY), operators_1.map(value => value.packages));
+}
+exports.getPackageNames = getPackageNames;
function finalIds(input) {
if (input.packageVersionIds.length > 0) {
return rxjs_1.of(input.packageVersionIds);
}
if (input.hasOldestVersionQueryInfo()) {
+ const filter = packages_1.getPackageNameFilter(input.packageNames);
+ if (!filter.isEmpty) {
+ return getPackageNames(input.owner, input.repo, RATE_LIMIT, '', input.token)
+ .pipe(operators_1.mergeMap(value => {
+ return value
+ .filter(info => filter.apply(info.name))
+ .map(info => finalIds(new input_1.Input(Object.assign(Object.assign({}, input), { packageNames: '', packageName: info.name }))));
+ }))
+ .pipe(operators_1.mergeMap(val => val));
+ }
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
@@ -131,6 +185,7 @@ const defaultParams = {
owner: '',
repo: '',
packageName: '',
+ packageNames: '',
numOldVersionsToDelete: 0,
minVersionsToKeep: 0,
ignoreVersions: new RegExp(''),
@@ -144,6 +199,7 @@ class Input {
this.owner = validatedParams.owner;
this.repo = validatedParams.repo;
this.packageName = validatedParams.packageName;
+ this.packageNames = validatedParams.packageNames;
this.numOldVersionsToDelete = validatedParams.numOldVersionsToDelete;
this.minVersionsToKeep = validatedParams.minVersionsToKeep;
this.ignoreVersions = validatedParams.ignoreVersions;
@@ -154,7 +210,7 @@ class Input {
hasOldestVersionQueryInfo() {
return !!(this.owner &&
this.repo &&
- this.packageName &&
+ (this.packageName || this.packageNames) &&
this.numOldVersionsToDelete >= 0 &&
this.token);
}
@@ -177,6 +233,245 @@ class Input {
exports.Input = Input;
+/***/ }),
+
+/***/ 6436:
+/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.getRepoPackages = exports.queryForRepoPackages = void 0;
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const rxjs_1 = __nccwpck_require__(5805);
+const operators_1 = __nccwpck_require__(7801);
+const graphql_1 = __nccwpck_require__(9057);
+const query = `
+ query getPackages($owner: String!, $repo: String!, $first: Int!){
+ repository(owner: $owner, name: $repo) {
+ packages(first:$first){
+ edges {
+ node {
+ name
+ id
+ }
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
+ }`;
+const Paginatequery = `
+ query getPackages($owner: String!, $repo: String!, $first: Int!, $after: String!){
+ repository(owner: $owner, name: $repo) {
+ packages(first:$first){
+ edges {
+ node {
+ name
+ id
+ }
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
+ }`;
+function queryForRepoPackages(owner, repo, numPackages, startCursor, token) {
+ if (startCursor === '') {
+ return rxjs_1.from(graphql_1.graphql(token, query, {
+ owner,
+ repo,
+ first: numPackages,
+ headers: {
+ Accept: 'application/vnd.github.packages-preview+json'
+ }
+ })).pipe(operators_1.catchError((err) => {
+ const msg = 'query for packages failed.';
+ return rxjs_1.throwError(err.errors && err.errors.length > 0
+ ? `${msg} ${err.errors[0].message}`
+ : `${msg} verify input parameters are correct ${JSON.stringify(err, null, 2)}`);
+ }));
+ }
+ else {
+ return rxjs_1.from(graphql_1.graphql(token, Paginatequery, {
+ owner,
+ repo,
+ first: numPackages,
+ before: startCursor,
+ headers: {
+ Accept: 'application/vnd.github.packages-preview+json'
+ }
+ })).pipe(operators_1.catchError((err) => {
+ const msg = 'query for packages failed.';
+ return rxjs_1.throwError(err.errors && err.errors.length > 0
+ ? `${msg} ${err.errors[0].message}`
+ : `${msg} verify input parameters are correct`);
+ }));
+ }
+}
+exports.queryForRepoPackages = queryForRepoPackages;
+function getRepoPackages(owner, repo, numPackages, startCursor, token) {
+ return queryForRepoPackages(owner, repo, numPackages, startCursor, token).pipe(operators_1.map(result => {
+ let r;
+ if (result.repository.packages.edges.length < 1) {
+ console.log(`package: No packages found for owner: ${owner} in repo: ${repo}`);
+ r = {
+ packages: [],
+ cursor: '',
+ paginate: false
+ };
+ return r;
+ }
+ const packages = result.repository.packages.edges;
+ const pages = result.repository.packages.pageInfo;
+ r = {
+ packages: packages.map(value => ({
+ id: value.node.id,
+ name: value.node.name
+ })),
+ cursor: pages.endCursor,
+ paginate: pages.hasNextPage
+ };
+ return r;
+ }));
+}
+exports.getRepoPackages = getRepoPackages;
+
+
+/***/ }),
+
+/***/ 221:
+/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
+
+"use strict";
+
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+__exportStar(__nccwpck_require__(6436), exports);
+__exportStar(__nccwpck_require__(2539), exports);
+
+
+/***/ }),
+
+/***/ 2539:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.getPackageNameFilter = void 0;
+/**
+ * Used to apply memoization on getPackageNameFilter
+ */
+const resultCache = {};
+/**
+ * Get a filter based on package names to match
+ *
+ * @param packageNames - serialized package names filter as string
+ * @returns the respective package filter
+ */
+function getPackageNameFilter(packageNames) {
+ if (resultCache[packageNames]) {
+ return resultCache[packageNames];
+ }
+ const result = calculatePackageNameFilter(packageNames);
+ resultCache[packageNames] = result;
+ return result;
+}
+exports.getPackageNameFilter = getPackageNameFilter;
+const emptyFilter = Object.freeze({
+ subfilters: Object.freeze([]),
+ isEmpty: true,
+ apply: () => false
+});
+/**
+ * Generates a filter based package names to match
+ *
+ * @param packageNames - serialized package names filter as string
+ * @returns the respective package filter
+ */
+function calculatePackageNameFilter(packageNames) {
+ if (packageNames === '') {
+ return emptyFilter;
+ }
+ const separatedPackageNames = packageNames
+ .split(',')
+ .map(name => name.trim())
+ .filter(name => name !== '');
+ if (separatedPackageNames.length <= 0) {
+ return emptyFilter;
+ }
+ const subfilters = separatedPackageNames.map(createFilter);
+ return {
+ subfilters,
+ isEmpty: subfilters.length <= 0,
+ apply: names => subfilters.some(filter => filter.apply(names))
+ };
+}
+function createFilter(packageName) {
+ if (packageName.startsWith('*') || packageName.endsWith('*')) {
+ return createWildcardFilter(packageName);
+ }
+ else if (packageName.startsWith('/') && packageName.endsWith('/')) {
+ return createRegexFilter(packageName);
+ }
+ else {
+ return createExactMatchFilter(packageName);
+ }
+}
+function createWildcardFilter(wildcardPackageName) {
+ const startsWithWildCard = wildcardPackageName.startsWith('*');
+ const endsWithWildCard = wildcardPackageName.endsWith('*');
+ let fn;
+ if (wildcardPackageName === '*') {
+ fn = () => true;
+ }
+ else if (startsWithWildCard && endsWithWildCard) {
+ const targetText = wildcardPackageName.substring(1, wildcardPackageName.length - 1);
+ fn = (packageName) => packageName.includes(targetText);
+ }
+ else if (startsWithWildCard) {
+ const targetText = wildcardPackageName.substring(1);
+ fn = (packageName) => packageName.endsWith(targetText);
+ }
+ else {
+ const targetText = wildcardPackageName.substring(0, wildcardPackageName.length - 1);
+ fn = (packageName) => packageName.startsWith(targetText);
+ }
+ return {
+ type: 'wildcard',
+ apply: fn
+ };
+}
+function createRegexFilter(regexPackageName) {
+ const regexPattern = regexPackageName.substring(1, regexPackageName.length - 1);
+ const regex = new RegExp(regexPattern);
+ return {
+ type: 'regex',
+ apply: (packageName) => regex.test(packageName)
+ };
+}
+function createExactMatchFilter(matchingPackageName) {
+ return {
+ type: 'string',
+ apply: (packageName) => packageName === matchingPackageName
+ };
+}
+
+
/***/ }),
/***/ 5544:
@@ -189,7 +484,7 @@ exports.deletePackageVersions = exports.deletePackageVersion = void 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const rxjs_1 = __nccwpck_require__(5805);
const operators_1 = __nccwpck_require__(7801);
-const graphql_1 = __nccwpck_require__(6320);
+const graphql_1 = __nccwpck_require__(9057);
let deleted = 0;
const mutation = `
mutation deletePackageVersion($packageVersionId: ID!) {
@@ -239,7 +534,7 @@ exports.getOldestVersions = exports.queryForOldestVersions = void 0;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const rxjs_1 = __nccwpck_require__(5805);
const operators_1 = __nccwpck_require__(7801);
-const graphql_1 = __nccwpck_require__(6320);
+const graphql_1 = __nccwpck_require__(9057);
const query = `
query getVersions($owner: String!, $repo: String!, $package: String!, $last: Int!) {
repository(owner: $owner, name: $repo) {
@@ -356,42 +651,6 @@ function getOldestVersions(owner, repo, packageName, numVersions, startCursor, t
exports.getOldestVersions = getOldestVersions;
-/***/ }),
-
-/***/ 6320:
-/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
-
-"use strict";
-
-var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
-};
-Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.graphql = void 0;
-/* eslint-disable @typescript-eslint/no-unused-vars */
-const github_1 = __nccwpck_require__(5438);
-/**
- * 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`.
- */
-function graphql(token, query, parameters) {
- return __awaiter(this, void 0, void 0, function* () {
- const github = new github_1.GitHub(token);
- return yield github.graphql(query, parameters);
- });
-}
-exports.graphql = graphql;
-
-
/***/ }),
/***/ 4428:
@@ -40071,6 +40330,7 @@ function getActionInput() {
owner: core_1.getInput('owner') ? core_1.getInput('owner') : github_1.context.repo.owner,
repo: core_1.getInput('repo') ? core_1.getInput('repo') : github_1.context.repo.repo,
packageName: core_1.getInput('package-name'),
+ packageNames: core_1.getInput('package-names'),
numOldVersionsToDelete: Number(core_1.getInput('num-old-versions-to-delete')),
minVersionsToKeep: Number(core_1.getInput('min-versions-to-keep')),
ignoreVersions: RegExp(core_1.getInput('ignore-versions')),
diff --git a/src/version/graphql.ts b/src/common/graphql.ts
similarity index 100%
rename from src/version/graphql.ts
rename to src/common/graphql.ts
diff --git a/src/delete.ts b/src/delete.ts
index c2f9c3c6..05c18990 100644
--- a/src/delete.ts
+++ b/src/delete.ts
@@ -1,7 +1,8 @@
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 {getRepoPackages, getPackageNameFilter, PackageInfo} from './packages'
+import {concatMap, map, mergeMap, expand, tap} from 'rxjs/operators'
const RATE_LIMIT = 99
let totalCount = 0
@@ -41,11 +42,55 @@ export function getVersionIds(
)
}
+export function getPackageNames(
+ owner: string,
+ repo: string,
+ numPackages: number,
+ cursor: string,
+ token: string
+): Observable {
+ return getRepoPackages(owner, repo, numPackages, cursor, token).pipe(
+ expand(value =>
+ value.paginate
+ ? getRepoPackages(owner, repo, numPackages, value.cursor, token)
+ : EMPTY
+ ),
+ map(value => value.packages)
+ )
+}
+
export function finalIds(input: Input): Observable {
if (input.packageVersionIds.length > 0) {
return of(input.packageVersionIds)
}
if (input.hasOldestVersionQueryInfo()) {
+ const filter = getPackageNameFilter(input.packageNames)
+ if (!filter.isEmpty) {
+ return getPackageNames(
+ input.owner,
+ input.repo,
+ RATE_LIMIT,
+ '',
+ input.token
+ )
+ .pipe(
+ mergeMap(value => {
+ return value
+ .filter(info => filter.apply(info.name))
+ .map(info =>
+ finalIds(
+ new Input({
+ ...input,
+ packageNames: '',
+ packageName: info.name
+ })
+ )
+ )
+ })
+ )
+ .pipe(mergeMap(val => val))
+ }
+
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
diff --git a/src/input.ts b/src/input.ts
index 35244d52..aa349821 100644
--- a/src/input.ts
+++ b/src/input.ts
@@ -3,6 +3,7 @@ export interface InputParams {
owner?: string
repo?: string
packageName?: string
+ packageNames?: string
numOldVersionsToDelete?: number
minVersionsToKeep?: number
ignoreVersions?: RegExp
@@ -15,6 +16,7 @@ const defaultParams = {
owner: '',
repo: '',
packageName: '',
+ packageNames: '',
numOldVersionsToDelete: 0,
minVersionsToKeep: 0,
ignoreVersions: new RegExp(''),
@@ -27,6 +29,7 @@ export class Input {
owner: string
repo: string
packageName: string
+ packageNames: string
numOldVersionsToDelete: number
minVersionsToKeep: number
ignoreVersions: RegExp
@@ -41,6 +44,7 @@ export class Input {
this.owner = validatedParams.owner
this.repo = validatedParams.repo
this.packageName = validatedParams.packageName
+ this.packageNames = validatedParams.packageNames
this.numOldVersionsToDelete = validatedParams.numOldVersionsToDelete
this.minVersionsToKeep = validatedParams.minVersionsToKeep
this.ignoreVersions = validatedParams.ignoreVersions
@@ -53,7 +57,7 @@ export class Input {
return !!(
this.owner &&
this.repo &&
- this.packageName &&
+ (this.packageName || this.packageNames) &&
this.numOldVersionsToDelete >= 0 &&
this.token
)
diff --git a/src/main.ts b/src/main.ts
index 2905ad0e..8a88d1c1 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -13,6 +13,7 @@ function getActionInput(): Input {
owner: getInput('owner') ? getInput('owner') : context.repo.owner,
repo: getInput('repo') ? getInput('repo') : context.repo.repo,
packageName: getInput('package-name'),
+ packageNames: getInput('package-names'),
numOldVersionsToDelete: Number(getInput('num-old-versions-to-delete')),
minVersionsToKeep: Number(getInput('min-versions-to-keep')),
ignoreVersions: RegExp(getInput('ignore-versions')),
diff --git a/src/packages/get-packages.ts b/src/packages/get-packages.ts
new file mode 100644
index 00000000..d714ab63
--- /dev/null
+++ b/src/packages/get-packages.ts
@@ -0,0 +1,165 @@
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+import {GraphQlQueryResponse} from '@octokit/graphql/dist-types/types'
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+import {Observable, from, throwError} from 'rxjs'
+import {catchError, map} from 'rxjs/operators'
+import {graphql} from '../common/graphql'
+
+export interface PackageInfo {
+ id: string
+ name: string
+}
+
+export interface QueryInfo {
+ packages: PackageInfo[]
+ cursor: string
+ paginate: boolean
+}
+
+export interface GetPackagesQueryResponse {
+ repository: {
+ packages: {
+ edges: {node: PackageInfo}[]
+ pageInfo: {
+ endCursor: string
+ hasNextPage: boolean
+ }
+ }
+ }
+}
+
+const query = `
+ query getPackages($owner: String!, $repo: String!, $first: Int!){
+ repository(owner: $owner, name: $repo) {
+ packages(first:$first){
+ edges {
+ node {
+ name
+ id
+ }
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
+ }`
+
+const Paginatequery = `
+ query getPackages($owner: String!, $repo: String!, $first: Int!, $after: String!){
+ repository(owner: $owner, name: $repo) {
+ packages(first:$first){
+ edges {
+ node {
+ name
+ id
+ }
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
+ }`
+
+export function queryForRepoPackages(
+ owner: string,
+ repo: string,
+ numPackages: number,
+ startCursor: string,
+ token: string
+): Observable {
+ if (startCursor === '') {
+ return from(
+ graphql(token, query, {
+ owner,
+ repo,
+ first: numPackages,
+ headers: {
+ Accept: 'application/vnd.github.packages-preview+json'
+ }
+ }) as Promise
+ ).pipe(
+ catchError((err: GraphQlQueryResponse) => {
+ const msg = 'query for packages failed.'
+ return throwError(
+ err.errors && err.errors.length > 0
+ ? `${msg} ${err.errors[0].message}`
+ : `${msg} verify input parameters are correct ${JSON.stringify(
+ err,
+ null,
+ 2
+ )}`
+ )
+ })
+ )
+ } else {
+ return from(
+ graphql(token, Paginatequery, {
+ owner,
+ repo,
+ first: numPackages,
+ before: startCursor,
+ headers: {
+ Accept: 'application/vnd.github.packages-preview+json'
+ }
+ }) as Promise
+ ).pipe(
+ catchError((err: GraphQlQueryResponse) => {
+ const msg = 'query for packages failed.'
+ return throwError(
+ err.errors && err.errors.length > 0
+ ? `${msg} ${err.errors[0].message}`
+ : `${msg} verify input parameters are correct`
+ )
+ })
+ )
+ }
+}
+
+export function getRepoPackages(
+ owner: string,
+ repo: string,
+ numPackages: number,
+ startCursor: string,
+ token: string
+): Observable {
+ return queryForRepoPackages(
+ owner,
+ repo,
+ numPackages,
+ startCursor,
+ token
+ ).pipe(
+ map(result => {
+ let r: QueryInfo
+ if (result.repository.packages.edges.length < 1) {
+ console.log(
+ `package: No packages found for owner: ${owner} in repo: ${repo}`
+ )
+ r = {
+ packages: [] as PackageInfo[],
+ cursor: '',
+ paginate: false
+ }
+ return r
+ }
+
+ const packages = result.repository.packages.edges
+ const pages = result.repository.packages.pageInfo
+
+ r = {
+ packages: packages.map(value => ({
+ id: value.node.id,
+ name: value.node.name
+ })),
+ cursor: pages.endCursor,
+ paginate: pages.hasNextPage
+ }
+
+ return r
+ })
+ )
+}
diff --git a/src/packages/index.ts b/src/packages/index.ts
new file mode 100644
index 00000000..eadf04e4
--- /dev/null
+++ b/src/packages/index.ts
@@ -0,0 +1,2 @@
+export * from './get-packages'
+export * from './package-name-filter'
diff --git a/src/packages/package-name-filter.ts b/src/packages/package-name-filter.ts
new file mode 100644
index 00000000..fba0af00
--- /dev/null
+++ b/src/packages/package-name-filter.ts
@@ -0,0 +1,126 @@
+/**
+ * Used to apply memoization on getPackageNameFilter
+ */
+const resultCache = {} as Record
+
+interface PackageNameSubFilter {
+ type: 'regex' | 'wildcard' | 'string'
+ apply: (packageName: string) => boolean
+}
+
+interface PackageNamesFilter {
+ readonly subfilters: readonly PackageNameSubFilter[]
+ readonly isEmpty: boolean
+ readonly apply: (packageName: string) => boolean
+}
+
+/**
+ * Get a filter based on package names to match
+ *
+ * @param packageNames - serialized package names filter as string
+ * @returns the respective package filter
+ */
+export function getPackageNameFilter(packageNames: string): PackageNamesFilter {
+ if (resultCache[packageNames]) {
+ return resultCache[packageNames]
+ }
+ const result = calculatePackageNameFilter(packageNames)
+ resultCache[packageNames] = result
+ return result
+}
+
+const emptyFilter = Object.freeze({
+ subfilters: Object.freeze([]),
+ isEmpty: true,
+ apply: () => false as boolean
+})
+
+/**
+ * Generates a filter based package names to match
+ *
+ * @param packageNames - serialized package names filter as string
+ * @returns the respective package filter
+ */
+function calculatePackageNameFilter(
+ packageNames: string
+): Readonly {
+ if (packageNames === '') {
+ return emptyFilter
+ }
+ const separatedPackageNames = packageNames
+ .split(',')
+ .map(name => name.trim())
+ .filter(name => name !== '')
+
+ if (separatedPackageNames.length <= 0) {
+ return emptyFilter
+ }
+
+ const subfilters = separatedPackageNames.map(createFilter)
+ return {
+ subfilters,
+ isEmpty: subfilters.length <= 0,
+ apply: names => subfilters.some(filter => filter.apply(names))
+ }
+}
+
+function createFilter(packageName: string): PackageNameSubFilter {
+ if (packageName.startsWith('*') || packageName.endsWith('*')) {
+ return createWildcardFilter(packageName)
+ } else if (packageName.startsWith('/') && packageName.endsWith('/')) {
+ return createRegexFilter(packageName)
+ } else {
+ return createExactMatchFilter(packageName)
+ }
+}
+
+function createWildcardFilter(
+ wildcardPackageName: string
+): PackageNameSubFilter {
+ const startsWithWildCard = wildcardPackageName.startsWith('*')
+ const endsWithWildCard = wildcardPackageName.endsWith('*')
+ let fn: PackageNameSubFilter['apply']
+ if (wildcardPackageName === '*') {
+ fn = () => true
+ } else if (startsWithWildCard && endsWithWildCard) {
+ const targetText = wildcardPackageName.substring(
+ 1,
+ wildcardPackageName.length - 1
+ )
+ fn = (packageName: string) => packageName.includes(targetText)
+ } else if (startsWithWildCard) {
+ const targetText = wildcardPackageName.substring(1)
+ fn = (packageName: string) => packageName.endsWith(targetText)
+ } else {
+ const targetText = wildcardPackageName.substring(
+ 0,
+ wildcardPackageName.length - 1
+ )
+ fn = (packageName: string) => packageName.startsWith(targetText)
+ }
+ return {
+ type: 'wildcard',
+ apply: fn
+ }
+}
+
+function createRegexFilter(regexPackageName: string): PackageNameSubFilter {
+ const regexPattern = regexPackageName.substring(
+ 1,
+ regexPackageName.length - 1
+ )
+ const regex = new RegExp(regexPattern)
+ return {
+ type: 'regex',
+ apply: (packageName: string) => regex.test(packageName)
+ }
+}
+
+function createExactMatchFilter(
+ matchingPackageName: string
+): PackageNameSubFilter {
+ return {
+ type: 'string',
+ apply: (packageName: string) => packageName === matchingPackageName
+ }
+}
diff --git a/src/version/delete-version.ts b/src/version/delete-version.ts
index d528ca1b..2f247e86 100644
--- a/src/version/delete-version.ts
+++ b/src/version/delete-version.ts
@@ -1,7 +1,7 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import {from, Observable, merge, throwError, of} from 'rxjs'
import {catchError, map, tap} from 'rxjs/operators'
-import {graphql} from './graphql'
+import {graphql} from '../common/graphql'
let deleted = 0
diff --git a/src/version/get-versions.ts b/src/version/get-versions.ts
index c49d57eb..8a0299b0 100644
--- a/src/version/get-versions.ts
+++ b/src/version/get-versions.ts
@@ -3,7 +3,7 @@ import {GraphQlQueryResponse} from '@octokit/graphql/dist-types/types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import {Observable, from, throwError} from 'rxjs'
import {catchError, map} from 'rxjs/operators'
-import {graphql} from './graphql'
+import {graphql} from '../common/graphql'
export interface VersionInfo {
id: string