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 {
/**
*
* @param {string} token
* @returns
*/
harveysanders marked this conversation as resolved.
Show resolved Hide resolved
constructor(token) {
this._client = new Octokit({
auth: `token ${token}`,
userAgent: `opspark_tool@${version}`
});
}

/**
*
* @param {string} url
* @returns {{owner: string, repo: string}}
*/
harveysanders marked this conversation as resolved.
Show resolved Hide resolved
static parseRepoUrl(url) {
const parsed = new URL(url);
return {
owner: parsed.pathname.split('/')[1],
repo: parsed.pathname.split('/')[2]
};
}
/**
*
* @param {string} url
* @param {string} directory
*/
harveysanders marked this conversation as resolved.
Show resolved Hide resolved
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 = {
/**
*
* @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
95 changes: 39 additions & 56 deletions controller/test.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,30 @@
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
harveysanders marked this conversation as resolved.
Show resolved Hide resolved
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 +47,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
24 changes: 11 additions & 13 deletions install.sh
harveysanders marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
#!/usr/bin/env bash
# Save current npm version to variable, ex v6.11.2
NODE_V=`node -v`
NODE_V=$(node -v)
# Pull out the major version number
NODE_MAJOR=`grep -oP '(?<=v).*?(?=\.)' <<< "$NODE_V"`
NODE_MAJOR=$(grep -oP '(?<=v).*?(?=\.)' <<<"$NODE_V")
# Check if npm version is less than 6
if [ ${NODE_MAJOR} -lt 6 ]
then
echo -e "\n\e[31mYour current version of Node is $NODE_V, which is not compatible with opspark.\n"
echo -e "Please \e[1m\e[34mupdate to (at least) Node version 6 using NVM \e[0m\e[31mand reinstall opspark.\n"
echo -e "Opspark WILL NOT WORK even if it is installed with this version of Node.\n"
pkill -9 -P $PPID
if [ ${NODE_MAJOR} -lt 6 ]; then
echo -e "\n\e[31mYour current version of Node is $NODE_V, which is not compatible with opspark.\n"
echo -e "Please \e[1m\e[34mupdate to (at least) Node version 6 using NVM \e[0m\e[31mand reinstall opspark.\n"
echo -e "Opspark WILL NOT WORK even if it is installed with this version of Node.\n"
pkill -9 -P $PPID
else
echo -e "\e[32mNode $NODE_V, good to go!"
npm install --global bower --depth=0 --progress
npm install --global mocha
sudo apt-get install subversion
echo "Great! Have fun using OpSpark!"
echo -e "\e[32mNode $NODE_V, good to go!"
npm install --global bower --depth=0 --progress
npm install --global mocha
echo "Great! Have fun using OpSpark!"
fi
Loading