firebase-tools
Version:
Command-Line Interface for Firebase
173 lines (167 loc) • 8.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.command = void 0;
const colorette_1 = require("colorette");
const ora = require("ora");
const command_1 = require("../command");
const error_1 = require("../error");
const api_1 = require("../hosting/api");
const utils = require("../utils");
const requireAuth_1 = require("../requireAuth");
const logger_1 = require("../logger");
const projectUtils_1 = require("../projectUtils");
exports.command = new command_1.Command("hosting:clone <source> <targetChannel>")
.description("clone a version from one site to another")
.help(`<source> and <targetChannel> accept the following formats:
- [<projectId>:]<siteId>:<channelId>
- [<projectId>:]<siteId>@<versionId> (source only)
Site projects are automatically discovered if omitted.
For example, to copy the content for a site \`my-site\` from a preview channel \`staging\` to a \`live\` channel:
firebase hosting:clone my-site:staging my-site:live
To clone across different Firebase projects:
firebase hosting:clone site-a:live site-b:live --project=project-b
# or explicitly:
firebase hosting:clone project-a:site-a:live project-b:site-b:live`)
.before(requireAuth_1.requireAuth)
.action(async (source, targetChannel, options) => {
source = source || "";
targetChannel = targetChannel || "";
let sourceProjectId;
let sourceSiteId;
let sourceChannelId;
let sourceVersion;
if (source.includes("@")) {
const [sitePart, version] = source.split("@");
sourceVersion = version;
if (sitePart.includes(":")) {
[sourceProjectId, sourceSiteId] = sitePart.split(":");
}
else {
sourceSiteId = sitePart;
}
}
else {
const parts = source.split(":");
if (parts.length === 3) {
[sourceProjectId, sourceSiteId, sourceChannelId] = parts;
}
else if (parts.length === 2) {
[sourceSiteId, sourceChannelId] = parts;
}
}
let targetProjectId;
let targetSiteId;
let targetChannelId;
const targetParts = targetChannel.split(":");
if (targetParts.length === 3) {
[targetProjectId, targetSiteId, targetChannelId] = targetParts;
}
else if (targetParts.length === 2) {
[targetSiteId, targetChannelId] = targetParts;
}
if (!sourceSiteId || (!sourceChannelId && !sourceVersion)) {
throw new error_1.FirebaseError(`"${source}" is not a valid source. Must be in the form "[<project>:]<site>:<channel>" or "[<project>:]<site>@<version>"`);
}
if (!targetSiteId || !targetChannelId) {
throw new error_1.FirebaseError(`"${targetChannel}" is not a valid target channel. Must be in the form "[<project>:]<site>:<channel>" (to clone to the active website, use "live" as the channel).`);
}
[sourceProjectId, targetProjectId] = await Promise.all([
resolveProjectId(sourceSiteId, sourceProjectId, options),
resolveProjectId(targetSiteId, targetProjectId, options),
]);
targetChannelId = (0, api_1.normalizeName)(targetChannelId);
if (sourceChannelId) {
sourceChannelId = (0, api_1.normalizeName)(sourceChannelId);
}
const equalProjectIds = sourceProjectId === targetProjectId;
const equalSiteIds = sourceSiteId === targetSiteId;
const equalChannelIds = sourceChannelId === targetChannelId;
if (equalProjectIds && equalSiteIds && equalChannelIds) {
throw new error_1.FirebaseError(`Source and destination cannot be equal. Please pick a different source or destination.`);
}
let sourceVersionName;
if (sourceVersion) {
sourceVersionName = `projects/${sourceProjectId}/sites/${sourceSiteId}/versions/${sourceVersion}`;
}
else if (sourceChannelId) {
const sChannel = await (0, api_1.getChannel)(sourceProjectId, sourceSiteId, sourceChannelId);
if (!sChannel) {
throw new error_1.FirebaseError(`Could not find the channel ${(0, colorette_1.bold)(sourceChannelId)} for site ${(0, colorette_1.bold)(sourceSiteId)} in project ${(0, colorette_1.bold)(sourceProjectId)}.`);
}
sourceVersionName = sChannel.release?.version?.name;
if (!sourceVersionName) {
throw new error_1.FirebaseError(`Could not find a version on the channel ${(0, colorette_1.bold)(sourceChannelId)} for site ${(0, colorette_1.bold)(sourceSiteId)}.`);
}
}
if (!sourceVersionName) {
throw new error_1.FirebaseError(`Could not find a version to clone for site ${(0, colorette_1.bold)(sourceSiteId)}.`);
}
let tChannel = await (0, api_1.getChannel)(targetProjectId, targetSiteId, targetChannelId);
if (!tChannel) {
utils.logBullet(`could not find channel ${(0, colorette_1.bold)(targetChannelId)} in site ${(0, colorette_1.bold)(targetSiteId)}, creating it...`);
try {
tChannel = await (0, api_1.createChannel)(targetProjectId, targetSiteId, targetChannelId);
}
catch (e) {
throw new error_1.FirebaseError(`Could not create the channel ${(0, colorette_1.bold)(targetChannelId)} for site ${(0, colorette_1.bold)(targetSiteId)}.`, { original: e });
}
utils.logSuccess(`Created new channel ${targetChannelId}`);
try {
await (0, api_1.addAuthDomains)(targetProjectId, [tChannel.url]);
}
catch (e) {
utils.logLabeledWarning("hosting:clone", `Unable to add channel domain to Firebase Auth. Visit the Firebase Console at ${utils.consoleUrl(targetProjectId, "/authentication/providers")}`);
logger_1.logger.debug("[hosting] unable to add auth domain", e);
}
}
const currentTargetVersionName = tChannel.release?.version?.name;
if (equalProjectIds && equalSiteIds && sourceVersionName === currentTargetVersionName) {
utils.logSuccess(`Channels ${(0, colorette_1.bold)(sourceChannelId || sourceVersion || "")} and ${(0, colorette_1.bold)(targetChannel)} are serving identical versions. No need to clone.`);
return;
}
let targetVersionName = sourceVersionName;
const spinner = ora("Cloning site content...").start();
try {
if (!equalSiteIds || !equalProjectIds) {
const targetVersion = await (0, api_1.cloneVersion)(targetProjectId, targetSiteId, sourceVersionName, true);
if (!targetVersion) {
throw new error_1.FirebaseError(`Could not clone the version ${(0, colorette_1.bold)(sourceVersion || sourceVersionName)} for site ${(0, colorette_1.bold)(targetSiteId)}.`);
}
targetVersionName = targetVersion.name;
}
await (0, api_1.createRelease)(targetProjectId, targetSiteId, targetChannelId, targetVersionName);
}
catch (err) {
spinner.fail();
throw err;
}
spinner.succeed();
utils.logSuccess(`Site ${(0, colorette_1.bold)(sourceSiteId)} ${sourceChannelId ? "channel" : "version"} ${(0, colorette_1.bold)(sourceChannelId || sourceVersion || sourceVersionName)} has been cloned to site ${(0, colorette_1.bold)(targetSiteId)} channel ${(0, colorette_1.bold)(targetChannelId)}.`);
utils.logSuccess(`Channel URL (${targetChannelId}): ${tChannel.url}`);
});
function parseProjectId(name) {
const matches = /^projects\/([^/]+)/.exec(name);
return matches ? matches[1] || "" : "";
}
async function resolveProjectId(siteId, explicitProjectId, options) {
if (explicitProjectId) {
return explicitProjectId;
}
try {
const site = await (0, api_1.getSite)("-", siteId);
const projectId = parseProjectId(site.name);
if (projectId) {
return projectId;
}
}
catch (e) {
if (e instanceof error_1.FirebaseError && e.status === 404) {
throw new error_1.FirebaseError(`Could not find the site ${(0, colorette_1.bold)(siteId)}.`, {
original: e,
status: 404,
});
}
logger_1.logger.debug(`[hosting] unable to resolve project for site ${siteId} via site lookup:`, e);
}
return (0, projectUtils_1.needProjectId)(options);
}