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

Swap svn for GitHub API calls #78

Merged
merged 12 commits into from
Feb 6, 2024
10 changes: 4 additions & 6 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,18 @@
"esbenp.prettier-vscode"
]
}
},
"containerEnv": {
"GREENLIGHT_HOST": "http://host.docker.internal:3000",
"NODE_ENV": "development"
}

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
11 changes: 10 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
module.exports = {
extends: 'airbnb-base',
plugins: ['import'],
env: {
node: true,
es6: true,
mocha: true
},
globals: {
fetch: false,
URL: false
},
rules: {
// enable additional rules
quotes: ['error', 'single', { "avoidEscape": true }],
quotes: ['error', 'single', { avoidEscape: true }],
semi: ['error', 'always'],

// disable rules from base configurations
Expand Down
133 changes: 133 additions & 0 deletions controller/github-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
const fs = require('node:fs/promises');
const path = require('path');
const { Octokit } = require('octokit');
const { version } = require('../package.json');

/** @typedef {import("@octokit/openapi-types/types").components } GitHubComponents */
/** @typedef {GitHubComponents["schemas"]["content-directory"] | GitHubComponents["schemas"]["content-file"] } GetContentResp */

class GithubAPI {
/**
* Creates a new GitHub API client.
* @param {string} token
* @returns
*/
constructor(token) {
this._client = new Octokit({
auth: `token ${token}`,
userAgent: `opspark_tool@${version}`
});
}

/**
* Parses a GitHub repository URL and returns the owner and repo.
* @param {string} url
* @returns {{owner: string, repo: string}}
*/
static parseRepoUrl(url) {
const parsed = new URL(url);
return {
owner: parsed.pathname.split('/')[1],
repo: parsed.pathname.split('/')[2]
};
}
/**
* Downloads the tests for a project from GitHub into the specified `directory`.
* @param {string} url
* @param {string} directory
*/
async downloadProjectTests(url, directory) {
const { owner, repo } = GithubAPI.parseRepoUrl(url);
const res = await this._client.rest.repos.getContent({
owner,
repo,
path: 'test',
recursive: true
});

if (res.status !== 200) {
throw new Error('Failed to download tests');
}

/** @type GetContentResp[] | GetContentResp */
if (!Array.isArray(res.data)) {
// Single file?
return;
}

await fs.mkdir(path.join(directory, 'test'), { recursive: true });
const promises = res.data.map(async content =>
this._downloadFiles(content, directory)
);
await Promise.all(promises);
}

/**
* Downloads the package.json and .npmrc files for a project.
* @param {string} url
* @param {string} directory
*/
async downloadProjectPackage(url, directory) {
const files = ['package.json', '.npmrc'];
const { owner, repo } = GithubAPI.parseRepoUrl(url);
const promises = files.map(async filePath => {
const res = await this._client.rest.repos.getContent({
mediaType: { format: 'raw' },
owner,
repo,
path: filePath
});
if (res.status !== 200) {
throw new Error('Failed to download package');
}
await fs.writeFile(path.join(directory, filePath), res.data, {
encoding: 'utf8'
});
});

await Promise.all(promises);
}
/**
* Downloads the files in a GitHub API contents response.
* @param {GetContentResp} contents
* @param {string} directory
*/
// eslint-disable-next-line class-methods-use-this
async _downloadFiles(contents, directory) {
if (!directory) {
throw new Error('No directory provided');
}
// TODO: Recursively create directories and write files
if (contents.type === 'dir') {
return;
}

const filePath = path.join(directory, contents.path);
const res = await fetch(contents.download_url);
if (!res.ok || !res.body) {
throw new Error(
`Failed to download file: ${contents.path}\n${res.statusText}`
);
}

const fileContents = await res.text();
await fs.writeFile(filePath, fileContents, { encoding: 'utf8' });
}
}

let _client;
module.exports = {
/**
* Returns a new or existing GitHub API client.
* @param {string} token
* @returns {GithubAPI}
*/

getClient(token) {
if (!token) throw new Error('No token provided');
if (!_client) {
_client = new GithubAPI(token);
}
return _client;
}
};
20 changes: 10 additions & 10 deletions controller/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ module.exports.checkGithubAuth = function (token, userAgent) {
return `curl -A ${userAgent} https://api.github.com/?access_token=${token}`;
};

module.exports.downloadProject = function (url, token, directory) {
return `svn co ${url}/trunk --password ${token} ${directory}`;
};

module.exports.downloadProjectTests = function (url, token, directory) {
return `svn export ${url}/trunk/test --password ${token} ${directory}/test`;
};

module.exports.downloadProjectPackage = function (url, token, directory) {
return `svn export ${url}/trunk/package.json --password ${token} ${directory}/package.json && svn export ${url}/trunk/.npmrc --password ${token} ${directory}/.npmrc`;
/**
* @param {string} gitUrl
* @param {string} token
* @param {string} directory
* @returns string
*/
module.exports.downloadProject = function (gitUrl, token, directory) {
const parsed = new URL(gitUrl);
const withPAT = `https://${token}@${parsed.host}${parsed.pathname}.git`;
return `git clone ${withPAT} ${directory}`;
};

module.exports.createGistHelper = function (username, token, content) {
Expand Down
3 changes: 2 additions & 1 deletion controller/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ function shelveProject(project) {
}, '');
const cmd = `mv ${path}/${name} ${path}/${underscores}_${name}`;
console.log(clc.yellow('Shelving project. . .'));
exec(cmd, function () {
exec(cmd, function (err) {
if (err) return rej(err);
res(`${path}/${underscores}_${name}`);
});
});
Expand Down
103 changes: 44 additions & 59 deletions controller/test.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,32 @@
const clc = require('cli-color');
const fs = require('fs');
const changeCase = require('change-case');
const exec = require('child_process').exec;

const { home } = require('./env');
const janitor = require('./janitor');
const github = require('./github');
const { getClient } = require('./github-api');
const greenlight = require('./greenlight');
const sessions = require('./sessions');
const projects = require('./projects');
const report = require('./reporter');
const {
downloadProjectTests,
downloadProjectPackage,
installProjectDependenciesCmd,
removeProjectTestsCmd,
execAsync
} = require('./helpers');

const rootDirectory = `${home()}/environment`;

// if (cloud9User) {
// githubDir = fs
// .readdirSync(rootDirectory)
// .filter(dir => /[\w]+\.github\.io/.test(dir))[0];
// rootDirectory = `${home()}/environment/${githubDir}`;
// } else if (codenvyUser) {
// rootDirectory = codenvyUser;
// githubDir = fs
// .readdirSync(rootDirectory)
// .filter(dir => /[\w]+\.github\.io/.test(dir))[0];
// rootDirectory = `${rootDirectory}/${githubDir}`;
// }
const projectsDirectory = `${rootDirectory}/projects`;

// Start of test command
// Runs the listProjectsOf function from projects to select project
// that user wants to be tested
/**
* `test` command entry point.
* Runs the `listProjectsOf` function from projects to select project
* that user wants to be tested.
*/
function test() {
console.log(clc.blue('Beginning test process!'));
projects.action = () => 'test';
projects.action = 'test';
github
.getCredentials()
.catch(janitor.error(clc.red('Failure getting credentials')))
Expand All @@ -62,47 +49,45 @@ function test() {
}

module.exports.test = test;

/**
* Runs svn export to download the tests for the specific project
* and places them in the correct directory
* Then calls setEnv
* @param {*} project
* @returns Promise<object>
* @typedef {{
* _id: string;
* _session: string;
* desc: string;
* name: string;
* url: string;
* }} Project
*/
function grabTests(project) {
return new Promise(function (res, rej) {
const name = changeCase.paramCase(project.name);
console.log(clc.yellow(`Downloading tests for ${name}. . .`));
const directory = `${projectsDirectory}/${name}`;
const cmd = downloadProjectTests(
project.url,
github.grabLocalAuthToken(),
directory
);
const pckgCmd = downloadProjectPackage(
project.url,
github.grabLocalAuthToken(),
directory
);
if (fs.existsSync(`${directory}/test`)) {
console.log(clc.green('Skipping tests.'));
res(project);
} else {
exec(cmd, function (error) {
if (error) return rej(error);
console.log(clc.green('Successfully downloaded tests!'));
if (!fs.existsSync(`${directory}/package.json`)) {
console.log(clc.yellow(`Downloading Package.json for ${name}. . .`));
exec(pckgCmd, function () {
console.log(clc.green('Package.json successfully installed'));
res(project);
});
} else {
res(project);
}
});
}
});

/**
* Downloads the tests for a project from GitHub and
* installs them in the project's "test" directory.
* @param {Project} project
* @returns {Promise<Project>}
*/
async function grabTests(project) {
const name = changeCase.paramCase(project.name);
console.log(clc.yellow(`Downloading tests for ${name}. . .`));
const directory = `${projectsDirectory}/${name}`;

if (fs.existsSync(`${directory}/test`)) {
console.log(clc.green('Skipping tests.'));
return project;
}

const ghClient = getClient(github.grabLocalAuthToken());
await ghClient.downloadProjectTests(project.url, directory);
console.log(clc.green('Successfully downloaded tests!'));

if (!fs.existsSync(`${directory}/package.json`)) {
console.log(clc.yellow(`Downloading Package.json for ${name}. . .`));
await ghClient.downloadProjectPackage(project.url, directory);
console.log(clc.green('Package.json successfully installed'));
return project;
}

return project;
}

module.exports.grabTests = grabTests;
Expand Down
19 changes: 0 additions & 19 deletions install.sh

This file was deleted.

Loading