diff --git a/README.md b/README.md index b1b909b..5f83933 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ Note that the current implementation filters for the first 100 versions ## `version` -**Required** The name of the version that is being searched for, i.e. `1.0.0-SNAPSHOT` +**Required** A regular expression that is matched against the name of the +version that is being searched for, i.e. `1.0.0-SNAPSHOT` for an exact match +or `.*-SNAPSHOT` for all snapshots. ## `token` @@ -32,7 +34,7 @@ A comma separated list of package version ids ## Example usage ``` -- uses: castlabs/get-package-version-id-action@v1.0 +- uses: castlabs/get-package-version-id-action@v2.0 id: versions with: versions: "1.0-SNAPSHOT" diff --git a/action.yml b/action.yml index da4f41e..9d14a19 100644 --- a/action.yml +++ b/action.yml @@ -1,8 +1,8 @@ name: 'Package Version IDs' description: 'Find ids for package versions based on the version name' inputs: - version: - description: 'The name of the version' + version: + description: 'A regular expression that is matched against the name of the version' required: true token: diff --git a/dist/index.js b/dist/index.js index 6ed4ca8..e7aedc3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -8471,34 +8471,127 @@ var __webpack_exports__ = {}; const core = __nccwpck_require__(2186); const github = __nccwpck_require__(5438); -async function fetchIds(token, version) { - const octokit = github.getOctokit(token) - const {owner, repo} = github.context.repo - const response = await octokit.graphql( - `query Versions($repo: String!, $version: String!, $owner:String!) { - repository(name: $repo, owner: $owner) { - packages(first:100) { +// This is the main graphql query that we issue +// we do not filter here but rather get all package version +// and then use a filter on the results +// +// Note that this is double pagination and we need to deal with +// both cursors +const VERSIONS_QUERY = `query Versions($repo: String!, $owner:String!, $cursor_packages:String, $cursor_versions:String) { + repository(name: $repo, owner: $owner) { + packages(first:100, after: $cursor_packages) { + pageInfo { + endCursor + hasNextPage + } + nodes { + versions(first: 100, after: $cursor_versions) { + pageInfo { + endCursor + hasNextPage + } nodes { - version(version: $version) { - id - } + id + version } } } - - } - `, - { - repo, - version, - owner + } } +}`; + +/** + * + * @param octokit The octokit instance + * @param repo The repo + * @param owner The ownser + * @param cursor_packages The packages cursor + * @param cursor_versions The versions cursor + * @return {Promise<*>} The raw response + */ +async function query(octokit, repo, owner, cursor_packages, cursor_versions) { + return octokit.graphql( + VERSIONS_QUERY, + { + repo, + owner, + cursor_packages, + cursor_versions + }, ); +} + +/** + * Takes the response from `query()` and a regex matcher and + * returns all version ids in the response where the version matches + * + * @param response The raw responses + * @param matcher The regex matcher + * @return {string[]} The version ids where the version matches + */ +function getIds(response, matcher) { + return response.repository.packages.nodes + .map(i => i.versions) + .flatMap(i => i.nodes) + .filter(i => i !== null) + .filter(i => matcher.test(i.version)) + .map(i => i.id) +} + +/** + * Takes the raw response from `query` and returns all version cursors + * if there are any + * + * @param response The response + * @return {string[]} All version cursors of empty array + */ +function versionCursors(response) { return response.repository.packages.nodes - .map(i => i.version) - .filter(i => i !== null) - .map(i => i.id) - .join(','); + .map(i => i.versions) + .flatMap(i => i.pageInfo) + .filter(i => i.hasNextPage) + .map(i => i.endCursor) +} + +/** + * Takes the raw response from `query` and returns all + * package cursors + * + * @param response The raw response + * @return {string[]} All package cursors if there are any + */ +function packagesCursors(response) { + return [response.repository.packages.pageInfo] + .filter(i => i.hasNextPage) + .map(i => i.endCursor) +} + + +async function fetchIds(token, version) { + const octokit = github.getOctokit(token) + const {owner, repo} = github.context.repo + // the matcher for the version string + const matcher = new RegExp('^' + version + '$') + + // We start with a null as the first package cursor + let pkgCursors = [null]; + // we collect the final results here + let versions = []; + while (pkgCursors.length > 0) { + for (const pc of pkgCursors) { + let response = await query(octokit, repo, owner, pc, null); + pkgCursors = packagesCursors(response); + versions = versions.concat(getIds(response, matcher)); + for (const c of versionCursors(response)) { + response = await query(octokit, repo, owner, pc, c); + versions = versions.concat(getIds(response, matcher)); + } + } + } + + let results = [...new Set(versions)] + console.log(">> RESULTS ", JSON.stringify(results, null, 2)) + return results } async function main() { @@ -8507,12 +8600,12 @@ async function main() { const token = core.getInput('token') || process.env.GITHUB_TOKEN; core.info(`Fetch IDs for ${version}`) const ids = await fetchIds(token, version); - core.info(`Found ${ids.split(',').length} ids for version '${version}': ${ids}`) - core.setOutput("ids", ids); + core.info(`Found ${ids.length} ids for version '${version}': ${ids.join(',')}`) + core.setOutput("ids", ids.join(',')); } catch (error) { console.log(error) core.setFailed(error.message); - } + } } main() diff --git a/index.js b/index.js index c7e1342..c434c42 100644 --- a/index.js +++ b/index.js @@ -1,34 +1,127 @@ const core = require('@actions/core'); const github = require('@actions/github'); -async function fetchIds(token, version) { - const octokit = github.getOctokit(token) - const {owner, repo} = github.context.repo - const response = await octokit.graphql( - `query Versions($repo: String!, $version: String!, $owner:String!) { - repository(name: $repo, owner: $owner) { - packages(first:100) { +// This is the main graphql query that we issue +// we do not filter here but rather get all package version +// and then use a filter on the results +// +// Note that this is double pagination and we need to deal with +// both cursors +const VERSIONS_QUERY = `query Versions($repo: String!, $owner:String!, $cursor_packages:String, $cursor_versions:String) { + repository(name: $repo, owner: $owner) { + packages(first:100, after: $cursor_packages) { + pageInfo { + endCursor + hasNextPage + } + nodes { + versions(first: 100, after: $cursor_versions) { + pageInfo { + endCursor + hasNextPage + } nodes { - version(version: $version) { - id - } + id + version } } } - - } - `, - { - repo, - version, - owner + } } +}`; + +/** + * + * @param octokit The octokit instance + * @param repo The repo + * @param owner The ownser + * @param cursor_packages The packages cursor + * @param cursor_versions The versions cursor + * @return {Promise<*>} The raw response + */ +async function query(octokit, repo, owner, cursor_packages, cursor_versions) { + return octokit.graphql( + VERSIONS_QUERY, + { + repo, + owner, + cursor_packages, + cursor_versions + }, ); +} + +/** + * Takes the response from `query()` and a regex matcher and + * returns all version ids in the response where the version matches + * + * @param response The raw responses + * @param matcher The regex matcher + * @return {string[]} The version ids where the version matches + */ +function getIds(response, matcher) { + return response.repository.packages.nodes + .map(i => i.versions) + .flatMap(i => i.nodes) + .filter(i => i !== null) + .filter(i => matcher.test(i.version)) + .map(i => i.id) +} + +/** + * Takes the raw response from `query` and returns all version cursors + * if there are any + * + * @param response The response + * @return {string[]} All version cursors of empty array + */ +function versionCursors(response) { return response.repository.packages.nodes - .map(i => i.version) - .filter(i => i !== null) - .map(i => i.id) - .join(','); + .map(i => i.versions) + .flatMap(i => i.pageInfo) + .filter(i => i.hasNextPage) + .map(i => i.endCursor) +} + +/** + * Takes the raw response from `query` and returns all + * package cursors + * + * @param response The raw response + * @return {string[]} All package cursors if there are any + */ +function packagesCursors(response) { + return [response.repository.packages.pageInfo] + .filter(i => i.hasNextPage) + .map(i => i.endCursor) +} + + +async function fetchIds(token, version) { + const octokit = github.getOctokit(token) + const {owner, repo} = github.context.repo + // the matcher for the version string + const matcher = new RegExp('^' + version + '$') + + // We start with a null as the first package cursor + let pkgCursors = [null]; + // we collect the final results here + let versions = []; + while (pkgCursors.length > 0) { + for (const pc of pkgCursors) { + let response = await query(octokit, repo, owner, pc, null); + pkgCursors = packagesCursors(response); + versions = versions.concat(getIds(response, matcher)); + for (const c of versionCursors(response)) { + response = await query(octokit, repo, owner, pc, c); + versions = versions.concat(getIds(response, matcher)); + } + } + } + + let results = [...new Set(versions)] + console.log(">> RESULTS ", JSON.stringify(results, null, 2)) + return results } async function main() { @@ -37,12 +130,12 @@ async function main() { const token = core.getInput('token') || process.env.GITHUB_TOKEN; core.info(`Fetch IDs for ${version}`) const ids = await fetchIds(token, version); - core.info(`Found ${ids.split(',').length} ids for version '${version}': ${ids}`) - core.setOutput("ids", ids); + core.info(`Found ${ids.length} ids for version '${version}': ${ids.join(',')}`) + core.setOutput("ids", ids.join(',')); } catch (error) { console.log(error) core.setFailed(error.message); - } + } } main()