firebase-tools
Version:
Command-Line Interface for Firebase
233 lines (232 loc) • 9.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.deploy = exports.isDeployingWebFramework = exports.TARGETS = exports.TARGET_PERMISSIONS = exports.VALID_DEPLOY_TARGETS = void 0;
const clc = require("colorette");
const logger_1 = require("../logger");
const api_1 = require("../api");
const colorette_1 = require("colorette");
const lodash_1 = require("lodash");
const projectUtils_1 = require("../projectUtils");
const utils_1 = require("../utils");
const error_1 = require("../error");
const track_1 = require("../track");
const lifecycleHooks_1 = require("./lifecycleHooks");
const experiments = require("../experiments");
const HostingTarget = require("./hosting");
const DatabaseTarget = require("./database");
const FirestoreTarget = require("./firestore");
const FunctionsTarget = require("./functions");
const StorageTarget = require("./storage");
const RemoteConfigTarget = require("./remoteconfig");
const ExtensionsTarget = require("./extensions");
const DataConnectTarget = require("./dataconnect");
const AppHostingTarget = require("./apphosting");
const AuthTarget = require("./auth");
const frameworks_1 = require("../frameworks");
const prepare_1 = require("./hosting/prepare");
const requirePermissions_1 = require("../requirePermissions");
const context_1 = require("./dataconnect/context");
exports.VALID_DEPLOY_TARGETS = [
"database",
"storage",
"firestore",
"functions",
"hosting",
"remoteconfig",
"extensions",
"dataconnect",
"apphosting",
"auth",
];
exports.TARGET_PERMISSIONS = {
database: ["firebasedatabase.instances.update"],
hosting: ["firebasehosting.sites.update"],
functions: [
"cloudfunctions.functions.list",
"cloudfunctions.functions.create",
"cloudfunctions.functions.get",
"cloudfunctions.functions.update",
"cloudfunctions.functions.delete",
"cloudfunctions.operations.get",
],
firestore: [
"datastore.indexes.list",
"datastore.indexes.create",
"datastore.indexes.update",
"datastore.indexes.delete",
],
storage: [
"firebaserules.releases.create",
"firebaserules.rulesets.create",
"firebaserules.releases.update",
],
remoteconfig: ["cloudconfig.configs.get", "cloudconfig.configs.update"],
dataconnect: [
"cloudsql.databases.create",
"cloudsql.databases.update",
"cloudsql.instances.connect",
"cloudsql.instances.create",
"cloudsql.instances.get",
"cloudsql.instances.list",
"cloudsql.instances.update",
"cloudsql.users.create",
"firebasedataconnect.connectors.create",
"firebasedataconnect.connectors.delete",
"firebasedataconnect.connectors.list",
"firebasedataconnect.connectors.update",
"firebasedataconnect.operations.get",
"firebasedataconnect.services.create",
"firebasedataconnect.services.delete",
"firebasedataconnect.services.update",
"firebasedataconnect.services.list",
"firebasedataconnect.schemas.create",
"firebasedataconnect.schemas.delete",
"firebasedataconnect.schemas.list",
"firebasedataconnect.schemas.update",
],
apphosting: [],
extensions: [],
auth: ["firebase.projects.update", "firebaseauth.configs.update"],
};
exports.TARGETS = {
hosting: HostingTarget,
database: DatabaseTarget,
firestore: FirestoreTarget,
functions: FunctionsTarget,
storage: StorageTarget,
remoteconfig: RemoteConfigTarget,
extensions: ExtensionsTarget,
dataconnect: DataConnectTarget,
apphosting: AppHostingTarget,
auth: AuthTarget,
};
const chain = async function (fns, context, options, payload) {
for (const latest of fns) {
await latest(context, options, payload);
}
};
const isDeployingWebFramework = (options) => {
const config = options.config.get("hosting");
if (!config)
return false;
const normalizedConfig = Array.isArray(config) ? config : [config];
const webFrameworksInConfig = normalizedConfig.filter((c) => c?.source);
if (webFrameworksInConfig.length === 0)
return false;
if (!options.only)
return true;
return options.only.split(",").some((it) => {
const [target, site] = it.split(":");
if (target !== "hosting")
return false;
if (!site)
return true;
return webFrameworksInConfig.some((c) => [c.site, c.target].includes(site));
});
};
exports.isDeployingWebFramework = isDeployingWebFramework;
const deploy = async function (targetNames, options, customContext = {}) {
const projectId = (0, projectUtils_1.needProjectId)(options);
const payload = {};
const context = Object.assign({ projectId }, customContext);
const predeploys = [];
const prepares = [];
const deploys = [];
const releases = [];
const postdeploys = [];
const startTime = Date.now();
if (targetNames.includes("hosting") && (0, exports.isDeployingWebFramework)(options)) {
experiments.assertEnabled("webframeworks", "deploy a web framework from source");
await (0, frameworks_1.prepareFrameworks)("deploy", targetNames, context, options);
}
if (targetNames.includes("hosting") && (0, prepare_1.hasPinnedFunctions)(options)) {
experiments.assertEnabled("pintags", "deploy a tagged function as a hosting rewrite");
if (!targetNames.includes("functions")) {
targetNames.unshift("functions");
try {
await (0, requirePermissions_1.requirePermissions)(options, exports.TARGET_PERMISSIONS["functions"]);
}
catch (e) {
if ((0, utils_1.isRunningInGithubAction)()) {
throw new error_1.FirebaseError("It looks like you are deploying a Hosting site along with Cloud Functions " +
"using a GitHub action version that did not include Cloud Functions " +
"permissions. Please reinstall the GitHub action with" +
clc.bold("firebase init hosting:github"), { original: e });
}
else {
throw e;
}
}
}
await (0, prepare_1.addPinnedFunctionsToOnlyString)(context, options);
}
for (const targetName of targetNames) {
const target = exports.TARGETS[targetName];
if (!target) {
return Promise.reject(new error_1.FirebaseError(`${(0, colorette_1.bold)(targetName)} is not a valid deploy target`));
}
predeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "predeploy"));
prepares.push(target.prepare);
if (!options.dryRun) {
deploys.push(target.deploy);
releases.push(target.release);
postdeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "postdeploy"));
}
}
logger_1.logger.info();
logger_1.logger.info((0, colorette_1.bold)((0, colorette_1.white)("===") + " Deploying to '" + projectId + "'..."));
logger_1.logger.info();
(0, utils_1.logBullet)("deploying " + (0, colorette_1.bold)(targetNames.join(", ")));
let result = "predeploys_error";
try {
await chain(predeploys, context, options, payload);
result = "prepares_error";
await chain(prepares, context, options, payload);
result = "deploys_error";
await chain(deploys, context, options, payload);
result = "releases_error";
await chain(releases, context, options, payload);
result = "postdeploys_error";
await chain(postdeploys, context, options, payload);
result = "success";
}
finally {
const baseParams = {
interactive: options.nonInteractive ? "false" : "true",
dry_run: options.dryRun ? "true" : "false",
result: result,
};
const duration = Date.now() - startTime;
const params = Object.assign({}, baseParams);
Object.keys(exports.TARGETS).reduce((accum, t) => {
accum[t] = "false";
return accum;
}, params);
for (const t of targetNames) {
params[t] = "true";
}
void (0, track_1.trackGA4)("product_deploy", params, duration);
const stats = context?.dataconnect?.deployStats;
if (stats) {
const fdcParams = (0, context_1.deployStatsParams)(stats);
void (0, track_1.trackGA4)("dataconnect_deploy", { ...fdcParams, ...baseParams }, duration);
}
}
const successMessage = options.dryRun ? "Dry run complete!" : "Deploy complete!";
logger_1.logger.info();
(0, utils_1.logSuccess)((0, colorette_1.bold)((0, colorette_1.underline)(successMessage)));
logger_1.logger.info();
const deployedHosting = (0, lodash_1.includes)(targetNames, "hosting");
logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)(options.project ?? "_", "/overview"));
if (deployedHosting) {
(0, lodash_1.each)(context.hosting?.deploys, (deploy) => {
logger_1.logger.info((0, colorette_1.bold)("Hosting URL:"), (0, utils_1.addSubdomain)((0, api_1.hostingOrigin)(), deploy.config.site));
});
const versionNames = context.hosting?.deploys.map((deploy) => deploy.version);
return { hosting: versionNames?.length === 1 ? versionNames[0] : versionNames };
}
else {
return { hosting: undefined };
}
};
exports.deploy = deploy;