413 lines
16 KiB
JavaScript
413 lines
16 KiB
JavaScript
"use strict";
|
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
/**
|
|
* Experimental leg work of a Github - VFS adapter being used by some projects.
|
|
*/
|
|
const GitHubApi = require("github");
|
|
const objects_1 = require("@xblox/core/objects");
|
|
const Path_1 = require("../../model/Path");
|
|
const pathUtil = require("path");
|
|
const base64_1 = require("../../io/base64"); // gitdata.updateFile
|
|
const matcher_1 = require("../../fs/utils/matcher");
|
|
/**
|
|
* Enumeration for getTree item types. Defined by Github
|
|
* @link https://developer.github.com/v3/git/trees/
|
|
* @export
|
|
* @enum {string}
|
|
*/
|
|
var ETreeBlobNodeType;
|
|
(function (ETreeBlobNodeType) {
|
|
ETreeBlobNodeType[ETreeBlobNodeType["BLOB"] = 'blob'] = "BLOB";
|
|
ETreeBlobNodeType[ETreeBlobNodeType["TREE"] = 'tree'] = "TREE";
|
|
ETreeBlobNodeType[ETreeBlobNodeType["COMMIT"] = 'commit'] = "COMMIT";
|
|
})(ETreeBlobNodeType = exports.ETreeBlobNodeType || (exports.ETreeBlobNodeType = {}));
|
|
/**
|
|
* Class to retrieve a tree per user, owner and repo - name.
|
|
* This will fetch the required parent SHA automatically first.
|
|
* @link https://developer.github.com/v3/git/trees/
|
|
*
|
|
* @export
|
|
* @class Tree
|
|
*/
|
|
class Tree {
|
|
constructor(client, options) {
|
|
this.client = client;
|
|
this.options = options;
|
|
}
|
|
/**
|
|
* Before doing anything, we need the tree's SHA.
|
|
*
|
|
* @param {boolean} [allowCached=true]
|
|
* @returns {Promise<string>}
|
|
*
|
|
* @memberOf Tree
|
|
*/
|
|
init(allowCached = true) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
return new Promise((resolve, reject) => {
|
|
if (this._sha && allowCached) {
|
|
return resolve(this._sha);
|
|
}
|
|
this.client.repos.getBranch(this.options, (e, res) => {
|
|
if (e) {
|
|
return reject(e);
|
|
}
|
|
this._sha = res.data.commit.sha;
|
|
resolve(this._sha);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
//
|
|
// ─── DELETE ─────────────────────────────────────────────────────────────────────
|
|
//
|
|
parseDeleteOptions(options, path) {
|
|
const opts = options || {};
|
|
const parsedOptions = {};
|
|
parsedOptions.progress = opts.progress;
|
|
parsedOptions.conflictCallback = opts.conflictCallback;
|
|
parsedOptions.conflictSettings = opts.conflictSettings;
|
|
parsedOptions.debug = opts.debug;
|
|
parsedOptions.trash = opts.trash;
|
|
parsedOptions.matching = opts.matching;
|
|
if (!opts.filter) {
|
|
if (opts.matching) {
|
|
parsedOptions.filter = matcher_1.create(path, opts.matching);
|
|
}
|
|
else {
|
|
parsedOptions.filter = () => { return true; };
|
|
}
|
|
}
|
|
return parsedOptions;
|
|
}
|
|
/**
|
|
* Implement IVFS#delete
|
|
*
|
|
* @param {string} path
|
|
* @param {string} [message="remove file"]
|
|
* @returns {Promise<any[]>}
|
|
*
|
|
* @memberOf Tree
|
|
*/
|
|
rm(path, message = "remove file", options) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
options = options || this.parseDeleteOptions(options, path);
|
|
return new Promise((resolve, reject) => {
|
|
this.init().then((sha) => {
|
|
this.info(path, false).then((node) => {
|
|
let current = 0;
|
|
let total = 1;
|
|
// file
|
|
if (node.type === ETreeBlobNodeType.BLOB) {
|
|
let deleteArgs = this.extend({ path: path, message: message, sha: node.sha });
|
|
return this.client.repos.deleteFile(deleteArgs).then((res) => {
|
|
resolve([res]);
|
|
});
|
|
}
|
|
// folder, since a delete operation will alter the whole SHA on the tree,
|
|
// we have to then delete each node on by re-querying the tree's SHA subsequently
|
|
if (node.type === ETreeBlobNodeType.TREE) {
|
|
const ret = [];
|
|
const proceed = (nodes) => {
|
|
if (!nodes.length) {
|
|
resolve(ret);
|
|
return;
|
|
}
|
|
const next = nodes[0];
|
|
current++;
|
|
if (options.progress) {
|
|
options.progress(node.path, current, total, VFS.mapNode(next));
|
|
}
|
|
if (next.type === ETreeBlobNodeType.TREE) {
|
|
nodes.shift();
|
|
}
|
|
this.rm(next.path, message).then((res) => {
|
|
nodes.shift();
|
|
ret.push(...res);
|
|
proceed(nodes);
|
|
}).catch((e) => {
|
|
proceed(nodes);
|
|
});
|
|
};
|
|
this.children(path, true).then((nodes) => {
|
|
total = nodes.length;
|
|
proceed(nodes);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
info(path, allowCached = false) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
return new Promise((resolve, reject) => {
|
|
this.ls(true, allowCached).then((nodes) => {
|
|
const node = nodes.find((value, index, object) => {
|
|
return value.path === path;
|
|
});
|
|
if (node) {
|
|
resolve(node);
|
|
}
|
|
else {
|
|
reject('Cant find node for ' + path);
|
|
}
|
|
}).catch(reject);
|
|
});
|
|
});
|
|
}
|
|
children(path, allowCached = false) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
return new Promise((resolve, reject) => {
|
|
this.ls(true, allowCached).then((nodes) => {
|
|
const node = nodes.find((value, index, object) => {
|
|
return value.path === path;
|
|
});
|
|
if (node) {
|
|
const paths = nodes.map((node) => {
|
|
return node.path;
|
|
});
|
|
const ret = new Path_1.Path(node.path).getChildren(paths, true).map((childPath) => {
|
|
return nodes.find((value, index, object) => {
|
|
return value.path === childPath;
|
|
});
|
|
});
|
|
resolve(ret);
|
|
}
|
|
else {
|
|
reject('Cant find node for ' + path);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
childrenSync(path, nodes) {
|
|
const paths = nodes.map((node) => {
|
|
return node.path;
|
|
});
|
|
const ret = new Path_1.Path(path).getChildren(paths, true).map((childPath) => {
|
|
return nodes.find((value, index, object) => {
|
|
return value.path === childPath;
|
|
});
|
|
});
|
|
return ret;
|
|
}
|
|
ls(recursive = true, allowCached = false) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
return new Promise((resolve, reject) => {
|
|
if (this._tree && allowCached) {
|
|
return resolve(this._tree);
|
|
}
|
|
this.init(allowCached).then((sha) => {
|
|
this.client.gitdata.getTree(this.extend({
|
|
sha: this._sha,
|
|
recursive: recursive
|
|
}), (e, res) => {
|
|
if (e) {
|
|
return reject(e);
|
|
}
|
|
const tree = res.data.tree;
|
|
this._tree = tree;
|
|
resolve(tree);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
//
|
|
// ─── UTILS ──────────────────────────────────────────────────────────────────────
|
|
//
|
|
/**
|
|
* Most of the time, Github wants some basic parameters together with some API specific
|
|
* parameters like path. This function will clone the base options and then merges the
|
|
* extras.
|
|
*
|
|
* @param {*} args
|
|
* @returns {*}
|
|
*
|
|
* @memberOf Tree
|
|
*/
|
|
extend(args) {
|
|
return objects_1.mixin(objects_1.clone(this.options), args);
|
|
}
|
|
}
|
|
exports.Tree = Tree;
|
|
class VFS {
|
|
constructor(resource) {
|
|
this.resource = resource;
|
|
this.options = resource.options;
|
|
this.init();
|
|
}
|
|
extend(args) {
|
|
return objects_1.mixin(objects_1.clone(this.options), args);
|
|
}
|
|
/**
|
|
* Map internal node rep. to public INode
|
|
*
|
|
* @param {ITreeBlobNode} node
|
|
* @param {string} mount
|
|
* @param {IObjectLiteral} options
|
|
* @returns {INode}
|
|
*
|
|
* @memberOf VFS
|
|
*/
|
|
static mapNode(node, mount, options) {
|
|
const directory = node.type === ETreeBlobNodeType.TREE;
|
|
const parent = new Path_1.Path(node.path, false, false).getParentPath();
|
|
return {
|
|
path: './' + node.path,
|
|
sizeBytes: node.size,
|
|
size: node.size,
|
|
owner: {
|
|
user: null,
|
|
group: null
|
|
},
|
|
isDir: directory,
|
|
directory: directory,
|
|
mime: !directory ? 'file' : 'folder',
|
|
name: pathUtil.win32.basename(node.path),
|
|
fileType: directory ? 'folder' : 'file',
|
|
mount: mount,
|
|
parent: "./" + parent.toString(),
|
|
type: ''
|
|
};
|
|
}
|
|
get(path, options) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
return new Promise((resolve, reject) => {
|
|
let getContentParams = this.extend({ path: path, ref: this.options.branch });
|
|
this.client.repos.getContent(getContentParams, (err, res) => {
|
|
if (err) {
|
|
reject(err);
|
|
}
|
|
resolve(res.data);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
/**
|
|
* Impl. IVFS#set. This will update the file content.
|
|
*
|
|
* @TODOs
|
|
* - This will fetch the file's info by retrieving the whole tree for getting
|
|
* the required SHA
|
|
*
|
|
* @param {string} path
|
|
* @param {(String | Buffer)} content
|
|
* @param {IObjectLiteral} [options]
|
|
* @returns {Promise<string>}
|
|
*
|
|
* @memberOf VFS
|
|
*/
|
|
set(path, content, options) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
return new Promise((resolve, reject) => {
|
|
const tree = new Tree(this.client, this.resource.options);
|
|
// split and strip slashes
|
|
path = new Path_1.Path(path, false, false).toString();
|
|
tree.info(new Path_1.Path(path, false, false).toString(), false).then((node) => {
|
|
options = options || {};
|
|
let params = options;
|
|
params.message = options['message'] || "no message";
|
|
params.content = base64_1.encode(content);
|
|
params.sha = node.sha;
|
|
params.path = path;
|
|
let setContentParams = objects_1.mixin(this.extend({ path: path, ref: this.options.branch }), params);
|
|
this.client.repos.updateFile(setContentParams, (err, res) => {
|
|
if (err) {
|
|
reject(err);
|
|
}
|
|
resolve(res);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
ls(path, mount, options) {
|
|
return __awaiter(this, void 0, void 0, function* () {
|
|
return new Promise((resolve, reject) => {
|
|
const tree = new Tree(this.client, this.resource.options);
|
|
tree.ls(false).then((nodes) => {
|
|
const ret = nodes.map((node) => {
|
|
const newNode = VFS.mapNode(node, mount, options);
|
|
// for directories, we add all child nodes
|
|
if (newNode.directory) {
|
|
const children = tree.childrenSync(newNode.path, nodes);
|
|
if (children && children.length) {
|
|
newNode.children = children.map((child) => {
|
|
return VFS.mapNode(child, mount, options);
|
|
});
|
|
newNode._EX = true;
|
|
}
|
|
}
|
|
return newNode;
|
|
});
|
|
resolve(ret);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
init() {
|
|
this.client = new GitHubApi(this.resource.client || {
|
|
// optional
|
|
debug: false,
|
|
protocol: "https",
|
|
host: "api.github.com",
|
|
// pathPrefix: "/api/v3", // for some GHEs; none for GitHub
|
|
headers: {
|
|
"user-agent": "My-Cool-GitHub-App",
|
|
"Accept": "application/vnd.github.v3.raw"
|
|
},
|
|
followRedirects: true,
|
|
timeout: 5000
|
|
});
|
|
if (this.resource.auth) {
|
|
this.client.authenticate(this.resource.auth);
|
|
}
|
|
}
|
|
}
|
|
exports.VFS = VFS;
|
|
function test() {
|
|
let token = "5b86c11984717f8146756593644abe60785b5f";
|
|
token += '1f';
|
|
const owner = 'xblox';
|
|
const repo = 'xide';
|
|
const branch = 've';
|
|
const tFile = 'mixins';
|
|
try {
|
|
let github = new GitHubApi({
|
|
// optional
|
|
debug: false,
|
|
protocol: "https",
|
|
host: "api.github.com",
|
|
// pathPrefix: "/api/v3", // for some GHEs; none for GitHub
|
|
headers: {
|
|
"user-agent": "My-Cool-GitHub-App",
|
|
"Accept": "application/vnd.github.v3.raw"
|
|
},
|
|
followRedirects: true,
|
|
timeout: 5000
|
|
});
|
|
github.authenticate({
|
|
type: "oauth",
|
|
token: token
|
|
});
|
|
let t = new Tree(github, { owner: owner, repo: repo, branch: branch });
|
|
t.rm(tFile).then((res) => {
|
|
console.log('removed file ', res);
|
|
});
|
|
}
|
|
catch (e) {
|
|
console.error('e', e);
|
|
}
|
|
}
|
|
exports.test = test;
|
|
//# sourceMappingURL=Github.js.map
|