Dev: serve versions from git
This commit is contained in:
parent
6015289565
commit
a7f5bae92a
66
git-serve-server.js
Normal file
66
git-serve-server.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const https = require('https');
|
||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const gitstatic = require("./git-serve");
|
||||||
|
const express = require("express");
|
||||||
|
|
||||||
|
const repository = '.git';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.get(/^\/.+/, gitstatic.route().repository(repository));
|
||||||
|
app.get(/\//, (req, res) => {
|
||||||
|
gitstatic.listAllCommits(repository, (err, commits) => {
|
||||||
|
console.log(err, commits);
|
||||||
|
|
||||||
|
res.send(
|
||||||
|
commits.map((commit) => {
|
||||||
|
return `<a href="/${commit.sha}/public/index.html" target="_blank"><span style="font-family: monospace;">${commit.sha.slice(0, 7)} - ${commit.date.toISOString()}</span></a> - <a href="https://github.com/morethanwords/tweb/commit/${commit.sha}" target="_blank">${commit.subject}</a><br>`;
|
||||||
|
}).join('')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const { networkInterfaces } = require('os');
|
||||||
|
const nets = networkInterfaces();
|
||||||
|
const results = {};
|
||||||
|
|
||||||
|
for(const name of Object.keys(nets)) {
|
||||||
|
for(const net of nets[name]) {
|
||||||
|
// Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses
|
||||||
|
if(net.family === 'IPv4' && !net.internal) {
|
||||||
|
if(!results[name]) {
|
||||||
|
results[name] = [];
|
||||||
|
}
|
||||||
|
results[name].push(net.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useHttp = false;
|
||||||
|
const server = useHttp ? http : https;
|
||||||
|
let options = {};
|
||||||
|
if(!useHttp) {
|
||||||
|
options.key = fs.readFileSync(__dirname + '/certs/server-key.pem');
|
||||||
|
options.cert = fs.readFileSync(__dirname + '/certs/server-cert.pem');
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = 3000;
|
||||||
|
const protocol = useHttp ? 'http' : 'https';
|
||||||
|
console.log('Listening port:', port);
|
||||||
|
function createServer(host) {
|
||||||
|
server.createServer(options, app).listen(port, host, () => {
|
||||||
|
console.log('Host:', `${protocol}://${host || 'localhost'}:${port}/`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const name in results) {
|
||||||
|
const ips = results[name];
|
||||||
|
for(const ip of ips) {
|
||||||
|
createServer(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createServer();
|
246
git-serve.js
Normal file
246
git-serve.js
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
// Thanks to https://github.com/mbostock/git-static
|
||||||
|
|
||||||
|
var child = require("child_process"),
|
||||||
|
mime = require("mime"),
|
||||||
|
path = require("path");
|
||||||
|
|
||||||
|
var shaRe = /^[0-9a-f]{40}$/,
|
||||||
|
emailRe = /^<.*@.*>$/;
|
||||||
|
|
||||||
|
function readBlob(repository, revision, file, callback) {
|
||||||
|
var git = child.spawn("git", ["cat-file", "blob", revision + ":" + file], {cwd: repository}),
|
||||||
|
data = [],
|
||||||
|
exit;
|
||||||
|
|
||||||
|
git.stdout.on("data", function(chunk) {
|
||||||
|
data.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
git.on("exit", function(code) {
|
||||||
|
exit = code;
|
||||||
|
});
|
||||||
|
|
||||||
|
git.on("close", function() {
|
||||||
|
if (exit > 0) return callback(error(exit));
|
||||||
|
callback(null, Buffer.concat(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
git.stdin.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.readBlob = readBlob;
|
||||||
|
|
||||||
|
exports.getBranches = function(repository, callback) {
|
||||||
|
child.exec("git branch -l", {cwd: repository}, function(error, stdout) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
callback(null, stdout.split(/\n/).slice(0, -1).map(function(s) { return s.slice(2); }));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getSha = function(repository, revision, callback) {
|
||||||
|
child.exec("git rev-parse '" + revision.replace(/'/g, "'\''") + "'", {cwd: repository}, function(error, stdout) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
callback(null, stdout.trim());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getBranchCommits = function(repository, callback) {
|
||||||
|
child.exec("git for-each-ref refs/heads/ --sort=-authordate --format='%(objectname)\t%(refname:short)\t%(authordate:iso8601)\t%(authoremail)'", {cwd: repository}, function(error, stdout) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
callback(null, stdout.split("\n").map(function(line) {
|
||||||
|
var fields = line.split("\t"),
|
||||||
|
sha = fields[0],
|
||||||
|
ref = fields[1],
|
||||||
|
date = new Date(fields[2]),
|
||||||
|
author = fields[3];
|
||||||
|
if (!shaRe.test(sha) || !date || !emailRe.test(author)) return;
|
||||||
|
return {
|
||||||
|
sha: sha,
|
||||||
|
ref: ref,
|
||||||
|
date: date,
|
||||||
|
author: author.substring(1, author.length - 1)
|
||||||
|
};
|
||||||
|
}).filter(function(commit) {
|
||||||
|
return commit;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getCommit = function(repository, revision, callback) {
|
||||||
|
if (arguments.length < 3) callback = revision, revision = null;
|
||||||
|
child.exec(shaRe.test(revision)
|
||||||
|
? "git log -1 --date=iso " + revision + " --format='%H\n%ad'"
|
||||||
|
: "git for-each-ref --count 1 --sort=-authordate 'refs/heads/" + (revision ? revision.replace(/'/g, "'\''") : "") + "' --format='%(objectname)\n%(authordate:iso8601)'", {cwd: repository}, function(error, stdout) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
var lines = stdout.split("\n"),
|
||||||
|
sha = lines[0],
|
||||||
|
date = new Date(lines[1]);
|
||||||
|
if (!shaRe.test(sha) || !date) return void callback(new Error("unable to get commit"));
|
||||||
|
callback(null, {
|
||||||
|
sha: sha,
|
||||||
|
date: date
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getRelatedCommits = function(repository, branch, sha, callback) {
|
||||||
|
if (!shaRe.test(sha)) return callback(new Error("invalid SHA: " + sha));
|
||||||
|
child.exec("git log --format='%H' '" + branch.replace(/'/g, "'\''") + "' | grep -C1 " + sha, {cwd: repository}, function(error, stdout) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
var shas = stdout.split(/\n/),
|
||||||
|
i = shas.indexOf(sha);
|
||||||
|
|
||||||
|
callback(null, {
|
||||||
|
previous: shas[i + 1],
|
||||||
|
next: shas[i - 1]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.listCommits = function(repository, sha1, sha2, callback) {
|
||||||
|
if (!shaRe.test(sha1)) return callback(new Error("invalid SHA: " + sha1));
|
||||||
|
if (!shaRe.test(sha2)) return callback(new Error("invalid SHA: " + sha2));
|
||||||
|
child.exec("git log --format='%H\t%ad' " + sha1 + ".." + sha2, {cwd: repository}, function(error, stdout) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
callback(null, stdout.split(/\n/).slice(0, -1).map(function(commit) {
|
||||||
|
var fields = commit.split(/\t/);
|
||||||
|
return {
|
||||||
|
sha: fields[0],
|
||||||
|
date: new Date(fields[1])
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {(repository: string, callback: (err: Error, commits?: {sha: string, date: Date, author: string, subject: string}[]) => void) => void} */
|
||||||
|
exports.listAllCommits = function(repository, callback) {
|
||||||
|
child.exec("git log --branches --format='%H\t%ad\t%an\t%s'", {cwd: repository}, function(error, stdout) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
callback(null, stdout.split(/\n/).slice(0, -1).map(function(commit) {
|
||||||
|
var fields = commit.split(/\t/);
|
||||||
|
return {
|
||||||
|
sha: fields[0],
|
||||||
|
date: new Date(fields[1]),
|
||||||
|
author: fields[2],
|
||||||
|
subject: fields[3]
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.listTree = function(repository, revision, callback) {
|
||||||
|
child.exec("git ls-tree -r " + revision, {cwd: repository}, function(error, stdout) {
|
||||||
|
if (error) return callback(error);
|
||||||
|
callback(null, stdout.split(/\n/).slice(0, -1).map(function(commit) {
|
||||||
|
var fields = commit.split(/\t/);
|
||||||
|
return {
|
||||||
|
sha: fields[0].split(/\s/)[2],
|
||||||
|
name: fields[1]
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.route = function() {
|
||||||
|
var repository = defaultRepository,
|
||||||
|
revision = defaultRevision,
|
||||||
|
file = defaultFile,
|
||||||
|
type = defaultType;
|
||||||
|
|
||||||
|
function route(request, response) {
|
||||||
|
var repository_,
|
||||||
|
revision_,
|
||||||
|
file_;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if ((repository_ = repository(request.url)) == null
|
||||||
|
|| (revision_ = revision(request.url)) == null
|
||||||
|
|| (file_ = file(request.url)) == null) return serveNotFound();
|
||||||
|
|
||||||
|
readBlob(repository_, revision_, file_, function(error, data) {
|
||||||
|
if (error) return error.code === 128 ? serveNotFound() : serveError(error);
|
||||||
|
response.writeHead(200, {
|
||||||
|
"Content-Type": type(file_),
|
||||||
|
"Cache-Control": "public, max-age=300"
|
||||||
|
});
|
||||||
|
response.end(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
function serveError(error) {
|
||||||
|
response.writeHead(500, {"Content-Type": "text/plain"});
|
||||||
|
response.end(error + "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function serveNotFound() {
|
||||||
|
response.writeHead(404, {"Content-Type": "text/plain"});
|
||||||
|
response.end("File not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
route.repository = function(_) {
|
||||||
|
if (!arguments.length) return repository;
|
||||||
|
repository = functor(_);
|
||||||
|
return route;
|
||||||
|
};
|
||||||
|
|
||||||
|
route.sha = // sha is deprecated; use revision instead
|
||||||
|
route.revision = function(_) {
|
||||||
|
if (!arguments.length) return revision;
|
||||||
|
revision = functor(_);
|
||||||
|
return route;
|
||||||
|
};
|
||||||
|
|
||||||
|
route.file = function(_) {
|
||||||
|
if (!arguments.length) return file;
|
||||||
|
file = functor(_);
|
||||||
|
return route;
|
||||||
|
};
|
||||||
|
|
||||||
|
route.type = function(_) {
|
||||||
|
if (!arguments.length) return type;
|
||||||
|
type = functor(_);
|
||||||
|
return route;
|
||||||
|
};
|
||||||
|
|
||||||
|
return route;
|
||||||
|
};
|
||||||
|
|
||||||
|
function functor(_) {
|
||||||
|
return typeof _ === "function" ? _ : function() { return _; };
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultRepository() {
|
||||||
|
return path.join(__dirname, "repository");
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultRevision(url) {
|
||||||
|
return decodeURIComponent(url.substring(1, url.indexOf("/", 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultFile(url) {
|
||||||
|
url = url.substring(url.indexOf("/", 1) + 1);
|
||||||
|
const pathIdx = url.indexOf('?');
|
||||||
|
if(pathIdx !== -1) {
|
||||||
|
url = url.slice(0, pathIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeURIComponent(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultType(file) {
|
||||||
|
var type = mime.getType(file) || "text/plain";
|
||||||
|
return text(type) ? type + "; charset=utf-8" : type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function text(type) {
|
||||||
|
return /^(text\/)|(application\/(javascript|json)|image\/svg$)/.test(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(code) {
|
||||||
|
var e = new Error;
|
||||||
|
// @ts-ignore
|
||||||
|
e.code = code;
|
||||||
|
return e;
|
||||||
|
}
|
31
package-lock.json
generated
31
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"mime": "^3.0.0",
|
||||||
"webpack-dev-server": "^3.11.2"
|
"webpack-dev-server": "^3.11.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -15390,14 +15391,14 @@
|
|||||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
},
|
},
|
||||||
"node_modules/mime": {
|
"node_modules/mime": {
|
||||||
"version": "2.4.4",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||||
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
|
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"mime": "cli.js"
|
"mime": "cli.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mime-db": {
|
"node_modules/mime-db": {
|
||||||
@ -25914,6 +25915,17 @@
|
|||||||
"readable-stream": "^2.0.1"
|
"readable-stream": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webpack-dev-middleware/node_modules/mime": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webpack-dev-server": {
|
"node_modules/webpack-dev-server": {
|
||||||
"version": "3.11.2",
|
"version": "3.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz",
|
||||||
@ -39180,9 +39192,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mime": {
|
"mime": {
|
||||||
"version": "2.4.4",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||||
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
|
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="
|
||||||
},
|
},
|
||||||
"mime-db": {
|
"mime-db": {
|
||||||
"version": "1.40.0",
|
"version": "1.40.0",
|
||||||
@ -47230,6 +47242,11 @@
|
|||||||
"errno": "^0.1.3",
|
"errno": "^0.1.3",
|
||||||
"readable-stream": "^2.0.1"
|
"readable-stream": "^2.0.1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"mime": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"mime": "^3.0.0",
|
||||||
"webpack-dev-server": "^3.11.2"
|
"webpack-dev-server": "^3.11.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user