diff --git a/README.md b/README.md index a73212ce..5b755483 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 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 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) @@ -28,13 +28,24 @@ This action deletes versions of a package from [GitHub Packages](https://github. # Required if deleting a version from a package hosted in a different org than the one executing the workflow. owner: - # Name of the package. - # Required - package-name: - # Type of the package. Can be one of container, maven, npm, nuget, or rubygems. # Required package-type: + + # Defaults to an empty string. + # 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. @@ -70,7 +81,7 @@ This action deletes versions of a package from [GitHub Packages](https://github. # Valid Input Combinations -`owner`, `package-name`, `package-type` 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` @@ -98,6 +109,7 @@ This action deletes versions of a package from [GitHub Packages](https://github. - [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, the __package-name__, __min-versions-to-keep__ and __delete-only-pre-release-versions__ inputs are required. @@ -247,6 +259,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, 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..a6601488 --- /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: any[] = [] + 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/action.yml b/action.yml index 67192dc7..1e8d1b0d 100644 --- a/action.yml +++ b/action.yml @@ -25,6 +25,19 @@ inputs: Type of package. Can be one of container, maven, npm, nuget, or rubygems. required: true + + 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/package-lock.json b/package-lock.json index 436811f8..94554553 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@actions/core": "^1.9.1", "@actions/github": "^2.1.1", "@octokit/rest": "^19.0.5", + "graphql": "^16.6.0", "rxjs": "^6.5.4" }, "devDependencies": { @@ -4131,7 +4132,6 @@ "version": "16.6.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -11008,8 +11008,7 @@ "graphql": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", - "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==", - "dev": true + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==" }, "has": { "version": "1.0.3", diff --git a/package.json b/package.json index 790ad587..4af36b70 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@actions/core": "^1.9.1", "@actions/github": "^2.1.1", "@octokit/rest": "^19.0.5", + "graphql": "^16.6.0", "rxjs": "^6.5.4" }, "devDependencies": { diff --git a/src/common/graphql.ts b/src/common/graphql.ts new file mode 100644 index 00000000..1f5d5608 --- /dev/null +++ b/src/common/graphql.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +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 { + const github = new GitHub(token) + return await github.graphql(query, parameters) +} \ No newline at end of file diff --git a/src/delete.ts b/src/delete.ts index e701c163..40fdb902 100644 --- a/src/delete.ts +++ b/src/delete.ts @@ -2,16 +2,35 @@ import {Input} from './input' import {EMPTY, Observable, of, throwError} from 'rxjs' -import {reduce, concatMap, map, expand, tap} from 'rxjs/operators' +import {reduce, concatMap, map, expand, tap, mergeMap, catchError} from 'rxjs/operators' import { deletePackageVersions, getOldestVersions, RestVersionInfo } from './version' +import {getRepoPackages, getPackageNameFilter, PackageInfo} from './packages' + export const RATE_LIMIT = 100 let totalCount = 0 +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 getVersionIds( owner: string, packageName: string, @@ -51,6 +70,34 @@ export function finalIds(input: Input): Observable { return of(input.packageVersionIds.slice(0, toDelete)) } 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)) + } + return getVersionIds( input.owner, input.packageName, diff --git a/src/input.ts b/src/input.ts index f0af8bfc..20d6debc 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,8 +1,10 @@ export interface InputParams { packageVersionIds?: string[] owner?: string + repo?: string packageName?: string packageType?: string + packageNames?: string numOldVersionsToDelete?: number minVersionsToKeep?: number ignoreVersions?: RegExp @@ -13,8 +15,10 @@ export interface InputParams { const defaultParams = { packageVersionIds: [], owner: '', + repo: '', packageName: '', packageType: '', + packageNames: '', numOldVersionsToDelete: 0, minVersionsToKeep: 0, ignoreVersions: new RegExp(''), @@ -25,8 +29,10 @@ const defaultParams = { export class Input { packageVersionIds: string[] owner: string + repo: string packageName: string packageType: string + packageNames: string numOldVersionsToDelete: number minVersionsToKeep: number ignoreVersions: RegExp @@ -39,8 +45,10 @@ export class Input { this.packageVersionIds = validatedParams.packageVersionIds this.owner = validatedParams.owner + this.repo = validatedParams.repo this.packageName = validatedParams.packageName this.packageType = validatedParams.packageType + this.packageNames = validatedParams.packageNames this.numOldVersionsToDelete = validatedParams.numOldVersionsToDelete this.minVersionsToKeep = validatedParams.minVersionsToKeep this.ignoreVersions = validatedParams.ignoreVersions @@ -52,7 +60,8 @@ export class Input { hasOldestVersionQueryInfo(): boolean { return !!( this.owner && - this.packageName && + this.repo && + (this.packageName || this.packageNames) && this.numOldVersionsToDelete >= 0 && this.token ) diff --git a/src/main.ts b/src/main.ts index 152799fc..3d6e7b74 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,6 +14,7 @@ function getActionInput(): Input { owner: getInput('owner') ? getInput('owner') : context.repo.owner, packageName: getInput('package-name'), packageType: getInput('package-type'), + 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/get-versions.ts b/src/version/get-versions.ts index eb8e274e..07e5fc80 100644 --- a/src/version/get-versions.ts +++ b/src/version/get-versions.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import {from, Observable, merge, throwError, of} from 'rxjs' +import {from, Observable, merge, throwError, of, EMPTY} from 'rxjs' import {catchError, map} from 'rxjs/operators' import {Octokit} from '@octokit/rest' import {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods/dist-types/generated/parameters-and-response-types' @@ -45,12 +45,7 @@ export function getOldestVersions( }) ).pipe( catchError(err => { - const msg = 'get versions API failed.' - return throwError( - err.errors && err.errors.length > 0 - ? `${msg} ${err.errors[0].message}` - : `${msg} ${err.message}` - ) + return EMPTY }), map(response => { const resp = {