"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.hostingConfig = exports.normalize = exports.resolveTargets = exports.validate = exports.extract = exports.filterExcept = exports.filterOnly = void 0; const colorette_1 = require("colorette"); const utils_1 = require("../utils"); const error_1 = require("../error"); const functional_1 = require("../functional"); const fsutils_1 = require("../fsutils"); const projectPath_1 = require("../projectPath"); const path = require("node:path"); const experiments = require("../experiments"); const logger_1 = require("../logger"); function matchingConfigs(configs, targets, assertMatches) { const matches = []; const [hasSite, hasTarget] = (0, functional_1.partition)(configs, (c) => "site" in c); for (const target of targets) { const siteMatch = hasSite.find((c) => c.site === target); const targetMatch = hasTarget.find((c) => c.target === target); if (siteMatch) { matches.push(siteMatch); } else if (targetMatch) { matches.push(targetMatch); } else if (assertMatches) { throw new error_1.FirebaseError(`Hosting site or target ${(0, colorette_1.bold)(target)} not detected in firebase.json`); } } return matches; } function filterOnly(configs, onlyString) { if (!onlyString) { return configs; } let onlyTargets = onlyString.split(","); if (onlyTargets.includes("hosting")) { return configs; } onlyTargets = onlyTargets .filter((target) => target.startsWith("hosting:")) .map((target) => target.replace("hosting:", "")); return matchingConfigs(configs, onlyTargets, true); } exports.filterOnly = filterOnly; function filterExcept(configs, exceptOption) { if (!exceptOption) { return configs; } const exceptTargets = exceptOption.split(","); if (exceptTargets.includes("hosting")) { return []; } const exceptValues = exceptTargets .filter((t) => t.startsWith("hosting:")) .map((t) => t.replace("hosting:", "")); const toReject = matchingConfigs(configs, exceptValues, false); return configs.filter((c) => !toReject.find((r) => c.site === r.site && c.target === r.target)); } exports.filterExcept = filterExcept; function extract(options) { const config = options.config.src; if (!config.hosting) { return []; } const assertOneTarget = (config) => { if (config.target && config.site) { throw new error_1.FirebaseError(`Hosting configs should only include either "site" or "target", not both.`); } }; if (!Array.isArray(config.hosting)) { const res = (0, utils_1.cloneDeep)(config.hosting); if (!res.target && !res.site) { res.site = options.site; } assertOneTarget(res); return [res]; } else { config.hosting.forEach(assertOneTarget); return (0, utils_1.cloneDeep)(config.hosting); } } exports.extract = extract; function validate(configs, options) { for (const config of configs) { validateOne(config, options); } } exports.validate = validate; function validateOne(config, options) { var _a, _b, _c, _d; const hasAnyStaticRewrites = !!((_a = config.rewrites) === null || _a === void 0 ? void 0 : _a.find((rw) => "destination" in rw)); const hasAnyDynamicRewrites = !!((_b = config.rewrites) === null || _b === void 0 ? void 0 : _b.find((rw) => !("destination" in rw))); const hasAnyRedirects = !!((_c = config.redirects) === null || _c === void 0 ? void 0 : _c.length); if (config.source && config.public) { throw new error_1.FirebaseError('Can only specify "source" or "public" in a Hosting config, not both'); } const root = experiments.isEnabled("webframeworks") ? config.source || config.public : config.public; const orSource = experiments.isEnabled("webframeworks") ? ' or "source"' : ""; if (!root && hasAnyStaticRewrites) { throw new error_1.FirebaseError(`Must supply a "public"${orSource} directory when using "destination" rewrites.`); } if (!root && !hasAnyDynamicRewrites && !hasAnyRedirects) { throw new error_1.FirebaseError(`Must supply a "public"${orSource} directory or at least one rewrite or redirect in each "hosting" config.`); } if (root && !(0, fsutils_1.dirExistsSync)((0, projectPath_1.resolveProjectPath)(options, root))) { logger_1.logger.debug(`Specified "${config.source ? "source" : "public"}" directory "${root}" does not exist; Deploy to Hosting site "${config.site || config.target || ""}" may fail or be empty.`); } const regionWithoutFunction = (rewrite) => typeof rewrite.region === "string" && typeof rewrite.function !== "string"; const violation = (_d = config.rewrites) === null || _d === void 0 ? void 0 : _d.find(regionWithoutFunction); if (violation) { throw new error_1.FirebaseError("Rewrites only support 'region' as a top-level field when 'function' is set as a string"); } if (config.i18n) { if (!root) { throw new error_1.FirebaseError(`Must supply a "public"${orSource} directory when using "i18n" configuration.`); } if (!config.i18n.root) { throw new error_1.FirebaseError('Must supply a "root" in "i18n" config.'); } const i18nPath = path.join(root, config.i18n.root); if (!(0, fsutils_1.dirExistsSync)((0, projectPath_1.resolveProjectPath)(options, i18nPath))) { (0, utils_1.logLabeledWarning)("hosting", `Couldn't find specified i18n root directory ${(0, colorette_1.bold)(config.i18n.root)} in public directory ${(0, colorette_1.bold)(root)}`); } } } function resolveTargets(configs, options) { return configs.map((config) => { const newConfig = (0, utils_1.cloneDeep)(config); if (config.site) { return newConfig; } if (!config.target) { throw new error_1.FirebaseError("Assertion failed: resolving hosting target of a site with no site name " + "or target name. This should have caused an error earlier", { exit: 2 }); } if (!options.project) { throw new error_1.FirebaseError("Assertion failed: options.project is not set. Commands depending on hosting.config should use requireProject", { exit: 2 }); } const matchingTargets = options.rc.requireTarget(options.project, "hosting", config.target); if (matchingTargets.length > 1) { throw new error_1.FirebaseError(`Hosting target ${(0, colorette_1.bold)(config.target)} is linked to multiple sites, ` + `but only one is permitted. ` + `To clear, run:\n\n ${(0, colorette_1.bold)(`firebase target:clear hosting ${config.target}`)}`); } newConfig.site = matchingTargets[0]; return newConfig; }); } exports.resolveTargets = resolveTargets; function isLegacyFunctionsRewrite(rewrite) { return "function" in rewrite && typeof rewrite.function === "string"; } function normalize(configs) { var _a; for (const config of configs) { config.rewrites = (_a = config.rewrites) === null || _a === void 0 ? void 0 : _a.map((rewrite) => { if (!("function" in rewrite)) { return rewrite; } if (isLegacyFunctionsRewrite(rewrite)) { const modern = Object.assign(Object.assign({}, rewrite), { function: { functionId: rewrite.function, } }); delete modern.region; if ("region" in rewrite && typeof rewrite.region === "string") { modern.function.region = rewrite.region; } if (rewrite.region) { modern.function.region = rewrite.region; } return modern; } return rewrite; }); } } exports.normalize = normalize; function hostingConfig(options) { if (!options.normalizedHostingConfig) { let configs = extract(options); configs = filterOnly(configs, options.only); configs = filterExcept(configs, options.except); normalize(configs); validate(configs, options); const resolved = resolveTargets(configs, options); options.normalizedHostingConfig = resolved; } return options.normalizedHostingConfig; } exports.hostingConfig = hostingConfig;