Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Octokit for fetching resources off Github #35

Merged
merged 4 commits into from
May 10, 2021
Merged
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ List the Node.js versions supported by the package/repository

This repository is managed by the [Package Maintenance Working Group](https://github.com/nodejs/package-maintenance), see [Governance](https://github.com/nodejs/package-maintenance/blob/master/Governance.md).

## Setup

No setup is required, however if you do not have a `GH_TOKEN` environment limit, you will likely hit a request rate limit on Github API, which may result in very long wait times for retries.

## Usage (command line)

```
Expand Down
35 changes: 35 additions & 0 deletions lib/loader/octokit-wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';

const { Octokit } = require('@octokit/rest');
const { throttling } = require('@octokit/plugin-throttling');

const Constants = require('../constants');
const Logger = require('../logger');


const internals = {
Octokit: Octokit.plugin(throttling)
};


exports.create = () => {

const octokit = new internals.Octokit({
auth: process.env.GH_TOKEN,
userAgent: Constants.userAgent,
throttle: {
onRateLimit: (retryAfter, options) => {

Logger.warn(['loader'], 'Request quota exceeded for request %s %s. Will retry in %d seconds. Have you set a GH_TOKEN in env?', options.method, options.url, retryAfter);

return true;
},
onAbuseLimit: (retryAfter, options) => {

return false;
}
}
});

return octokit;
};
46 changes: 30 additions & 16 deletions lib/loader/repository.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use strict';

const GitUrlParse = require('git-url-parse');
const Wreck = require('@hapi/wreck');

const Logger = require('../logger');
const OctokitWrapper = require('./octokit-wrapper');
const Utils = require('../utils');


Expand All @@ -30,40 +30,54 @@ exports.create = (repository) => {

return head;
},
loadFile: async (filename, options) => {
loadFile: async (filename, options = {}) => {

if (parsedRepository.source !== 'github.com') {
throw new Error('Only github.com paths supported, feel free to PR at https://github.com/pkgjs/detect-node-support');
}

const url = `https://raw.githubusercontent.com/${parsedRepository.full_name}/HEAD/${filename}`;
Logger.log(['loader'], 'Loading: %s', url);
const resource = `${parsedRepository.full_name}:${filename}@HEAD`;
Logger.log(['loader'], 'Loading: %s', resource);

if (options === undefined && internals.cache.has(url)) {
Logger.log(['loader'], 'From cache: %s', url);
return internals.cache.get(url);
}
const octokit = OctokitWrapper.create();

try {
const { payload } = await Wreck.get(url, options);

if (options === undefined) {
internals.cache.set(url, payload);
let result;
if (internals.cache.has(resource)) {
Logger.log(['loader'], 'From cache: %s', resource);
result = internals.cache.get(resource);
}
else {
result = await octokit.repos.getContent({
owner: parsedRepository.owner,
repo: parsedRepository.name,
path: filename
});
}

internals.cache.set(resource, result);

Logger.log(['loader'], 'Loaded: %s', resource);

const content = Buffer.from(result.data.content, 'base64');

if (options.json) {
return JSON.parse(content.toString());
}

Logger.log(['loader'], 'Loaded: %s', url);
return payload;
return content;
}
catch (err) {

if (err.data && err.data.res.statusCode === 404) {
Logger.log(['loader'], 'Not found: %s', url);
if (err.status === 404) {
Logger.log(['loader'], 'Not found: %s', resource);
const error = new Error(`${repository} does not contain a ${filename}`);
error.code = 'ENOENT';
throw error;
}

Logger.error(['loader'], 'Failed to load: %s', url);
Logger.error(['loader'], 'Failed to load: %s', resource);
throw err;
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ exports.detect = async (what) => {

const { loadFile, getCommit } = await Loader.create({ path, repository, packageName });

const packageJson = await loadFile('package.json', { json: 'force' });
const packageJson = await loadFile('package.json', { json: true });

const meta = {
packageJson,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@
"sinon": "^10.0.0"
},
"dependencies": {
"@hapi/wreck": "^17.0.0",
"@npmcli/arborist": "^2.0.0",
"@octokit/plugin-throttling": "^3.2.2",
"@octokit/rest": "^18.0.0",
"@pkgjs/nv": "0.1.0",
"debug": "^4.1.1",
"git-url-parse": "^11.1.2",
Expand Down
8 changes: 5 additions & 3 deletions test/fixtures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ module.exports = class TestContext {
.get('/dist/index.json')
.reply(200, Fs.readFileSync(Path.join(__dirname, 'node-release-dist.json')));

Nock('https://raw.githubusercontent.com')
Nock('https://api.github.com')
.persist()
.get('/nodejs/ci-config-travis/HEAD/lts/gte-10.yml')
.reply(200, Fs.readFileSync(Path.join(__dirname, 'travis-ymls', 'nodejs-ci-config-travis-gte-10.yml')));
.get('/repos/nodejs/ci-config-travis/contents/lts%2Fgte-10.yml')
.reply(200, {
content: Fs.readFileSync(Path.join(__dirname, 'travis-ymls', 'nodejs-ci-config-travis-gte-10.yml')).toString('base64')
});

this._cleanup.push(() => {

Expand Down
Loading