mirror of
https://github.com/zhigang1992/firebase-tools.git
synced 2026-04-30 04:45:27 +08:00
merge
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var previews = require("../previews");
|
var previews = require("../previews"); //eslint-disable-line
|
||||||
|
|
||||||
module.exports = function(client) {
|
module.exports = function(client) {
|
||||||
var loadCommand = function(name) {
|
var loadCommand = function(name) {
|
||||||
var cmd = require("./" + name);
|
var cmd = require("./" + name);
|
||||||
@@ -53,13 +54,6 @@ module.exports = function(client) {
|
|||||||
|
|
||||||
client.help = loadCommand("help");
|
client.help = loadCommand("help");
|
||||||
|
|
||||||
if (previews.kits) {
|
|
||||||
client.kits = {
|
|
||||||
install: loadCommand("kits-install"),
|
|
||||||
uninstall: loadCommand("kits-uninstall"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
client.init = loadCommand("init");
|
client.init = loadCommand("init");
|
||||||
client.list = loadCommand("list");
|
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(
|
var previews = _.assign(
|
||||||
{
|
{
|
||||||
// insert previews here...
|
// insert previews here...
|
||||||
kits: false,
|
|
||||||
},
|
},
|
||||||
configstore.get("previews")
|
configstore.get("previews")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import DatabaseRemove from "../../database/remove";
|
|||||||
import { NodeSize, RemoveRemote } from "../../database/removeRemote";
|
import { NodeSize, RemoveRemote } from "../../database/removeRemote";
|
||||||
|
|
||||||
class TestRemoveRemote implements RemoveRemote {
|
class TestRemoveRemote implements RemoveRemote {
|
||||||
public data: any;
|
data: any;
|
||||||
|
|
||||||
constructor(data: any) {
|
constructor(data: any) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public deletePath(path: string): Promise<boolean> {
|
deletePath(path: string): Promise<boolean> {
|
||||||
if (path === "/") {
|
if (path === "/") {
|
||||||
this.data = null;
|
this.data = null;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -25,7 +25,7 @@ class TestRemoveRemote implements RemoveRemote {
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public prefetchTest(path: string): Promise<NodeSize> {
|
prefetchTest(path: string): Promise<NodeSize> {
|
||||||
const d = this._dataAtpath(path);
|
const d = this._dataAtpath(path);
|
||||||
if (!d) {
|
if (!d) {
|
||||||
return Promise.resolve(NodeSize.EMPTY);
|
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);
|
const d = this._dataAtpath(path);
|
||||||
if (d) {
|
if (d) {
|
||||||
return Promise.resolve(Object.keys(d));
|
return Promise.resolve(Object.keys(d));
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe("Queue", () => {
|
|||||||
|
|
||||||
it("should be first-in-first-out", async () => {
|
it("should be first-in-first-out", async () => {
|
||||||
const order: string[] = [];
|
const order: string[] = [];
|
||||||
const queue = new Queue<Task>({
|
const queue = new Queue<Task, void>({
|
||||||
handler: createHandler(order),
|
handler: createHandler(order),
|
||||||
concurrency: 1,
|
concurrency: 1,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe("Stack", () => {
|
|||||||
|
|
||||||
it("should be first-in-last-out", async () => {
|
it("should be first-in-last-out", async () => {
|
||||||
const order: string[] = [];
|
const order: string[] = [];
|
||||||
const queue = new Stack<Task>({
|
const queue = new Stack<Task, void>({
|
||||||
handler: createHandler(order),
|
handler: createHandler(order),
|
||||||
concurrency: 1,
|
concurrency: 1,
|
||||||
});
|
});
|
||||||
@@ -30,7 +30,7 @@ describe("Stack", () => {
|
|||||||
|
|
||||||
it("should not repeat completed tasks", async () => {
|
it("should not repeat completed tasks", async () => {
|
||||||
const order: string[] = [];
|
const order: string[] = [];
|
||||||
const queue = new Stack<Task>({
|
const queue = new Stack<Task, void>({
|
||||||
handler: createHandler(order),
|
handler: createHandler(order),
|
||||||
concurrency: 1,
|
concurrency: 1,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Throttler, ThrottlerOptions } from "../../throttler/throttler";
|
|||||||
const TEST_ERROR = new Error("foobar");
|
const TEST_ERROR = new Error("foobar");
|
||||||
|
|
||||||
interface ThrottlerConstructor {
|
interface ThrottlerConstructor {
|
||||||
new <T>(options: ThrottlerOptions<T>): Throttler<T>;
|
new <T, R>(options: ThrottlerOptions<T, R>): Throttler<T, R>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const throttlerTest = (throttlerConstructor: ThrottlerConstructor) => {
|
const throttlerTest = (throttlerConstructor: ThrottlerConstructor) => {
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Throttler, ThrottlerOptions } from "./throttler";
|
import { Throttler, ThrottlerOptions } from "./throttler";
|
||||||
|
|
||||||
export class Queue<T> extends Throttler<T> {
|
export class Queue<T, R> extends Throttler<T, R> {
|
||||||
public cursor: number = 0;
|
cursor: number = 0;
|
||||||
|
|
||||||
constructor(options: ThrottlerOptions<T>) {
|
constructor(options: ThrottlerOptions<T, R>) {
|
||||||
super(options);
|
super(options);
|
||||||
this.name = this.name || "queue";
|
this.name = this.name || "queue";
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasWaitingTask(): boolean {
|
hasWaitingTask(): boolean {
|
||||||
return this.cursor !== this.total;
|
return this.cursor !== this.total;
|
||||||
}
|
}
|
||||||
|
|
||||||
public nextWaitingTaskIndex(): number {
|
nextWaitingTaskIndex(): number {
|
||||||
if (this.cursor >= this.total) {
|
if (this.cursor >= this.total) {
|
||||||
throw new Error("There is no more task in queue");
|
throw new Error("There is no more task in queue");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { Throttler, ThrottlerOptions } from "./throttler";
|
import { Throttler, ThrottlerOptions } from "./throttler";
|
||||||
|
|
||||||
export class Stack<T> extends Throttler<T> {
|
export class Stack<T, R> extends Throttler<T, R> {
|
||||||
public lastTotal: number = 0;
|
lastTotal: number = 0;
|
||||||
public stack: number[] = [];
|
stack: number[] = [];
|
||||||
|
|
||||||
constructor(options: ThrottlerOptions<T>) {
|
constructor(options: ThrottlerOptions<T, R>) {
|
||||||
super(options);
|
super(options);
|
||||||
this.name = this.name || "stack";
|
this.name = this.name || "stack";
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasWaitingTask(): boolean {
|
hasWaitingTask(): boolean {
|
||||||
return this.lastTotal !== this.total || this.stack.length > 0;
|
return this.lastTotal !== this.total || this.stack.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public nextWaitingTaskIndex(): number {
|
nextWaitingTaskIndex(): number {
|
||||||
while (this.lastTotal < this.total) {
|
while (this.lastTotal < this.total) {
|
||||||
this.stack.push(this.lastTotal);
|
this.stack.push(this.lastTotal);
|
||||||
this.lastTotal++;
|
this.lastTotal++;
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ function backoff(retryNumber: number, delay: number): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function DEFAULT_HANDLER(task: any): Promise<any> {
|
function DEFAULT_HANDLER<R>(task: any): Promise<R> {
|
||||||
return (task as () => Promise<any>)();
|
return (task as () => Promise<R>)();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ThrottlerOptions<T> {
|
export interface ThrottlerOptions<T, R> {
|
||||||
name?: string;
|
name?: string;
|
||||||
concurrency?: number;
|
concurrency?: number;
|
||||||
handler?: (task: T) => Promise<any>;
|
handler?: (task: T) => Promise<R>;
|
||||||
retries?: number;
|
retries?: number;
|
||||||
backoff?: number;
|
backoff?: number;
|
||||||
}
|
}
|
||||||
@@ -31,34 +31,34 @@ export interface ThrottlerStats {
|
|||||||
elapsed: number;
|
elapsed: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TaskData<T> {
|
interface TaskData<T, R> {
|
||||||
task: T;
|
task: T;
|
||||||
retryCount: number;
|
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> {
|
export abstract class Throttler<T, R> {
|
||||||
public name: string = "";
|
name: string = "";
|
||||||
public concurrency: number = 200;
|
concurrency: number = 200;
|
||||||
public handler: (task: T) => Promise<any> = DEFAULT_HANDLER;
|
handler: (task: T) => Promise<any> = DEFAULT_HANDLER;
|
||||||
public active: number = 0;
|
active: number = 0;
|
||||||
public complete: number = 0;
|
complete: number = 0;
|
||||||
public success: number = 0;
|
success: number = 0;
|
||||||
public errored: number = 0;
|
errored: number = 0;
|
||||||
public retried: number = 0;
|
retried: number = 0;
|
||||||
public total: number = 0;
|
total: number = 0;
|
||||||
public taskDataMap = new Map<number, TaskData<T>>();
|
taskDataMap = new Map<number, TaskData<T, R>>();
|
||||||
public waits: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];
|
waits: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];
|
||||||
public min: number = 9999999999;
|
min: number = 9999999999;
|
||||||
public max: number = 0;
|
max: number = 0;
|
||||||
public avg: number = 0;
|
avg: number = 0;
|
||||||
public retries: number = 0;
|
retries: number = 0;
|
||||||
public backoff: number = 200;
|
backoff: number = 200;
|
||||||
public closed: boolean = false;
|
closed: boolean = false;
|
||||||
public finished: boolean = false;
|
finished: boolean = false;
|
||||||
public startTime: number = 0;
|
startTime: number = 0;
|
||||||
|
|
||||||
constructor(options: ThrottlerOptions<T>) {
|
constructor(options: ThrottlerOptions<T, R>) {
|
||||||
if (options.name) {
|
if (options.name) {
|
||||||
this.name = 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.
|
* @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.
|
* @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) => {
|
const p = new Promise<void>((resolve, reject) => {
|
||||||
this.waits.push({ 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.
|
* 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.
|
* 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);
|
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.
|
* 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.
|
* If the task failed, both the promised returned by throttle and wait will reject.
|
||||||
*/
|
*/
|
||||||
public run<R>(task: T): Promise<R> {
|
run(task: T): Promise<R> {
|
||||||
return new Promise<R>((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.addHelper(task, { resolve, reject });
|
this.addHelper(task, { resolve, reject });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public close(): boolean {
|
close(): boolean {
|
||||||
this.closed = true;
|
this.closed = true;
|
||||||
return this.finishIfIdle();
|
return this.finishIfIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
public process(): void {
|
process(): void {
|
||||||
if (this.finishIfIdle() || this.active >= this.concurrency || !this.hasWaitingTask()) {
|
if (this.finishIfIdle() || this.active >= this.concurrency || !this.hasWaitingTask()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ export abstract class Throttler<T> {
|
|||||||
this.handle(this.nextWaitingTaskIndex());
|
this.handle(this.nextWaitingTaskIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(cursorIndex: number): Promise<void> {
|
async handle(cursorIndex: number): Promise<void> {
|
||||||
const taskData = this.taskDataMap.get(cursorIndex);
|
const taskData = this.taskDataMap.get(cursorIndex);
|
||||||
if (!taskData) {
|
if (!taskData) {
|
||||||
throw new Error(`taskData.get(${cursorIndex}) does not exist`);
|
throw new Error(`taskData.get(${cursorIndex}) does not exist`);
|
||||||
@@ -183,7 +183,7 @@ export abstract class Throttler<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public stats(): ThrottlerStats {
|
stats(): ThrottlerStats {
|
||||||
return {
|
return {
|
||||||
max: this.max,
|
max: this.max,
|
||||||
min: this.min,
|
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);
|
const taskData = this.taskDataMap.get(cursorIndex);
|
||||||
if (!taskData) {
|
if (!taskData) {
|
||||||
return "finished task";
|
return "finished task";
|
||||||
@@ -208,7 +208,7 @@ export abstract class Throttler<T> {
|
|||||||
|
|
||||||
private addHelper(
|
private addHelper(
|
||||||
task: T,
|
task: T,
|
||||||
wait?: { resolve: (result: any) => void; reject: (err: Error) => void }
|
wait?: { resolve: (result: R) => void; reject: (err: Error) => void }
|
||||||
): void {
|
): void {
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
throw new Error("Cannot add a task to a closed throttler.");
|
throw new Error("Cannot add a task to a closed throttler.");
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"arrow-parens": true,
|
"arrow-parens": true,
|
||||||
"interface-name": false,
|
"interface-name": false,
|
||||||
|
"member-access": [true, "no-public"],
|
||||||
"object-literal-key-quotes": [true, "as-needed"],
|
"object-literal-key-quotes": [true, "as-needed"],
|
||||||
"object-literal-sort-keys": false,
|
"object-literal-sort-keys": false,
|
||||||
"ordered-imports": [true, { "import-sources-order": "any" }],
|
"ordered-imports": [true, { "import-sources-order": "any" }],
|
||||||
|
|||||||
Reference in New Issue
Block a user