Skip to content

Commit

Permalink
Merge pull request #78 from OperationSpark/chore/remove-svn
Browse files Browse the repository at this point in the history
Swap svn for GitHub API calls
  • Loading branch information
harveysanders authored Feb 6, 2024
2 parents dad768d + 37b4459 commit c7c2393
Show file tree
Hide file tree
Showing 14 changed files with 4,081 additions and 3,375 deletions.
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

0 comments on commit c7c2393

Please sign in to comment.