提交 98da97be 编写于 作者: J Jason Park

Add webhooks for tracers and algorithms repositories

上级 ff73e54a
......@@ -2,6 +2,5 @@
/node_modules
/build
/npm-debug.log
/package-lock.json
/src/backend/public
.DS_Store
......@@ -8,6 +8,7 @@ const {
GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET,
GITHUB_WEBHOOK_SECRET,
} = process.env;
const __PROD__ = NODE_ENV === 'production';
......@@ -18,6 +19,7 @@ const proxyPort = parseInt(PROXY_PORT);
const githubClientId = GITHUB_CLIENT_ID;
const githubClientSecret = GITHUB_CLIENT_SECRET;
const githubWebhookSecret = GITHUB_WEBHOOK_SECRET;
const buildPath = path.resolve(__dirname, 'build');
const frontendBuildPath = path.resolve(buildPath, 'frontend');
......@@ -36,9 +38,10 @@ module.exports = {
proxyPort,
githubClientId,
githubClientSecret,
githubWebhookSecret,
frontendBuildPath,
backendBuildPath,
frontendSrcPath,
backendSrcPath,
apiEndpoint,
};
\ No newline at end of file
};
此差异已折叠。
import Promise from 'bluebird';
import child_process from 'child_process';
import fs from 'fs-extra';
const execute = (command, cwd, { stdout = process.stdout, stderr = process.stderr } = {}) => new Promise((resolve, reject) => {
if (!cwd) return reject(new Error('CWD Not Specified'));
......@@ -11,6 +12,12 @@ const execute = (command, cwd, { stdout = process.stdout, stderr = process.stder
if (stderr) child.stderr.pipe(stderr);
});
const createKey = name => name.toLowerCase().replace(/ /g, '-');
const listFiles = dirPath => fs.readdirSync(dirPath).filter(fileName => !fileName.startsWith('.'));
export {
execute,
createKey,
listFiles,
};
import GithubWebHook from 'express-github-webhook';
import { githubWebhookSecret } from '/environment';
const webhook = GithubWebHook({ path: '/', secret: githubWebhookSecret });
export default webhook;
......@@ -4,56 +4,51 @@ import fs from 'fs-extra';
import path from 'path';
import { NotFoundError } from '/common/error';
import { GitHubApi } from '/apis';
import { execute } from '/common/util';
import { createKey, execute, listFiles } from '/common/util';
import webhook from '/common/webhook';
const router = express.Router();
const repoPath = path.resolve(__dirname, '..', 'public', 'algorithms');
const getPath = (...args) => path.resolve(repoPath, ...args);
const cacheCategories = () => {
const allFiles = [];
const createKey = name => name.toLowerCase().replace(/ /g, '-');
const list = dirPath => fs.readdirSync(dirPath).filter(fileName => !fileName.startsWith('.'));
const cacheCategory = categoryName => {
const categoryKey = createKey(categoryName);
const categoryPath = getPath(categoryName);
const algorithms = list(categoryPath).map(algorithmName => cacheAlgorithm(categoryName, algorithmName));
return {
key: categoryKey,
name: categoryName,
algorithms,
};
};
const cacheAlgorithm = (categoryName, algorithmName) => {
const algorithmKey = createKey(algorithmName);
const algorithmPath = getPath(categoryName, algorithmName);
const files = list(algorithmPath).map(fileName => cacheFile(categoryName, algorithmName, fileName));
allFiles.push(...files);
return {
key: algorithmKey,
name: algorithmName,
files,
};
const cacheFile = (categoryName, algorithmName, fileName) => {
const filePath = getPath(categoryName, algorithmName, fileName);
const content = fs.readFileSync(filePath, 'utf-8');
return {
name: fileName,
path: filePath,
content,
contributors: [],
toJSON: () => fileName,
};
const cacheFile = (categoryName, algorithmName, fileName) => {
const filePath = getPath(categoryName, algorithmName, fileName);
const content = fs.readFileSync(filePath, 'utf-8');
return {
name: fileName,
path: filePath,
content,
contributors: [],
toJSON: () => fileName,
};
};
const cacheAlgorithm = (categoryName, algorithmName) => {
const algorithmKey = createKey(algorithmName);
const algorithmPath = getPath(categoryName, algorithmName);
const files = listFiles(algorithmPath).map(fileName => cacheFile(categoryName, algorithmName, fileName));
return {
key: algorithmKey,
name: algorithmName,
files,
};
};
const categories = list(getPath()).map(cacheCategory);
const cacheCategory = categoryName => {
const categoryKey = createKey(categoryName);
const categoryPath = getPath(categoryName);
const algorithms = listFiles(categoryPath).map(algorithmName => cacheAlgorithm(categoryName, algorithmName));
return {
key: categoryKey,
name: categoryName,
algorithms,
};
};
const cacheCommitAuthors = (page = 1, commitAuthors = {}) => {
const per_page = 100;
const cacheCommitAuthors = (page = 1, commitAuthors = {}) => GitHubApi.listCommits('algorithm-visualizer', 'algorithms', {
return GitHubApi.listCommits('algorithm-visualizer', 'algorithms', {
per_page,
page,
}).then(commits => {
......@@ -68,22 +63,30 @@ const cacheCategories = () => {
return cacheCommitAuthors(page + 1, commitAuthors);
}
});
const cacheContributors = commitAuthors => Promise.each(allFiles, file => {
return execute(`git --no-pager log --follow --no-merges --format="%H" "${file.path}"`, getPath(), { stdout: null })
.then(stdout => {
const output = stdout.toString().replace(/\n$/, '');
const shas = output.split('\n').reverse();
const contributors = [];
for (const sha of shas) {
const author = commitAuthors[sha];
if (author && !contributors.find(contributor => contributor.login === author.login)) {
contributors.push(author);
}
};
const cacheContributors = (files, commitAuthors) => Promise.each(files, file => {
return execute(`git --no-pager log --follow --no-merges --format="%H" "${file.path}"`, getPath(), { stdout: null })
.then(stdout => {
const output = stdout.toString().replace(/\n$/, '');
const shas = output.split('\n').reverse();
const contributors = [];
for (const sha of shas) {
const author = commitAuthors[sha];
if (author && !contributors.find(contributor => contributor.login === author.login)) {
contributors.push(author);
}
file.contributors = contributors;
});
});
cacheCommitAuthors().then(cacheContributors);
}
file.contributors = contributors;
});
});
const cacheCategories = () => {
const categories = listFiles(getPath()).map(cacheCategory);
const files = [];
categories.forEach(category => category.algorithms.forEach(algorithm => files.push(...algorithm.files)));
cacheCommitAuthors().then(commitAuthors => cacheContributors(files, commitAuthors));
return categories;
};
......@@ -94,9 +97,16 @@ const downloadCategories = () => (
execute(`git clone git@github.com:algorithm-visualizer/algorithms ${repoPath}`, __dirname)
);
let categories = []; // TODO: download again when webhooked
let categories = [];
downloadCategories().then(() => categories = cacheCategories());
webhook.on('algorithms', event => {
switch (event) {
case 'push':
downloadCategories().then(() => categories = cacheCategories());
break;
}
});
router.route('/')
.get((req, res, next) => {
......
......@@ -4,21 +4,13 @@ import uuid from 'uuid';
import path from 'path';
import { GitHubApi } from '/apis';
import { execute } from '/common/util';
import webhook from '/common/webhook';
const router = express.Router();
const repoPath = path.resolve(__dirname, '..', 'public', 'tracers');
const getCodesPath = (...args) => path.resolve(__dirname, '..', 'public', 'codes', ...args);
const buildRelease = release => (
fs.pathExistsSync(repoPath) ?
execute(`git fetch && ! git diff-index --quiet ${release.target_commitish}`, repoPath) :
execute(`git clone git@github.com:algorithm-visualizer/tracers ${repoPath}`, __dirname)
).then(() => execute(`git reset --hard ${release.target_commitish} && npm install && npm run build && ./bin/build`, repoPath));
GitHubApi.getLatestRelease('algorithm-visualizer', 'tracers').then(buildRelease);
// TODO: build release when webhooked
const getJsWorker = (req, res, next) => {
res.sendFile(path.resolve(repoPath, 'src', 'languages', 'js', 'tracers', 'build', 'tracers.js'));
};
......@@ -34,6 +26,21 @@ const trace = lang => (req, res, next) => {
.finally(() => fs.remove(tempPath));
};
const buildRelease = release => (
fs.pathExistsSync(repoPath) ?
execute(`git fetch && ! git diff-index --quiet ${release.target_commitish}`, repoPath) :
execute(`git clone git@github.com:algorithm-visualizer/tracers ${repoPath}`, __dirname)
).then(() => execute(`git reset --hard ${release.target_commitish} && npm install && npm run build && ./bin/build`, repoPath));
GitHubApi.getLatestRelease('algorithm-visualizer', 'tracers').then(buildRelease);
webhook.on('tracers', (event, data) => {
switch (event) {
case 'release':
buildRelease(data.release);
break;
}
});
router.route('/js')
.get(getJsWorker);
......
......@@ -3,12 +3,8 @@ import morgan from 'morgan';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import * as controllers from '/controllers';
import {
ClientError,
ForbiddenError,
NotFoundError,
UnauthorizedError,
} from '/common/error';
import { ClientError, ForbiddenError, NotFoundError, UnauthorizedError } from '/common/error';
import webhook from '/common/webhook';
const app = express();
app.use(morgan('tiny'));
......@@ -16,6 +12,7 @@ app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
Object.keys(controllers).forEach(key => app.use(`/${key}`, controllers[key]));
app.use('/webhook', webhook);
app.use((req, res, next) => next(new NotFoundError()));
app.use((err, req, res, next) => {
const statusMap = [
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册