UNPKG

firebase-tools

Version:
173 lines (167 loc) • 8.2 kB
"use strict"; 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); }