"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Config = void 0; const _ = require("lodash"); const clc = require("colorette"); const fs = require("fs-extra"); const path = require("path"); const cjson = require("cjson"); const detectProjectRoot_1 = require("./detectProjectRoot"); const error_1 = require("./error"); const fsutils = require("./fsutils"); const prompt_1 = require("./prompt"); const projectPath_1 = require("./projectPath"); const utils = require("./utils"); const firebaseConfigValidate_1 = require("./firebaseConfigValidate"); const logger_1 = require("./logger"); const loadCJSON_1 = require("./loadCJSON"); const parseBoltRules = require("./parseBoltRules"); class Config { constructor(src, options = {}) { var _a; this.data = {}; this.defaults = {}; this.notes = {}; this.options = options; this.projectDir = this.options.projectDir || (0, detectProjectRoot_1.detectProjectRoot)(this.options); this._src = src; if (this._src.firebase) { this.defaults.project = this._src.firebase; utils.logWarning(clc.bold('"firebase"') + " key in firebase.json is deprecated. Run " + clc.bold("firebase use --add") + " instead"); } if ((_a = this._src) === null || _a === void 0 ? void 0 : _a.rules) { this._src.database = Object.assign(Object.assign({}, this._src.database), { rules: this._src.rules }); } Config.MATERIALIZE_TARGETS.forEach((target) => { if (_.get(this._src, target)) { _.set(this.data, target, this.materialize(target)); } }); if (this.projectDir && fsutils.dirExistsSync(this.path(Config.DEFAULT_FUNCTIONS_SOURCE))) { if (Array.isArray(this.get("functions"))) { if (!this.get("functions.[0].source")) { this.set("functions.[0].source", Config.DEFAULT_FUNCTIONS_SOURCE); } } else { if (!this.get("functions.source")) { this.set("functions.source", Config.DEFAULT_FUNCTIONS_SOURCE); } } } } materialize(target) { const val = _.get(this._src, target); if (typeof val === "string") { let out = this.parseFile(target, val); const segments = target.split("."); const lastSegment = segments[segments.length - 1]; if (Object.keys(out).length === 1 && out[lastSegment]) { out = out[lastSegment]; } return out; } else if (val !== null && typeof val === "object") { return val; } throw new error_1.FirebaseError('Parse Error: "' + target + '" must be object or import path', { exit: 1, }); } parseFile(target, filePath) { const fullPath = (0, projectPath_1.resolveProjectPath)(this.options, filePath); const ext = path.extname(filePath); if (!fsutils.fileExistsSync(fullPath)) { throw new error_1.FirebaseError("Parse Error: Imported file " + filePath + " does not exist", { exit: 1, }); } switch (ext) { case ".json": if (target === "database") { this.notes.databaseRules = "json"; } else if (target === "database.rules") { this.notes.databaseRulesFile = filePath; try { return fs.readFileSync(fullPath, "utf8"); } catch (e) { if (e.code === "ENOENT") { throw new error_1.FirebaseError(`File not found: ${fullPath}`, { original: e }); } throw e; } } return (0, loadCJSON_1.loadCJSON)(fullPath); case ".bolt": if (target === "database") { this.notes.databaseRules = "bolt"; } return parseBoltRules(fullPath); default: throw new error_1.FirebaseError("Parse Error: " + filePath + " is not of a supported config file type", { exit: 1 }); } } get src() { return this._src; } get(key, fallback) { return _.get(this.data, key, fallback); } set(key, value) { _.set(this._src, key, value); return _.set(this.data, key, value); } has(key) { return _.has(this.data, key); } path(pathName) { const outPath = path.normalize(path.join(this.projectDir, pathName)); if (path.relative(this.projectDir, outPath).includes("..")) { throw new error_1.FirebaseError(clc.bold(pathName) + " is outside of project directory", { exit: 1 }); } return outPath; } readProjectFile(p, options = {}) { options = options || {}; try { const content = fs.readFileSync(this.path(p), "utf8"); if (options.json) { return JSON.parse(content); } return content; } catch (e) { if (options.fallback) { return options.fallback; } if (e.code === "ENOENT") { throw new error_1.FirebaseError(`File not found: ${this.path(p)}`, { original: e }); } throw e; } } writeProjectFile(p, content) { if (typeof content !== "string") { content = JSON.stringify(content, null, 2) + "\n"; } fs.ensureFileSync(this.path(p)); fs.writeFileSync(this.path(p), content, "utf8"); } projectFileExists(p) { return fs.existsSync(this.path(p)); } deleteProjectFile(p) { fs.removeSync(this.path(p)); } askWriteProjectFile(p, content, force) { const writeTo = this.path(p); let next; if (fsutils.fileExistsSync(writeTo) && !force) { next = (0, prompt_1.promptOnce)({ type: "confirm", message: "File " + clc.underline(p) + " already exists. Overwrite?", default: false, }); } else { next = Promise.resolve(true); } return next.then((result) => { if (result) { this.writeProjectFile(p, content); utils.logSuccess("Wrote " + clc.bold(p)); } else { utils.logBullet("Skipping write of " + clc.bold(p)); } }); } static load(options, allowMissing) { const pd = (0, detectProjectRoot_1.detectProjectRoot)(options); const filename = options.configPath || Config.FILENAME; if (pd) { try { const filePath = path.resolve(pd, path.basename(filename)); const data = cjson.load(filePath); const validator = (0, firebaseConfigValidate_1.getValidator)(); const valid = validator(data); if (!valid && validator.errors) { for (const e of validator.errors) { logger_1.logger.debug((0, firebaseConfigValidate_1.getErrorMessage)(e)); } } return new Config(data, options); } catch (e) { throw new error_1.FirebaseError(`There was an error loading ${filename}:\n\n` + e.message, { exit: 1, }); } } if (allowMissing) { return null; } throw new error_1.FirebaseError("Not in a Firebase app directory (could not locate firebase.json)", { exit: 1, }); } } exports.Config = Config; Config.DEFAULT_FUNCTIONS_SOURCE = "functions"; Config.FILENAME = "firebase.json"; Config.MATERIALIZE_TARGETS = [ "database", "emulators", "extensions", "firestore", "functions", "hosting", "storage", "remoteconfig", ];