mirror of
https://github.com/zhigang1992/firebase-tools.git
synced 2026-01-12 17:22:36 +08:00
merge
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
var previews = require("../previews");
|
||||
var previews = require("../previews"); //eslint-disable-line
|
||||
|
||||
module.exports = function(client) {
|
||||
var loadCommand = function(name) {
|
||||
var cmd = require("./" + name);
|
||||
@@ -53,13 +54,6 @@ module.exports = function(client) {
|
||||
|
||||
client.help = loadCommand("help");
|
||||
|
||||
if (previews.kits) {
|
||||
client.kits = {
|
||||
install: loadCommand("kits-install"),
|
||||
uninstall: loadCommand("kits-uninstall"),
|
||||
};
|
||||
}
|
||||
|
||||
client.init = loadCommand("init");
|
||||
client.list = loadCommand("list");
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var clc = require("cli-color");
|
||||
|
||||
var Command = require("../command");
|
||||
var getProjectId = require("../getProjectId");
|
||||
var logger = require("../logger");
|
||||
var requirePermissions = require("../requirePermissions");
|
||||
var utils = require("../utils");
|
||||
var kits = require("../kits");
|
||||
|
||||
// TODO: add option for urlPath to be inserted or parse urlPath for name if needed
|
||||
module.exports = new Command("kits:install <githubRepo>")
|
||||
.option("-b, --branch <branch>", "repository branch to download from. Defaults to master")
|
||||
.option("-p, --path <path>", "custom path to kit configuration file. Defaults to kits.json")
|
||||
.option("--id <releaseId>", "release version to be installed. Defaults to latest")
|
||||
.before(requirePermissions, [
|
||||
/* TODO */
|
||||
])
|
||||
.action(function(githubRepo, options) {
|
||||
var projectId = getProjectId(options);
|
||||
var kit = githubRepo.split("/");
|
||||
|
||||
var kitOwner;
|
||||
var kitRepo;
|
||||
|
||||
if (kit.length > 1) {
|
||||
kitOwner = kit[0];
|
||||
kitRepo = kit[1];
|
||||
} else {
|
||||
kitOwner = "function-kits";
|
||||
kitRepo = kit[0];
|
||||
}
|
||||
|
||||
var githubConfig = {
|
||||
id: options.releaseId || "latest",
|
||||
owner: kitOwner,
|
||||
manifestPath: options.path || "kits.json",
|
||||
ref: options.branch || "master",
|
||||
repo: kitRepo,
|
||||
};
|
||||
|
||||
var gitRepo = kitOwner + "/" + kitRepo;
|
||||
var kitFunctions;
|
||||
var runtimeConfig;
|
||||
|
||||
return kits.prepareKitsUpload
|
||||
.retrieveFile(githubConfig)
|
||||
.then(function(result) {
|
||||
var kitConfig = JSON.parse(result);
|
||||
kitFunctions = kitConfig.functions;
|
||||
|
||||
utils.logSuccess(
|
||||
clc.green.bold("kits: ") + "Fetched configuration file from " + clc.bold(gitRepo)
|
||||
);
|
||||
utils.logBullet(clc.bold("We will now ask a series of questions to help set up your kit."));
|
||||
|
||||
return kits.prepareKitsConfig.prompt(githubConfig.repo, kitConfig.config);
|
||||
})
|
||||
.then(function(result) {
|
||||
runtimeConfig = result;
|
||||
|
||||
return kits.prepareKitsUpload.upload(projectId, githubConfig, runtimeConfig);
|
||||
})
|
||||
.then(function(sourceUploadUrl) {
|
||||
utils.logSuccess(clc.green.bold("kits: ") + "Completed configuration setup.");
|
||||
logger.debug(clc.bold("kits: ") + "Source uploaded to GCS bucket");
|
||||
utils.logBullet(
|
||||
"Deploying kit " + gitRepo + " as " + clc.bold(runtimeConfig.kitname + "...")
|
||||
);
|
||||
|
||||
return kits.deploy(kitFunctions, options, runtimeConfig, sourceUploadUrl);
|
||||
});
|
||||
});
|
||||
@@ -1,135 +0,0 @@
|
||||
"use strict";
|
||||
var clc = require("cli-color");
|
||||
var _ = require("lodash");
|
||||
|
||||
var Command = require("../command");
|
||||
var gcp = require("../gcp");
|
||||
var pollKits = require("../kits/pollKits");
|
||||
var getProjectId = require("../getProjectId");
|
||||
var prompt = require("../prompt");
|
||||
var requirePermissions = require("../requirePermissions");
|
||||
var utils = require("../utils");
|
||||
|
||||
var DEFAULT_REGION = gcp.cloudfunctions.DEFAULT_REGION;
|
||||
|
||||
function _getFunctions(dict, kitName) {
|
||||
return _.reduce(
|
||||
dict[kitName],
|
||||
function(funcs, func) {
|
||||
return _.concat(funcs, func.functions);
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
function _listKits(projectId) {
|
||||
return gcp.cloudfunctions.list(projectId, DEFAULT_REGION).then(function(functions) {
|
||||
return _.chain(functions)
|
||||
.filter(function(func) {
|
||||
return _.has(func, "labels.goog-kit-name");
|
||||
})
|
||||
.map(function(funcInfo) {
|
||||
return {
|
||||
kit: funcInfo.labels["goog-kit-name"],
|
||||
source: funcInfo.labels["goog-kit-source"],
|
||||
functions: funcInfo.functionName,
|
||||
};
|
||||
})
|
||||
.groupBy("kit")
|
||||
.value();
|
||||
});
|
||||
}
|
||||
|
||||
function _promptForKitsUninstall(choices, dict) {
|
||||
return prompt({}, [
|
||||
{
|
||||
type: "checkbox",
|
||||
name: "kitNames",
|
||||
message:
|
||||
"Which kits would you like to delete? " +
|
||||
"The source of each kit is listed after the kit name.",
|
||||
choices: prompt.convertLabeledListChoices(choices),
|
||||
},
|
||||
]).then(function(list) {
|
||||
if (_.isEmpty(list.kitNames)) {
|
||||
return utils.reject("Please select at least one kit to delete", {
|
||||
exit: 1,
|
||||
});
|
||||
}
|
||||
return _.chain(list.kitNames)
|
||||
.map(function(key) {
|
||||
return prompt.listLabelToValue(key, choices);
|
||||
})
|
||||
.map(function(kit) {
|
||||
return _getFunctions(dict, kit);
|
||||
})
|
||||
.value();
|
||||
});
|
||||
}
|
||||
|
||||
function _deleteKitFunctions(projectId, functions) {
|
||||
return Promise.all(
|
||||
_.map(functions, function(funcName) {
|
||||
return gcp.cloudfunctions.delete({
|
||||
projectId: projectId,
|
||||
region: DEFAULT_REGION,
|
||||
functionName: funcName,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = new Command("kits:uninstall [kitName]")
|
||||
.description("Command to uninstall function kit")
|
||||
.before(requirePermissions, [
|
||||
/* TODO */
|
||||
])
|
||||
.action(function(kitName, options) {
|
||||
var projectId = getProjectId(options);
|
||||
return _listKits(projectId)
|
||||
.then(function(dict) {
|
||||
if (_.isEmpty(dict)) {
|
||||
return utils.reject("There are no kits asssociated with your project.", { exit: 1 });
|
||||
}
|
||||
|
||||
if (kitName) {
|
||||
if (!dict[kitName]) {
|
||||
return utils.reject("Could not find kit named " + clc.bold(kitName), { exit: 1 });
|
||||
}
|
||||
return _getFunctions(dict, kitName);
|
||||
}
|
||||
var choices = _.map(dict, function(kit, key) {
|
||||
return {
|
||||
name: key,
|
||||
label: key + ": " + kit[0].source,
|
||||
checked: false,
|
||||
};
|
||||
});
|
||||
return _promptForKitsUninstall(choices, dict);
|
||||
})
|
||||
.then(function(funcsToDelete) {
|
||||
utils.logBullet(clc.cyan.bold("kits: ") + "Deleting kits now...");
|
||||
return _deleteKitFunctions(projectId, _.flatten(funcsToDelete));
|
||||
})
|
||||
.then(function(operations) {
|
||||
utils.logBullet(
|
||||
clc.cyan.bold("kits: ") + "Checking to make sure kits have been deleted safely..."
|
||||
);
|
||||
|
||||
var printSuccess = function(kits) {
|
||||
return utils.logSuccess(
|
||||
clc.green.bold("kits: ") +
|
||||
"Successfully deleted the following kit(s): " +
|
||||
clc.bold(_.uniq(kits))
|
||||
);
|
||||
};
|
||||
|
||||
var printFail = function(reason) {
|
||||
return utils.logWarning(
|
||||
clc.yellow.bold("kits: ") + "Failed to delete the following kit: " + reason
|
||||
);
|
||||
};
|
||||
|
||||
return pollKits(operations, printSuccess, printFail);
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var _ = require("lodash");
|
||||
var clc = require("cli-color");
|
||||
|
||||
var FirebaseError = require("../error");
|
||||
var functionsConfig = require("../functionsConfig");
|
||||
var gcp = require("../gcp");
|
||||
var getProjectId = require("../getProjectId");
|
||||
var pollKits = require("./pollKits");
|
||||
var utils = require("../utils");
|
||||
|
||||
var DEFAULT_REGION = gcp.cloudfunctions.DEFAULT_REGION;
|
||||
|
||||
function _getFunctionTrigger(cloudfunction, firebaseconfig, config) {
|
||||
if (cloudfunction.httpsTrigger) {
|
||||
return _.pick(cloudfunction, "httpsTrigger");
|
||||
} else if (cloudfunction.eventTrigger) {
|
||||
var trigger = cloudfunction.eventTrigger;
|
||||
var resource = "projects/_/buckets/" + firebaseconfig.storageBucket;
|
||||
if (config.OBJECT_PREFIX) {
|
||||
resource = resource + "/" + config.OBJECT_PREFIX;
|
||||
}
|
||||
trigger.resource = resource;
|
||||
return { eventTrigger: trigger };
|
||||
}
|
||||
return new FirebaseError("Could not parse function trigger, unknown trigger type.");
|
||||
}
|
||||
|
||||
function _deployKitFunctions(functions, options, config, sourceUploadUrl) {
|
||||
var projectId = getProjectId(options);
|
||||
|
||||
if (functions.constructor !== Array) {
|
||||
functions = [functions];
|
||||
}
|
||||
|
||||
return functionsConfig.getFirebaseConfig(options).then(function(firebaseconfig) {
|
||||
// TODO: Do we deal with nested functions? How would we deal with nested functions
|
||||
return Promise.all(
|
||||
_.map(functions, function(cloudfunction) {
|
||||
var functionTrigger = _getFunctionTrigger(cloudfunction, firebaseconfig, config);
|
||||
return gcp.cloudfunctions.create({
|
||||
entryPoint: cloudfunction.entryPoint,
|
||||
functionName: config.kitname + "-" + cloudfunction.name,
|
||||
labels: {
|
||||
"goog-kit-source": config.kitsource,
|
||||
"goog-kit-name": config.kitname,
|
||||
},
|
||||
projectId: projectId,
|
||||
region: DEFAULT_REGION,
|
||||
sourceUploadUrl: sourceUploadUrl,
|
||||
trigger: functionTrigger,
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function(functions, options, config, sourceUploadUrl) {
|
||||
return _deployKitFunctions(functions, options, config, sourceUploadUrl).then(function(
|
||||
operations
|
||||
) {
|
||||
var printSuccess = function() {
|
||||
return utils.logSuccess(clc.green.bold("kits: ") + "Your kit has successfully been deployed");
|
||||
};
|
||||
|
||||
var printFail = function(reason) {
|
||||
utils.logWarning("Your kit could not be deployed.");
|
||||
utils.logWarning(reason);
|
||||
return new FirebaseError("Your kit could not be deployed.");
|
||||
};
|
||||
return pollKits(operations, printSuccess, printFail);
|
||||
});
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
prepareKitsConfig: require("./prepareKitsConfig"),
|
||||
prepareKitsUpload: require("./prepareKitsUpload"),
|
||||
deploy: require("./deploy"),
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Wrapper around pollOperations.js specifically for polling functions deployed through kits
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var _ = require("lodash");
|
||||
|
||||
var gcp = require("../gcp");
|
||||
var pollOperations = require("../pollOperations");
|
||||
|
||||
function _pollKitFunctions(operations) {
|
||||
var pollFunction = gcp.cloudfunctions.check;
|
||||
var interval = 2 * 1000;
|
||||
|
||||
// Code from functions/release.js
|
||||
var retryCondition = function(result) {
|
||||
// The error codes from a Google.LongRunning operation follow google.rpc.Code format.
|
||||
|
||||
var retryableCodes = [
|
||||
1, // cancelled by client
|
||||
4, // deadline exceeded
|
||||
10, // aborted (typically due to concurrency issue)
|
||||
14, // unavailable
|
||||
];
|
||||
|
||||
if (_.includes(retryableCodes, result.error.code)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var success = function(op) {
|
||||
return Promise.resolve(_.last(op.func.split("/")).match(/[^-]*/)[0]);
|
||||
};
|
||||
var fail = function(op) {
|
||||
return Promise.reject({
|
||||
kit: _.last(op.func.split("/")).match(/[^-]*/)[0],
|
||||
reason: op.error.message,
|
||||
});
|
||||
};
|
||||
|
||||
return pollOperations.pollAndRetry(
|
||||
operations,
|
||||
pollFunction,
|
||||
interval,
|
||||
success,
|
||||
fail,
|
||||
retryCondition
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = function(operations, printSuccess, printFail) {
|
||||
return _pollKitFunctions(operations)
|
||||
.then(function(successes) {
|
||||
return printSuccess(successes);
|
||||
})
|
||||
.catch(function(reason) {
|
||||
// since poll operations uses Promise.all, will catch immediately on first failure
|
||||
return printFail(reason);
|
||||
});
|
||||
};
|
||||
@@ -1,94 +0,0 @@
|
||||
"use strict";
|
||||
var _ = require("lodash");
|
||||
var clc = require("cli-color");
|
||||
|
||||
var FirebaseError = require("../error");
|
||||
var prompt = require("../prompt");
|
||||
var utils = require("../utils");
|
||||
|
||||
function _promptForKitConfig(kitName, kitConfig) {
|
||||
var prompts = [];
|
||||
// TODO: name does not allow for dashes
|
||||
prompts.push({
|
||||
name: "kitname",
|
||||
type: "input",
|
||||
default: kitName,
|
||||
message: "What would you like to name this kit?",
|
||||
});
|
||||
|
||||
for (var i = 0; i < kitConfig.length; i++) {
|
||||
prompts.push({
|
||||
name: kitConfig[i].name,
|
||||
type: "input",
|
||||
default: kitConfig[i].default,
|
||||
message: kitConfig[i].label,
|
||||
});
|
||||
}
|
||||
return prompt({ kitsource: kitName }, prompts)
|
||||
.then(function(answers) {
|
||||
// prompting again if needed
|
||||
var promptAgains = _.chain(kitConfig)
|
||||
.filter(function(question) {
|
||||
var value = answers[question.name];
|
||||
return !("default" in question) && !value;
|
||||
})
|
||||
.map(function(needsAnswer) {
|
||||
utils.logWarning(
|
||||
clc.yellow.bold("kits: ") + clc.bold(needsAnswer.name) + " requires a value."
|
||||
);
|
||||
return {
|
||||
name: needsAnswer.name,
|
||||
type: "input",
|
||||
message: needsAnswer.label,
|
||||
};
|
||||
})
|
||||
.value();
|
||||
return prompt(answers, promptAgains);
|
||||
})
|
||||
.then(function(configList) {
|
||||
// checking that all values that need to be set are set and of the correct type
|
||||
return utils.promiseAllSettled(
|
||||
_.map(kitConfig, function(question) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var configValue = configList[question.name];
|
||||
// if value still isn't set
|
||||
if (!("default" in question) && !configValue) {
|
||||
reject(new FirebaseError("The following value needs to be set: " + question.name));
|
||||
}
|
||||
switch (question.type) {
|
||||
case "number":
|
||||
configList[question.name] = _.toNumber(configValue);
|
||||
break;
|
||||
case "boolean":
|
||||
// why is there no toBoolean in lodash
|
||||
configList[question.name] = configValue === "true";
|
||||
break;
|
||||
default:
|
||||
// keep as a string
|
||||
configList[question.name] = _.toString(configValue);
|
||||
}
|
||||
resolve(configList);
|
||||
});
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(function(allPrompts) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var failed = _.chain(allPrompts)
|
||||
.filter({ state: "rejected" })
|
||||
.map("reason")
|
||||
.value();
|
||||
var config = _.find(allPrompts, function(succeeded) {
|
||||
return succeeded.state === "fulfilled";
|
||||
});
|
||||
if (failed.length > 0) {
|
||||
return reject(new FirebaseError("The following values need to be set.\n" + failed));
|
||||
}
|
||||
return resolve(config.value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
prompt: _promptForKitConfig,
|
||||
};
|
||||
@@ -1,185 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var _ = require("lodash");
|
||||
var archiver = require("archiver");
|
||||
var clc = require("cli-color");
|
||||
var filesize = require("filesize");
|
||||
var fs = require("fs");
|
||||
var tar = require("tar");
|
||||
var path = require("path");
|
||||
var request = require("request");
|
||||
var tmp = require("tmp");
|
||||
|
||||
var fsAsync = require("../fsAsync");
|
||||
var api = require("../api");
|
||||
var FirebaseError = require("../error");
|
||||
var gcp = require("../gcp");
|
||||
var utils = require("../utils");
|
||||
|
||||
var DEFAULT_REGION = gcp.cloudfunctions.DEFAULT_REGION;
|
||||
var CONFIG_DEST_FILE = ".runtimeconfig.json";
|
||||
|
||||
var _pipeAsync = function(from, to) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
to.on("finish", resolve);
|
||||
to.on("error", reject);
|
||||
from.pipe(to);
|
||||
});
|
||||
};
|
||||
|
||||
function _retrieveFile(githubConfig) {
|
||||
var endpoint =
|
||||
"/repos/" +
|
||||
githubConfig.owner +
|
||||
"/" +
|
||||
githubConfig.repo +
|
||||
"/contents/" +
|
||||
githubConfig.manifestPath;
|
||||
return api
|
||||
.request("GET", endpoint, {
|
||||
auth: false,
|
||||
origin: "https://api.github.com",
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": githubConfig.repo + "-kitsIntaller",
|
||||
},
|
||||
})
|
||||
.then(function(result) {
|
||||
if (result.status !== 200) {
|
||||
return Promise.reject(
|
||||
new FirebaseError(
|
||||
githubConfig.path + " could not be retrieved for kit at " + githubConfig.repo
|
||||
)
|
||||
);
|
||||
}
|
||||
var buf = Buffer.from(result.body.content, "base64");
|
||||
return Promise.resolve(buf);
|
||||
})
|
||||
.catch(function(error) {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
function _downloadSource(githubConfig) {
|
||||
var owner = githubConfig.owner;
|
||||
var repo = githubConfig.repo;
|
||||
var ref = githubConfig.ref;
|
||||
var tmpDir = tmp.dirSync({ prefix: "kits-source-" }).name;
|
||||
|
||||
var endpoint = "/repos/" + owner + "/" + repo + "/tarball/" + ref;
|
||||
var download = request({
|
||||
url: "https://api.github.com" + endpoint,
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3.sha",
|
||||
"User-Agent": repo + "-kitsIntaller",
|
||||
},
|
||||
});
|
||||
var untar = tar.x({
|
||||
cwd: tmpDir,
|
||||
// GitHub embeds everything in a folder named as the git
|
||||
// <org>-<repo>-<hash>
|
||||
strip: 1,
|
||||
});
|
||||
return _pipeAsync(download, untar).then(
|
||||
function() {
|
||||
utils.logSuccess(clc.green.bold("kits: ") + "Fetched kits source code.");
|
||||
return tmpDir;
|
||||
},
|
||||
function(err) {
|
||||
throw new FirebaseError("There was an error with fetching the kit", {
|
||||
original: err,
|
||||
exit: 2,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scaffolding code. Adapted from prepareFunctionsUpload.js
|
||||
**/
|
||||
var _packageSource = function(sourceDir, githubContext, configValues) {
|
||||
var tmpFile = tmp.fileSync({ prefix: "kits-upload-", postfix: ".zip" }).name;
|
||||
var fileStream = fs.createWriteStream(tmpFile, {
|
||||
flags: "w",
|
||||
defaultEncoding: "binary",
|
||||
});
|
||||
var archive = archiver("zip");
|
||||
var archiveDone = _pipeAsync(archive, fileStream);
|
||||
|
||||
return fsAsync
|
||||
.readdirRecursive({
|
||||
path: sourceDir,
|
||||
ignore: [
|
||||
githubContext.manfiestPath /* kit.json */,
|
||||
CONFIG_DEST_FILE /* .runtimeconfig.json */,
|
||||
"node_modules",
|
||||
],
|
||||
})
|
||||
.then(function(files) {
|
||||
_.forEach(files, function(file) {
|
||||
archive.file(file.name, {
|
||||
name: path.relative(sourceDir, file.name),
|
||||
mode: file.mode,
|
||||
});
|
||||
});
|
||||
archive.append(JSON.stringify(configValues, null, 2), {
|
||||
name: CONFIG_DEST_FILE,
|
||||
mode: 420 /* 0o644 */,
|
||||
});
|
||||
archive.finalize();
|
||||
return archiveDone;
|
||||
})
|
||||
.then(
|
||||
function() {
|
||||
utils.logBullet(
|
||||
clc.cyan.bold("kits:") +
|
||||
" packaged kit source (" +
|
||||
filesize(archive.pointer()) +
|
||||
") for uploading"
|
||||
);
|
||||
return {
|
||||
file: tmpFile,
|
||||
stream: fs.createReadStream(tmpFile),
|
||||
size: archive.pointer(),
|
||||
};
|
||||
},
|
||||
function(err) {
|
||||
throw new FirebaseError(
|
||||
"Could not read source directory. Remove links and shortcuts and try again.",
|
||||
{
|
||||
original: err,
|
||||
exit: 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function _uploadSourceCode(projectId, source) {
|
||||
var fullUrl;
|
||||
return gcp.cloudfunctions
|
||||
.generateUploadUrl(projectId, DEFAULT_REGION)
|
||||
.then(function(uploadUrl) {
|
||||
fullUrl = uploadUrl;
|
||||
uploadUrl = _.replace(uploadUrl, "https://storage.googleapis.com", "");
|
||||
return gcp.storage.upload(source, uploadUrl);
|
||||
})
|
||||
.then(function() {
|
||||
return fullUrl;
|
||||
});
|
||||
}
|
||||
|
||||
function _upload(projectId, githubContext, options) {
|
||||
return _downloadSource(githubContext)
|
||||
.then(function(sourceDir) {
|
||||
return _packageSource(sourceDir, githubContext, options);
|
||||
})
|
||||
.then(function(source) {
|
||||
return _uploadSourceCode(projectId, source);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
retrieveFile: _retrieveFile,
|
||||
upload: _upload,
|
||||
};
|
||||
@@ -6,7 +6,6 @@ var configstore = require("./configstore");
|
||||
var previews = _.assign(
|
||||
{
|
||||
// insert previews here...
|
||||
kits: false,
|
||||
},
|
||||
configstore.get("previews")
|
||||
);
|
||||
|
||||
@@ -5,13 +5,13 @@ import DatabaseRemove from "../../database/remove";
|
||||
import { NodeSize, RemoveRemote } from "../../database/removeRemote";
|
||||
|
||||
class TestRemoveRemote implements RemoveRemote {
|
||||
public data: any;
|
||||
data: any;
|
||||
|
||||
constructor(data: any) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public deletePath(path: string): Promise<boolean> {
|
||||
deletePath(path: string): Promise<boolean> {
|
||||
if (path === "/") {
|
||||
this.data = null;
|
||||
return Promise.resolve(true);
|
||||
@@ -25,7 +25,7 @@ class TestRemoveRemote implements RemoveRemote {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
public prefetchTest(path: string): Promise<NodeSize> {
|
||||
prefetchTest(path: string): Promise<NodeSize> {
|
||||
const d = this._dataAtpath(path);
|
||||
if (!d) {
|
||||
return Promise.resolve(NodeSize.EMPTY);
|
||||
@@ -39,7 +39,7 @@ class TestRemoveRemote implements RemoveRemote {
|
||||
}
|
||||
}
|
||||
|
||||
public listPath(path: string): Promise<string[]> {
|
||||
listPath(path: string): Promise<string[]> {
|
||||
const d = this._dataAtpath(path);
|
||||
if (d) {
|
||||
return Promise.resolve(Object.keys(d));
|
||||
|
||||
@@ -11,7 +11,7 @@ describe("Queue", () => {
|
||||
|
||||
it("should be first-in-first-out", async () => {
|
||||
const order: string[] = [];
|
||||
const queue = new Queue<Task>({
|
||||
const queue = new Queue<Task, void>({
|
||||
handler: createHandler(order),
|
||||
concurrency: 1,
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ describe("Stack", () => {
|
||||
|
||||
it("should be first-in-last-out", async () => {
|
||||
const order: string[] = [];
|
||||
const queue = new Stack<Task>({
|
||||
const queue = new Stack<Task, void>({
|
||||
handler: createHandler(order),
|
||||
concurrency: 1,
|
||||
});
|
||||
@@ -30,7 +30,7 @@ describe("Stack", () => {
|
||||
|
||||
it("should not repeat completed tasks", async () => {
|
||||
const order: string[] = [];
|
||||
const queue = new Stack<Task>({
|
||||
const queue = new Stack<Task, void>({
|
||||
handler: createHandler(order),
|
||||
concurrency: 1,
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Throttler, ThrottlerOptions } from "../../throttler/throttler";
|
||||
const TEST_ERROR = new Error("foobar");
|
||||
|
||||
interface ThrottlerConstructor {
|
||||
new <T>(options: ThrottlerOptions<T>): Throttler<T>;
|
||||
new <T, R>(options: ThrottlerOptions<T, R>): Throttler<T, R>;
|
||||
}
|
||||
|
||||
const throttlerTest = (throttlerConstructor: ThrottlerConstructor) => {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Throttler, ThrottlerOptions } from "./throttler";
|
||||
|
||||
export class Queue<T> extends Throttler<T> {
|
||||
public cursor: number = 0;
|
||||
export class Queue<T, R> extends Throttler<T, R> {
|
||||
cursor: number = 0;
|
||||
|
||||
constructor(options: ThrottlerOptions<T>) {
|
||||
constructor(options: ThrottlerOptions<T, R>) {
|
||||
super(options);
|
||||
this.name = this.name || "queue";
|
||||
}
|
||||
|
||||
public hasWaitingTask(): boolean {
|
||||
hasWaitingTask(): boolean {
|
||||
return this.cursor !== this.total;
|
||||
}
|
||||
|
||||
public nextWaitingTaskIndex(): number {
|
||||
nextWaitingTaskIndex(): number {
|
||||
if (this.cursor >= this.total) {
|
||||
throw new Error("There is no more task in queue");
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Throttler, ThrottlerOptions } from "./throttler";
|
||||
|
||||
export class Stack<T> extends Throttler<T> {
|
||||
public lastTotal: number = 0;
|
||||
public stack: number[] = [];
|
||||
export class Stack<T, R> extends Throttler<T, R> {
|
||||
lastTotal: number = 0;
|
||||
stack: number[] = [];
|
||||
|
||||
constructor(options: ThrottlerOptions<T>) {
|
||||
constructor(options: ThrottlerOptions<T, R>) {
|
||||
super(options);
|
||||
this.name = this.name || "stack";
|
||||
}
|
||||
|
||||
public hasWaitingTask(): boolean {
|
||||
hasWaitingTask(): boolean {
|
||||
return this.lastTotal !== this.total || this.stack.length > 0;
|
||||
}
|
||||
|
||||
public nextWaitingTaskIndex(): number {
|
||||
nextWaitingTaskIndex(): number {
|
||||
while (this.lastTotal < this.total) {
|
||||
this.stack.push(this.lastTotal);
|
||||
this.lastTotal++;
|
||||
|
||||
@@ -6,14 +6,14 @@ function backoff(retryNumber: number, delay: number): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
function DEFAULT_HANDLER(task: any): Promise<any> {
|
||||
return (task as () => Promise<any>)();
|
||||
function DEFAULT_HANDLER<R>(task: any): Promise<R> {
|
||||
return (task as () => Promise<R>)();
|
||||
}
|
||||
|
||||
export interface ThrottlerOptions<T> {
|
||||
export interface ThrottlerOptions<T, R> {
|
||||
name?: string;
|
||||
concurrency?: number;
|
||||
handler?: (task: T) => Promise<any>;
|
||||
handler?: (task: T) => Promise<R>;
|
||||
retries?: number;
|
||||
backoff?: number;
|
||||
}
|
||||
@@ -31,34 +31,34 @@ export interface ThrottlerStats {
|
||||
elapsed: number;
|
||||
}
|
||||
|
||||
interface TaskData<T> {
|
||||
interface TaskData<T, R> {
|
||||
task: T;
|
||||
retryCount: number;
|
||||
wait?: { resolve: (result: any) => void; reject: (err: Error) => void };
|
||||
wait?: { resolve: (R: any) => void; reject: (err: Error) => void };
|
||||
}
|
||||
|
||||
export abstract class Throttler<T> {
|
||||
public name: string = "";
|
||||
public concurrency: number = 200;
|
||||
public handler: (task: T) => Promise<any> = DEFAULT_HANDLER;
|
||||
public active: number = 0;
|
||||
public complete: number = 0;
|
||||
public success: number = 0;
|
||||
public errored: number = 0;
|
||||
public retried: number = 0;
|
||||
public total: number = 0;
|
||||
public taskDataMap = new Map<number, TaskData<T>>();
|
||||
public waits: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];
|
||||
public min: number = 9999999999;
|
||||
public max: number = 0;
|
||||
public avg: number = 0;
|
||||
public retries: number = 0;
|
||||
public backoff: number = 200;
|
||||
public closed: boolean = false;
|
||||
public finished: boolean = false;
|
||||
public startTime: number = 0;
|
||||
export abstract class Throttler<T, R> {
|
||||
name: string = "";
|
||||
concurrency: number = 200;
|
||||
handler: (task: T) => Promise<any> = DEFAULT_HANDLER;
|
||||
active: number = 0;
|
||||
complete: number = 0;
|
||||
success: number = 0;
|
||||
errored: number = 0;
|
||||
retried: number = 0;
|
||||
total: number = 0;
|
||||
taskDataMap = new Map<number, TaskData<T, R>>();
|
||||
waits: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];
|
||||
min: number = 9999999999;
|
||||
max: number = 0;
|
||||
avg: number = 0;
|
||||
retries: number = 0;
|
||||
backoff: number = 200;
|
||||
closed: boolean = false;
|
||||
finished: boolean = false;
|
||||
startTime: number = 0;
|
||||
|
||||
constructor(options: ThrottlerOptions<T>) {
|
||||
constructor(options: ThrottlerOptions<T, R>) {
|
||||
if (options.name) {
|
||||
this.name = options.name;
|
||||
}
|
||||
@@ -82,14 +82,14 @@ export abstract class Throttler<T> {
|
||||
/**
|
||||
* @return `true` if there are unscheduled task waiting to be scheduled.
|
||||
*/
|
||||
public abstract hasWaitingTask(): boolean;
|
||||
abstract hasWaitingTask(): boolean;
|
||||
|
||||
/**
|
||||
* @return the index of the next task to schedule.
|
||||
*/
|
||||
public abstract nextWaitingTaskIndex(): number;
|
||||
abstract nextWaitingTaskIndex(): number;
|
||||
|
||||
public wait(): Promise<void> {
|
||||
wait(): Promise<void> {
|
||||
const p = new Promise<void>((resolve, reject) => {
|
||||
this.waits.push({ resolve, reject });
|
||||
});
|
||||
@@ -101,7 +101,7 @@ export abstract class Throttler<T> {
|
||||
* When the task is completed, resolve will be called with handler's result.
|
||||
* If this task fails after retries, reject will be called with the error.
|
||||
*/
|
||||
public add(task: T): void {
|
||||
add(task: T): void {
|
||||
this.addHelper(task);
|
||||
}
|
||||
|
||||
@@ -109,18 +109,18 @@ export abstract class Throttler<T> {
|
||||
* Add the task to the throttler and return a promise of handler's result.
|
||||
* If the task failed, both the promised returned by throttle and wait will reject.
|
||||
*/
|
||||
public run<R>(task: T): Promise<R> {
|
||||
return new Promise<R>((resolve, reject) => {
|
||||
run(task: T): Promise<R> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.addHelper(task, { resolve, reject });
|
||||
});
|
||||
}
|
||||
|
||||
public close(): boolean {
|
||||
close(): boolean {
|
||||
this.closed = true;
|
||||
return this.finishIfIdle();
|
||||
}
|
||||
|
||||
public process(): void {
|
||||
process(): void {
|
||||
if (this.finishIfIdle() || this.active >= this.concurrency || !this.hasWaitingTask()) {
|
||||
return;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ export abstract class Throttler<T> {
|
||||
this.handle(this.nextWaitingTaskIndex());
|
||||
}
|
||||
|
||||
public async handle(cursorIndex: number): Promise<void> {
|
||||
async handle(cursorIndex: number): Promise<void> {
|
||||
const taskData = this.taskDataMap.get(cursorIndex);
|
||||
if (!taskData) {
|
||||
throw new Error(`taskData.get(${cursorIndex}) does not exist`);
|
||||
@@ -183,7 +183,7 @@ export abstract class Throttler<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public stats(): ThrottlerStats {
|
||||
stats(): ThrottlerStats {
|
||||
return {
|
||||
max: this.max,
|
||||
min: this.min,
|
||||
@@ -198,7 +198,7 @@ export abstract class Throttler<T> {
|
||||
};
|
||||
}
|
||||
|
||||
public taskName(cursorIndex: number): string {
|
||||
taskName(cursorIndex: number): string {
|
||||
const taskData = this.taskDataMap.get(cursorIndex);
|
||||
if (!taskData) {
|
||||
return "finished task";
|
||||
@@ -208,7 +208,7 @@ export abstract class Throttler<T> {
|
||||
|
||||
private addHelper(
|
||||
task: T,
|
||||
wait?: { resolve: (result: any) => void; reject: (err: Error) => void }
|
||||
wait?: { resolve: (result: R) => void; reject: (err: Error) => void }
|
||||
): void {
|
||||
if (this.closed) {
|
||||
throw new Error("Cannot add a task to a closed throttler.");
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"rules": {
|
||||
"arrow-parens": true,
|
||||
"interface-name": false,
|
||||
"member-access": [true, "no-public"],
|
||||
"object-literal-key-quotes": [true, "as-needed"],
|
||||
"object-literal-sort-keys": false,
|
||||
"ordered-imports": [true, { "import-sources-order": "any" }],
|
||||
|
||||
Reference in New Issue
Block a user