Archived. See git-service instead.
A proxy library for custom git deploy logic made for koa.
Note: The api is currently not fully tested and may be unstable. Working on tests.
npm install --save koa-git-smart-proxy
Looking at existing git deployment libraries for node, not many have good compatibility with koa.So instead of creating a compatibility layer for my application, I created a new library made for koa. All core functinality now lives in a seperate package.
I took insparation from existing packages for node; pushover, git-http-backend, gems for ruby; grack and the http protocol documentation for git.
Note: You can also use the static class method GitSmartProxy.middleware
.
Create a new GitSmartProxy instance, and wait till it's ready. Return a promise for the instance.
-
context
<koa.Context> Koa context. -
command
<GitCommand> Git RPC handler.
- <Promise>
A promise that resolves to a new instance of
GitSmartProxy
.
Bare usage.
const { spawn } = require('child_process');
const { createServer } = require('http');
const koa = require('koa');
const { create, ServiceType } = require('koa-git-smart-proxy');
const { resolve } = require('path');
function command(repo_path, command, args = []) {
const full_path = resolve(r);
return spawn('git', [command, ...args, '.'], {
cwd: full_path,
});
}
const app = new koa;
app.use(async(ctx) => {
const service = await create(ctx, command);
// Not found
if (!await service.exists()) {
return service.reject(404);
}
// Forbidden
if (service.service === ServiceType.UNKNOWN) {
return service.reject();
}
return service.accept();
});
const server = createServer(app.callback());
server.listen(3000, () => console.log('listening on port 3000'));
Note: You can also use the static class method GitSmartProxy.middleware
.
Creates a middleware attaching a new instance to context.
options
<MiddlewareOptions> Middleware options.
- <Function> A koa middleware function.
Bare usage (with auto deploy).
const { createServer } = require('http');
const koa = require('koa');
const { middleware } = require('koa-git-smart-proxy');
const {
GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;
const app = new koa;
app.use(middleware({
auto_deploy: true,
git: root_folder,
}));
const server = createServer(app.callback());
server.listen(3000, () => console.log('listening on port 3000'));
Bare usage (without auto deploy).
const { createServer } = require('http');
const koa = require('koa');
const { middleware } = require('koa-git-smart-proxy');
const {
GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;
const app = new koa;
app.use(middleware({
git: root_folder,
}));
app.use(async(ctx) => {
const {proxy} = ctx.state;
// Not found
if (!await proxy.exists()) {
return proxy.reject(404);
}
// Forbidden
if (proxy.service === ServiceType.UNKNOWN) {
return proxy.reject();
}
return proxy.accept();
});
const server = createServer(app.callback());
server.listen(3000, () => console.log('listening on port 3000'));
Throw errors to context (with auto deploy)
const { createServer } = require('http');
const koa = require('koa');
const { middleware } = require('koa-git-smart-proxy');
const {
GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;
const app = new koa;
app.use(async(ctx, next) => {
try {
await next();
// Error __may__ be thrown from middleware
} catch (err) {
console.log(err);
ctx.body = err.message;
ctx.status = err.status || 500;
}
})
app.use(middleware({
throw_errors: true,
auto_deploy: true,
git: root_folder,
}));
const server = createServer(app.callback());
server.listen(3000, () => console.log('listening on port 3000'));
Note: When creating new instances, use exported function create or static method create.
-
service
<ServiceType> Service type. Read-only. -
status
<RequestStatus> Request status. Read-only. -
metadata
<GitMetadata> Request metadata. -
repository
<String> Repoistory name/path.
-
onAccept
<Signal<T>> Dispatched when request is accepted. WhereT
(payload) is accepted path for repository. -
onReject
<Signal<T>> Dispatched when request is rejected. WhereT
(payload) is an object containing properties 'reason' and 'status'. -
onFinal
<Signal<T>> Dispatched when request is either accepted or rejected. Has no payload. -
onError
<Signal<T>> Dispatched when something goes wrong. WhereT
(payload) is an instance of Error.
Usage example. (With auto deploy)
/* ... init app and add middleware */
app.use(async(ctx, next) => {
const {proxy} = ctx.state;
// Fires only when accepted
proxy.onAccept.add((repo_path) => console.log('accepted %s', repo_path));
// Fires only when rejected
proxy.onReject.add(({reason, status}) =>
console.log('rejected with reason "%s" and status %s', reason, status));
// Fires either when rejected or accepted
proxy.onFinal.add(() => console.log('service is done'));
// Fires on any error in accept, reject or exists.
proxy.onError.add((err) => console.error(err));
});
Note: You can also use the exported create
function.
Create a new GitSmartProxy instance, and wait till it's ready. Return a promise for the instance.
-
context
<koa.Context> Koa context. -
command
<GitCommand> Git RPC handler.
- <Promise>
A promise that resolves to a new instance of
GitSmartProxy
.
Bare usage.
const { spawn } = require('child_process');
const { createServer } = require('http');
const koa = require('koa');
const { GitSmartProxy, ServiceType } = require('koa-git-smart-proxy');
const { resolve } = require('path');
function command(repo_path, command, args = []) {
const full_path = resolve(r);
return spawn('git', [command, ...args, '.'], {
cwd: full_path,
});
}
const app = new koa;
app.use(async(ctx) => {
const service = await GitSmartProxy.create(ctx, command);
// Not found
if (!await service.exists()) {
return service.reject(404);
}
// Forbidden
if (service.service === ServiceType.UNKNOWN) {
return service.reject();
}
return service.accept();
});
const server = createServer(app.callback());
server.listen(3000, () => console.log('listening on port 3000'));
Note: You can also use the exported middleware
function.
Creates a middleware attaching a new instance to context.
options
<MiddlewareOptions> Middleware options.
- <Function> A koa middleware function.
Bare usage (with auto deploy).
const { createServer } = require('http');
const koa = require('koa');
const { GitSmartProxy } = require('koa-git-smart-proxy');
const {
GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;
const app = new koa;
app.use(GitSmartProxy.middleware({
auto_deploy: true,
git: root_folder,
}));
const server = createServer(app.callback());
server.listen(3000, () => console.log('listening on port 3000'));
Bare usage (without auto deploy).
const { createServer } = require('http');
const koa = require('koa');
const { GitSmartProxy } = require('koa-git-smart-proxy');
const app = new koa;
app.use(GitSmartProxy.middleware({
git: root_folder,
}));
app.use(async(ctx) => {
const {proxy} = ctx.state;
// Not found
if (!await proxy.exists()) {
return proxy.reject(404);
}
// Forbidden
if (proxy.service === ServiceType.UNKNOWN) {
return proxy.reject();
}
return proxy.accept();
});
const server = createServer(app.callback());
server.listen(3000, () => console.log('listening on port 3000'));
Throw errors to context (with auto deploy)
const { createServer } = require('http');
const koa = require('koa');
const { GitSmartProxy } = require('koa-git-smart-proxy');
const {
GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;
const app = new koa;
app.use(async(ctx, next) => {
try {
await next();
// Error __may__ be thrown from middleware
} catch (err) {
console.log(err);
ctx.body = err.message;
ctx.status = err.status || 500;
}
})
app.use(GitSmartProxy.middleware({
throw_errors: true,
auto_deploy: true,
git: root_folder,
}));
const server = createServer(app.callback());
server.listen(3000, () => console.log('listening on port 3000'));
Accept the request for provided service.
alternative_path
<String> Optional alternative path where repository is stored.
- <Promise> An empty promise that resolves when processing is done.
Reject request to service. Optionally supplied with status code and reason.
-
status
<Number> HTTP Status code to set for response. Defaults to403
. -
reason
<String> Rejection reason. Defaults to text for status code.
- <Promise> An empty promise that resolves when processing is done.
Reject request to service. Supplied with a reason and optionally a status code.
-
reason
<String> Rejection reason. -
status
<Number> HTTP Status code to set for response. Defaults to403
.
- <Promise> An empty promise that resolves when processing is done.
Checks if repository exists.
- <String> Optional alternative path where repository is stored.
- <Promise> A promise that resolves to a boolean for whether or not repository exists.
Side-loades verbose messages to client.
Service types with values.
-
UNKNOWN
(0) Unknown service. -
INFO
(1) Advertise refs. -
PULL
(2) All forms of data fetch. -
PUSH
(3) All forms of data push. (Including setting tags)
Request stauts with values.
-
PENDING
(0) Request is still pending. -
ACCEPTED
(1) Request was accepted. -
REJECTED
(2) Request was rejected.
Request metadata. Only available for pull/push services.
-
want
<Array> An array containing what the client want. Pull only. -
have
<Array> An array containing what the client have. Pull only. -
ref
<Object> Push only. -
old_commit
<String> Old commit. Push only. -
new_commit
<String> New commit Push only. -
capebilities
<Array> An array containing the capebilities client want/have.
Middleware options.
-
git
<string> | <GitExecutableOptions> | <GitCommand> Can either be a string to the local root folder, a custom handler or an options object. Defaults toprocess.cwd()
. -
auto_deploy
<Boolean> Auto deployment accepts or rejects a request if no action is taken further down the middleware chain. No default. -
key_name
<String> Where to store instance inContext.state
. Defaults to'proxy'
. -
throw_errors
<Boolean> Throw errors on context. Defaults tofalse
.
A function returning stdin/stdout of a spawned git process.
-
repository
<String> Repository name. -
command
<String> Git command to execute. -
command_args
<Array> An array of arguments to pass to git instance.
- <GitCommandResult> An object containing stdin/stdout.
An object containing stdin/stdout of a git process.
-
stdin
<Writable> Process standard input. -
stdout
<Readable> Process standard output. -
stderr
<Readable> Process error output.
Options for customizing the default GitCommand
.
-
executable_path
<String> A path to the git executable. Defaults to'git'
. -
root_folder
<String> A path leading to the projects root folder. Will be resolved. Defaults toprocess.cwd()
.
In the below example, we statelessly authenticate with HTTP Basic Authenticatoin and check if both repo exist and service is available for user (or guest).
const koa = require('koa');
const passport = require('koa-passport');
const HttpStatus = ('http-status');
const { match } = require('koa-match');
const { middleware, ServiceType } = require('koa-git-smart-proxy');
const Models = require('./models');
// Get repository root folder
const {
REPOS_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;
// Create app
const app = new koa;
// Git services (Info, pull & push)
app.use(match({
path: ':username/:repository.git/:path(.*)?',
handlers: [
// Authenticate client
passport.initialize(),
passport.authenticate('basic'),
// Get repository
async(ctx, next) => {
const {username, repository} = ctx.params;
const repo = await Models.Repository.findByOwnerAndName(username, repository);
if (repo) {
ctx.state.repo = repo;
}
return next();
},
// Attach proxy
middleware({git: {root_folder} }),
// Validation
async(ctx) => {
const {proxy, repo, user} = ctx.state;
const {username, repository, path} = ctx.params;
// Redirect
if (!path) {
return ctx.redirect('back', `/${username}/${repository}/`);
}
// Not found
if (!repo) {
return proxy.reject(HttpStatus.NOT_FOUND);
}
// Unknown service
if (proxy.service === ServiceType.UNKNOWN) {
return proxy.reject();
}
// Unautrorized access
if (!await repo.checkService(proxy.service, user)) {
ctx.set('www-authenticate', `Basic`);
return proxy.reject(HttpStatus.UNAUTHORIZED);
}
// Repos are stored differently than url structure.
const repo_path = repo.get_path();
// #accept can be supplied with an absolute path
// or a path relative to root_folder.
return proxy.accept(repo_path);
}
]
}));
const server = createServer(app.callback());
server.listen(3000, () => console.log('listening on port 3000'));
This module includes a TypeScript
declaration file to enable auto complete in compatible editors and type
information for TypeScript projects. This module depends on the Node.js
types, so install @types/node
:
npm install --save-dev @types/node
MIT