|
|
|
// Native
|
|
|
|
const path = require("path");
|
|
|
|
const url = require("url");
|
|
|
|
const childProcess = require("child_process");
|
|
|
|
|
|
|
|
// Packages
|
|
|
|
const fs = require("fs-promise");
|
|
|
|
const download = require("download");
|
|
|
|
const tmp = require("tmp-promise");
|
|
|
|
const isURL = require("is-url");
|
|
|
|
|
|
|
|
const cloneRepo = (parts, tmpDir) =>
|
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
let host;
|
|
|
|
|
|
|
|
switch (parts.type) {
|
|
|
|
case "GitLab":
|
|
|
|
host = `gitlab.com`;
|
|
|
|
break;
|
|
|
|
case "Bitbucket":
|
|
|
|
host = `bitbucket.org`;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
host = `github.com`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const url = `https://${host}/${parts.main}`;
|
|
|
|
const ref = parts.ref ||
|
|
|
|
(parts.type === "Bitbucket" ? "default" : "master");
|
|
|
|
const cmd = `git clone ${url} --single-branch ${ref}`;
|
|
|
|
|
|
|
|
childProcess.exec(cmd, { cwd: tmpDir.path }, (err, stdout) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(stdout);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const renameRepoDir = async (pathParts, tmpDir) => {
|
|
|
|
const tmpContents = await fs.readdir(tmpDir.path);
|
|
|
|
|
|
|
|
const oldTemp = path.join(tmpDir.path, tmpContents[0]);
|
|
|
|
const newTemp = path.join(tmpDir.path, pathParts.main.replace("/", "-"));
|
|
|
|
|
|
|
|
await fs.rename(oldTemp, newTemp);
|
|
|
|
tmpDir.path = newTemp;
|
|
|
|
|
|
|
|
return tmpDir;
|
|
|
|
};
|
|
|
|
|
|
|
|
const downloadRepo = async repoPath => {
|
|
|
|
const pathParts = gitPathParts(repoPath);
|
|
|
|
|
|
|
|
const tmpDir = await tmp.dir({
|
|
|
|
// We'll remove it manually once deployment is done
|
|
|
|
keep: true,
|
|
|
|
// Recursively remove directory when calling respective method
|
|
|
|
unsafeCleanup: true
|
|
|
|
});
|
|
|
|
|
|
|
|
let gitInstalled = true;
|
|
|
|
|
|
|
|
try {
|
|
|
|
await cloneRepo(pathParts, tmpDir);
|
|
|
|
} catch (err) {
|
|
|
|
gitInstalled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gitInstalled) {
|
|
|
|
return await renameRepoDir(pathParts, tmpDir);
|
|
|
|
}
|
|
|
|
|
|
|
|
let url;
|
|
|
|
|
|
|
|
switch (pathParts.type) {
|
|
|
|
case "GitLab": {
|
|
|
|
const ref = pathParts.ref ? `?ref=${pathParts.ref}` : "";
|
|
|
|
url = `https://gitlab.com/${pathParts.main}/repository/archive.tar` + ref;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "Bitbucket":
|
|
|
|
url = `https://bitbucket.org/${pathParts.main}/get/${pathParts.ref || "default"}.zip`;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
url = `https://api.github.com/repos/${pathParts.main}/tarball/${pathParts.ref}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
await download(url, tmpDir.path, {
|
|
|
|
extract: true
|
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
tmpDir.cleanup();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return await renameRepoDir(pathParts, tmpDir);
|
|
|
|
};
|
|
|
|
|
|
|
|
const capitalizePlatform = name => {
|
|
|
|
const names = {
|
|
|
|
github: "GitHub",
|
|
|
|
gitlab: "GitLab",
|
|
|
|
bitbucket: "Bitbucket"
|
|
|
|
};
|
|
|
|
|
|
|
|
return names[name];
|
|
|
|
};
|
|
|
|
|
|
|
|
const splittedURL = fullURL => {
|
|
|
|
const parsedURL = url.parse(fullURL);
|
|
|
|
const pathParts = parsedURL.path.split("/");
|
|
|
|
|
|
|
|
pathParts.shift();
|
|
|
|
|
|
|
|
// Set path to repo...
|
|
|
|
const main = pathParts[0] + "/" + pathParts[1];
|
|
|
|
|
|
|
|
// ...and then remove it from the parts
|
|
|
|
pathParts.splice(0, 2);
|
|
|
|
|
|
|
|
// Assign Git reference
|
|
|
|
let ref = pathParts.length >= 2 ? pathParts[1] : "";
|
|
|
|
|
|
|
|
// Firstly be sure that we haven know the ref type
|
|
|
|
if (pathParts[0]) {
|
|
|
|
// Then shorten the SHA of the commit
|
|
|
|
if (pathParts[0] === "commit" || pathParts[0] === "commits") {
|
|
|
|
ref = ref.substring(0, 7);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're deploying master by default,
|
|
|
|
// so there's no need to indicate it explicitly
|
|
|
|
if (ref === "master") {
|
|
|
|
ref = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
main,
|
|
|
|
ref,
|
|
|
|
type: capitalizePlatform(parsedURL.host.split(".")[0])
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const gitPathParts = main => {
|
|
|
|
let ref = "";
|
|
|
|
|
|
|
|
if (isURL(main)) {
|
|
|
|
return splittedURL(main);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (main.split("/")[1].includes("#")) {
|
|
|
|
const parts = main.split("#");
|
|
|
|
|
|
|
|
ref = parts[1];
|
|
|
|
main = parts[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
main,
|
|
|
|
ref,
|
|
|
|
type: capitalizePlatform("github")
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const isRepoPath = path => {
|
|
|
|
if (!path) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const allowedHosts = ["github.com", "gitlab.com", "bitbucket.org"];
|
|
|
|
|
|
|
|
if (isURL(path)) {
|
|
|
|
const urlParts = url.parse(path);
|
|
|
|
const slashSplitted = urlParts.path.split("/").filter(n => n);
|
|
|
|
const notBare = slashSplitted.length >= 2;
|
|
|
|
|
|
|
|
if (allowedHosts.includes(urlParts.host) && notBare) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return "no-valid-url";
|
|
|
|
}
|
|
|
|
|
|
|
|
return /[^\s\\]\/[^\s\\]/g.test(path);
|
|
|
|
};
|
|
|
|
|
|
|
|
const fromGit = async (path, debug) => {
|
|
|
|
let tmpDir = false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
tmpDir = await downloadRepo(path);
|
|
|
|
} catch (err) {
|
|
|
|
if (debug) {
|
|
|
|
console.log(`Could not download "${path}" repo from GitHub`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return tmpDir;
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
gitPathParts,
|
|
|
|
isRepoPath,
|
|
|
|
fromGit
|
|
|
|
};
|