Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 28 additions & 35 deletions packages/server/src/lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ const getReleaseForTags = async (
};

/**
* Calls GitHub's API to list tags for a repository.
* https://docs.github.com/en/rest/repos/repos#list-repository-tags
* Calls GitHub's API to check if a specific tag exists.
* https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#get-a-reference
* @param name repository name including username. ex: node/node or bcoe/yargs
* @param token
* @param matchingTags list of possible tag names to fetch. The first one to match will be returned.
Expand All @@ -143,42 +143,35 @@ const getMatchingTags = async (
matchingTags: string[]
): Promise<string | undefined> => {
const client = gh.client(token, clientOptions);
// We check up to 1100 of the most recent tags for a match,
// using a large page size to allow for monorepos with 100s of tags:
const maxPagination = 12;
for (let page = 1; page < maxPagination; page++) {
console.info(
`Quering /repos/${name}/tags with 100 per page and page: ${page}`
);
const tags: [{name: string}] = await new Promise((resolve, reject) => {
client.get(
`/repos/${name}/tags`,
{per_page: 100, page: page},
(err: Error, code: number, resp: [{name: string}]) => {
if (err) {
return reject(
Error(
`getMatchingTags: tags = ${matchingTags.toString()}, err = ${err}`
)
);
} else if (code !== 200) {
return reject(
new Error(
`getRelease: unexpected http code = ${code} tag = ${matchingTags.toString()}`
)
);
} else {
resolve(resp);
for (const tag of matchingTags) {
// GitHub's API is lenient with special characters like '@' and '/' in the
// tag name and handles them correctly even if they are not URL encoded.
const apiPath = `/repos/${name}/git/refs/tags/${tag}`;
const exists = await new Promise<boolean>((resolve, reject) => {
client.get(apiPath, (err: {statusCode: number}, code: number) => {
if (err) {
if (err.statusCode === 404) {
return resolve(false);
}
return reject(
Error(
`getMatchingTags: tag = ${tag}, statusCode = ${err.statusCode}, err = ${err}`
)
);
}
);
});
for (const item of tags) {
for (const tag of matchingTags) {
if (item.name === tag) {
return tag;
if (code !== 200) {
return reject(
Error(
`getMatchingTags: tag = ${tag}, unexpected http code = ${code}`
)
);
}
}
resolve(true);
});
});
if (exists) {
console.info(`Found matching tag: ${tag}`);
return tag;
}
}
console.info('No matching tags found.');
Expand Down
56 changes: 15 additions & 41 deletions packages/server/test/lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ describe('github', () => {
const request = nock('https://api.github.com')
.get('/repos/bcoe/test/releases/tags/v1.0.2')
.replyWithError({statusCode: 404})
.get('/repos/bcoe/test/tags?per_page=100&page=1')
.reply(200, [{name: 'v1.0.2'}]);
.get('/repos/bcoe/test/git/refs/tags/v1.0.2')
.reply(200, {ref: 'refs/tags/v1.0.2'});

const latest = await github.getRelease('bcoe/test', 'abc123', ['v1.0.2']);
expect(latest).to.equal('v1.0.2');
Expand All @@ -77,8 +77,8 @@ describe('github', () => {
const request = nock('https://api.github.com')
.get('/repos/bcoe/test/releases/tags/v1.0.2')
.replyWithError({statusCode: 500})
.get('/repos/bcoe/test/tags?per_page=100&page=1')
.reply(200, [{name: 'v1.0.2'}]);
.get('/repos/bcoe/test/git/refs/tags/v1.0.2')
.reply(200, {ref: 'refs/tags/v1.0.2'});

const latest = await github.getRelease('bcoe/test', 'abc123', ['v1.0.2']);
expect(latest).to.equal('v1.0.2');
Expand All @@ -89,8 +89,8 @@ describe('github', () => {
const request = nock('https://api.github.com')
.get('/repos/bcoe/test/releases/tags/v1.0.2')
.replyWithError({statusCode: 404})
.get('/repos/bcoe/test/tags?per_page=100&page=1')
.reply(404);
.get('/repos/bcoe/test/git/refs/tags/v1.0.2')
.reply(401);
let err: Error | undefined = undefined;
try {
await github.getRelease('bcoe/test', 'abc123', ['v1.0.2']);
Expand All @@ -99,7 +99,7 @@ describe('github', () => {
}
expect(err).to.not.equal(undefined);
if (err) {
expect(err.message).to.include('unexpected http code');
expect(err.message).to.include('unexpected http code = 401');
}
request.done();
});
Expand All @@ -110,28 +110,10 @@ describe('github', () => {
.replyWithError({statusCode: 404})
.get('/repos/bcoe/test/releases/tags/@scope/foo@1.0.2')
.replyWithError({statusCode: 404})
.get('/repos/bcoe/test/tags?per_page=100&page=1')
.reply(200, [{name: 'v1.0.2'}])
.get('/repos/bcoe/test/tags?per_page=100&page=2')
.reply(200, [{name: 'v1.0.3'}])
.get('/repos/bcoe/test/tags?per_page=100&page=3')
.reply(200, [{name: 'v1.0.4'}])
.get('/repos/bcoe/test/tags?per_page=100&page=4')
.reply(200, [{name: 'v1.0.5'}])
.get('/repos/bcoe/test/tags?per_page=100&page=5')
.reply(200, [{name: 'v1.0.6'}])
.get('/repos/bcoe/test/tags?per_page=100&page=6')
.reply(200, [{name: 'v1.0.7'}])
.get('/repos/bcoe/test/tags?per_page=100&page=7')
.reply(200, [{name: 'v1.0.8'}])
.get('/repos/bcoe/test/tags?per_page=100&page=8')
.reply(200, [{name: 'v1.0.9'}])
.get('/repos/bcoe/test/tags?per_page=100&page=9')
.reply(200, [{name: 'v1.0.10'}])
.get('/repos/bcoe/test/tags?per_page=100&page=10')
.reply(200, [{name: 'v1.0.10'}])
.get('/repos/bcoe/test/tags?per_page=100&page=11')
.reply(200, [{name: 'v1.0.10'}]);
.get('/repos/bcoe/test/git/refs/tags/foo-v1.0.2')
.replyWithError({statusCode: 404})
.get('/repos/bcoe/test/git/refs/tags/@scope/foo@1.0.2')
.replyWithError({statusCode: 404});

expect(
await github.getRelease('bcoe/test', 'abc123', [
Expand All @@ -146,12 +128,8 @@ describe('github', () => {
const request = nock('https://api.github.com')
.get('/repos/bcoe/test/releases/tags/foo-v1.0.2')
.replyWithError({statusCode: 404})
.get('/repos/bcoe/test/tags?per_page=100&page=1')
.reply(200, [{name: 'v1.0.3'}])
.get('/repos/bcoe/test/tags?per_page=100&page=2')
.reply(200, [{name: 'v1.0.4'}])
.get('/repos/bcoe/test/tags?per_page=100&page=3')
.reply(200, [{name: 'foo-v1.0.2'}]);
.get('/repos/bcoe/test/git/refs/tags/foo-v1.0.2')
.reply(200, {ref: 'refs/tags/foo-v1.0.2'});

const latest = await github.getRelease('bcoe/test', 'abc123', [
'foo-v1.0.2',
Expand All @@ -164,12 +142,8 @@ describe('github', () => {
const request = nock('https://api.github.com')
.get('/repos/bcoe/test/releases/tags/@scope/foo@1.0.2')
.replyWithError({statusCode: 404})
.get('/repos/bcoe/test/tags?per_page=100&page=1')
.reply(200, [{name: 'v1.0.3'}])
.get('/repos/bcoe/test/tags?per_page=100&page=2')
.reply(200, [{name: 'v1.0.4'}])
.get('/repos/bcoe/test/tags?per_page=100&page=3')
.reply(200, [{name: '@scope/foo@1.0.2'}]);
.get('/repos/bcoe/test/git/refs/tags/@scope/foo@1.0.2')
.reply(200, {ref: 'refs/tags/@scope/foo@1.0.2'});

const latest = await github.getRelease('bcoe/test', 'abc123', [
'@scope/foo@1.0.2',
Expand Down
70 changes: 17 additions & 53 deletions packages/server/test/lib/write-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ describe('writePackage', () => {
.get('/repos/foo/bar/releases/tags/v1.0.0')
.replyWithError({statusCode: 404})
// most recent release tag on GitHub is v1.0.0
.get('/repos/foo/bar/tags?per_page=100&page=1')
.reply(200, [{name: 'v1.0.0'}]);
.get('/repos/foo/bar/git/refs/tags/v1.0.0')
.reply(200, {ref: 'refs/tags/v1.0.0'});

const ret = await writePackage('@soldair/foo', req, res);
npmRequest.done();
Expand Down Expand Up @@ -253,8 +253,8 @@ describe('writePackage', () => {
.get('/repos/foo/bar/releases/tags/v1.0.0')
.replyWithError({statusCode: 404})
// most recent tag on GitHub is v1.0.0
.get('/repos/foo/bar/tags?per_page=100&page=1')
.reply(200, [{name: 'v1.0.0'}]);
.get('/repos/foo/bar/git/refs/tags/v1.0.0')
.reply(200, {ref: 'refs/tags/v1.0.0'});

const ret = await writePackage('@soldair/foo', req, res);
npmRequest.done();
Expand Down Expand Up @@ -369,28 +369,8 @@ describe('writePackage', () => {
.get('/repos/foo/bar/releases/tags/v1.0.0')
.replyWithError({statusCode: 404})
// most recent release tag on GitHub is v0.1.0
.get('/repos/foo/bar/tags?per_page=100&page=1')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=2')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=3')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=4')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=5')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=6')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=7')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=8')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=9')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=10')
.reply(200, [{name: 'v0.1.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=11')
.reply(200, [{name: 'v0.1.0'}]);
.get('/repos/foo/bar/git/refs/tags/v1.0.0')
.replyWithError({statusCode: 404});

const ret = await writePackage('@soldair/foo', req, res);
npmRequest.done();
Expand Down Expand Up @@ -449,7 +429,7 @@ describe('writePackage', () => {
.get('/repos/foo/bar/releases/tags/v1.0.0')
.replyWithError({statusCode: 404})
// Error while fetching tags
.get('/repos/foo/bar/tags?per_page=100&page=1')
.get('/repos/foo/bar/git/refs/tags/v1.0.0')
.reply(500);

const ret = await writePackage('@soldair/foo', req, res);
Expand Down Expand Up @@ -630,8 +610,8 @@ describe('writePackage', () => {
.get('/repos/foo/bar/releases/tags/@soldair/foo@1.0.0')
.replyWithError({statusCode: 404})
// But there is a matching tag for v1.0.0
.get('/repos/foo/bar/tags?per_page=100&page=1')
.reply(200, [{name: 'foo-v1.0.0'}]);
.get('/repos/foo/bar/git/refs/tags/foo-v1.0.0')
.reply(200, {ref: 'refs/tags/foo-v1.0.0'});

const ret = await writePackage('@soldair/foo', req, res);
npmRequest.done();
Expand Down Expand Up @@ -693,8 +673,10 @@ describe('writePackage', () => {
.get('/repos/foo/bar/releases/tags/@soldair/foo@1.0.0')
.replyWithError({statusCode: 404})
// most recent release tag on GitHub is v1.0.0
.get('/repos/foo/bar/tags?per_page=100&page=1')
.reply(200, [{name: '@soldair/foo@1.0.0'}]);
.get('/repos/foo/bar/git/refs/tags/foo-v1.0.0')
.replyWithError({statusCode: 404})
.get('/repos/foo/bar/git/refs/tags/@soldair/foo@1.0.0')
.reply(200, {ref: 'refs/tags/@soldair/foo@1.0.0'});

const ret = await writePackage('@soldair/foo', req, res);
npmRequest.done();
Expand Down Expand Up @@ -756,28 +738,10 @@ describe('writePackage', () => {
.get('/repos/foo/bar/releases/tags/@soldair/foo@1.0.0')
.replyWithError({statusCode: 404})
// This is monorepo-style token but the tags on GH are not monorepo-style
.get('/repos/foo/bar/tags?per_page=100&page=1')
.reply(200, [{name: 'v1.0.0'}])
.get('/repos/foo/bar/tags?per_page=100&page=2')
.reply(200, [{name: 'v1.0.3'}])
.get('/repos/foo/bar/tags?per_page=100&page=3')
.reply(200, [{name: 'v1.0.4'}])
.get('/repos/foo/bar/tags?per_page=100&page=4')
.reply(200, [{name: 'v1.0.5'}])
.get('/repos/foo/bar/tags?per_page=100&page=5')
.reply(200, [{name: 'v1.0.6'}])
.get('/repos/foo/bar/tags?per_page=100&page=6')
.reply(200, [{name: 'v1.0.7'}])
.get('/repos/foo/bar/tags?per_page=100&page=7')
.reply(200, [{name: 'v1.0.8'}])
.get('/repos/foo/bar/tags?per_page=100&page=8')
.reply(200, [{name: 'v1.0.9'}])
.get('/repos/foo/bar/tags?per_page=100&page=9')
.reply(200, [{name: 'v1.0.10'}])
.get('/repos/foo/bar/tags?per_page=100&page=10')
.reply(200, [{name: 'v1.0.10'}])
.get('/repos/foo/bar/tags?per_page=100&page=11')
.reply(200, [{name: 'v1.0.10'}]);
.get('/repos/foo/bar/git/refs/tags/foo-v1.0.0')
.replyWithError({statusCode: 404})
.get('/repos/foo/bar/git/refs/tags/@soldair/foo@1.0.0')
.replyWithError({statusCode: 404});

const ret = await writePackage('@soldair/foo', req, res);
npmRequest.done();
Expand Down