firebase-tools
Version:
Command-Line Interface for Firebase
246 lines (245 loc) • 10.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.KIT_PREFIX = exports.DEFAULT_CODEBASE = void 0;
exports.normalize = normalize;
exports.validateCodebase = validateCodebase;
exports.validateKit = validateKit;
exports.validatePrefix = validatePrefix;
exports.assertUnique = assertUnique;
exports.validateKitInstanceId = validateKitInstanceId;
exports.validateAndAddKitInstances = validateAndAddKitInstances;
exports.validate = validate;
exports.normalizeAndValidate = normalizeAndValidate;
exports.configForCodebase = configForCodebase;
exports.isLocalConfig = isLocalConfig;
exports.isRemoteConfig = isRemoteConfig;
exports.isKitConfig = isKitConfig;
exports.addKitPrefix = addKitPrefix;
exports.requireLocal = requireLocal;
exports.resolveConfigDir = resolveConfigDir;
exports.shouldUseRuntimeConfig = shouldUseRuntimeConfig;
const error_1 = require("../error");
const experiments = require("../experiments");
exports.DEFAULT_CODEBASE = "default";
function normalize(config) {
if (!config) {
throw new error_1.FirebaseError("No valid functions configuration detected in firebase.json");
}
if (Array.isArray(config)) {
if (config.length < 1) {
throw new error_1.FirebaseError("Requires at least one functions.source in firebase.json.");
}
return config;
}
return [config];
}
function validateCodebase(codebase) {
if (codebase.length === 0 || codebase.length > 63 || !/^[a-z0-9_-]+$/.test(codebase)) {
throw new error_1.FirebaseError("Invalid codebase name. Codebase must be less than 64 characters and " +
"can contain only lowercase letters, numeric characters, underscores, and dashes.");
}
}
function validateKit(kit) {
if (kit.length === 0 || kit.length > 40 || !/^[a-z0-9_-]+$/.test(kit)) {
throw new error_1.FirebaseError("Invalid kit name. Kit name must be 40 characters or less and " +
"can contain only lowercase letters, numeric characters, underscores, and dashes.");
}
}
function validatePrefix(prefix) {
if (prefix.length > 30) {
throw new error_1.FirebaseError("Invalid prefix. Prefix must be 30 characters or less.");
}
if (!/^[a-z](?:[a-z0-9-]*[a-z0-9])?$/.test(prefix)) {
throw new error_1.FirebaseError("Invalid prefix. Prefix must start with a lowercase letter, can contain only lowercase letters, numeric characters, and dashes, and cannot start or end with a dash.");
}
}
function validateSingle(config) {
if ("kit" in config) {
experiments.assertEnabled("kits", "use functions kits");
if (!config.kit) {
throw new error_1.FirebaseError("Must specify 'kit' name in a functions kit config.");
}
validateKit(config.kit);
if (config.codebase) {
throw new error_1.FirebaseError("Cannot specify both 'kit' and 'codebase' in a single functions config.");
}
if (config.remoteSource) {
throw new error_1.FirebaseError("Cannot specify both 'kit' and 'remoteSource' in a single functions config.");
}
if (config.prefix) {
throw new error_1.FirebaseError("Cannot specify 'prefix' in a functions kit config.");
}
if (!config.source) {
throw new error_1.FirebaseError("Must specify 'source' in a functions kit config.");
}
if (!config.instances ||
typeof config.instances !== "object" ||
Array.isArray(config.instances)) {
throw new error_1.FirebaseError("Must specify 'instances' as an object mapping instance IDs to configuration paths in a functions kit config.");
}
return config;
}
const { source, remoteSource, runtime, codebase: providedCodebase, ...rest } = config;
if (source && remoteSource) {
throw new error_1.FirebaseError("Cannot specify both 'source' and 'remoteSource' in a single functions config. Please choose one.");
}
if (!source && !remoteSource) {
throw new error_1.FirebaseError("codebase source must be specified. Must specify either 'source' or 'remoteSource' in a functions config.");
}
const codebase = providedCodebase ?? exports.DEFAULT_CODEBASE;
validateCodebase(codebase);
if (config.prefix) {
validatePrefix(config.prefix);
}
const commonConfig = { codebase, ...rest };
if (source) {
return {
...commonConfig,
source,
...(runtime ? { runtime } : {}),
};
}
else if (remoteSource) {
if (!remoteSource.repository || !remoteSource.ref) {
throw new error_1.FirebaseError("remoteSource requires 'repository' and 'ref' to be specified.");
}
if (!runtime) {
throw new error_1.FirebaseError("functions.runtime is required when using remoteSource in firebase.json.");
}
return {
...commonConfig,
remoteSource,
runtime,
};
}
throw new error_1.FirebaseError("Invalid functions config.");
}
function assertUnique(config, property, propval) {
const values = new Set();
if (propval) {
values.add(propval);
}
for (const single of config) {
const value = single[property];
if (value !== undefined) {
if (values.has(value)) {
throw new error_1.FirebaseError(`functions.${String(property)} must be unique but '${String(value)}' was used more than once.`);
}
values.add(value);
}
}
}
function assertUniqueSourcePrefixPair(config) {
const sourcePrefixPairs = new Set();
for (const c of config) {
let sourceIdentifier;
let sourceDescription;
if (c.source) {
sourceIdentifier = c.source;
sourceDescription = `source directory ('${c.source}')`;
}
else if (c.remoteSource) {
sourceIdentifier = `remote:${c.remoteSource.repository}#${c.remoteSource.ref}@dir:${c.remoteSource.dir || "."}`;
sourceDescription = `remote source ('${c.remoteSource.repository}')`;
}
else {
continue;
}
const key = JSON.stringify({ source: sourceIdentifier, prefix: c.prefix || "" });
if (sourcePrefixPairs.has(key)) {
throw new error_1.FirebaseError(`More than one functions config specifies the same ${sourceDescription} and prefix ('${c.prefix ?? ""}'). Please add a unique 'prefix' to each function configuration that shares this source to resolve the conflict.`);
}
sourcePrefixPairs.add(key);
}
}
function validateKitInstanceId(instanceId) {
if (instanceId.length === 0 ||
instanceId.length > 40 ||
!/^[a-z0-9_](?:[a-z0-9_-]*[a-z0-9_])?$/.test(instanceId)) {
throw new error_1.FirebaseError(`Invalid kit instance ID '${instanceId}'. Instance ID must be 40 characters or less, ` +
"can contain only lowercase letters, numeric characters, underscores, and dashes, and cannot start or end with a dash.");
}
}
function validateAndAddKitInstances(instanceIds, allProjectInstanceIds) {
const seenInBatch = new Set();
for (const instanceId of instanceIds) {
validateKitInstanceId(instanceId);
if (seenInBatch.has(instanceId) || allProjectInstanceIds.has(instanceId)) {
throw new error_1.FirebaseError(`functions kit instance ID must be unique across all kits, but '${instanceId}' was used more than once.`);
}
seenInBatch.add(instanceId);
}
for (const instanceId of seenInBatch) {
allProjectInstanceIds.add(instanceId);
}
}
function assertUniqueKitInstancesAndCodebases(config) {
const codebases = new Set();
const instanceIds = new Set();
for (const c of config) {
if ("codebase" in c && c.codebase) {
codebases.add(c.codebase);
}
if ("instances" in c && c.instances) {
validateAndAddKitInstances(Object.keys(c.instances), instanceIds);
}
}
for (const instanceId of instanceIds) {
if (codebases.has(instanceId)) {
throw new error_1.FirebaseError(`functions codebase name and kit instance ID must be mutually exclusive, but '${instanceId}' was used as both a codebase name and a kit instance ID.`);
}
}
}
function validate(config) {
const validated = config.map((cfg) => validateSingle(cfg));
assertUnique(validated, "codebase");
assertUnique(validated, "kit");
assertUniqueSourcePrefixPair(validated);
assertUniqueKitInstancesAndCodebases(validated);
return validated;
}
function normalizeAndValidate(config) {
return validate(normalize(config));
}
function configForCodebase(config, codebaseOrInstance) {
const codebaseCfg = config.find((c) => {
if (isKitConfig(c)) {
return c.instances && codebaseOrInstance in c.instances;
}
return c.codebase === codebaseOrInstance;
});
if (!codebaseCfg) {
throw new error_1.FirebaseError(`No functions config found for codebase or kit instance ${codebaseOrInstance}`);
}
return codebaseCfg;
}
function isLocalConfig(c) {
return "source" in c && !("kit" in c);
}
function isRemoteConfig(c) {
return "remoteSource" in c;
}
function isKitConfig(c) {
return "kit" in c;
}
exports.KIT_PREFIX = "kit-";
function addKitPrefix(instanceId) {
return `${exports.KIT_PREFIX}${instanceId}`;
}
function requireLocal(c, purpose) {
if (isRemoteConfig(c)) {
const msg = purpose ??
"This operation requires a local functions source directory, but the codebase is configured with a remote source.";
throw new error_1.FirebaseError(msg);
}
return c;
}
function resolveConfigDir(c, instanceId) {
if (isKitConfig(c)) {
return instanceId ? c.instances[instanceId] : undefined;
}
return c.configDir || (isLocalConfig(c) ? c.source : undefined);
}
function shouldUseRuntimeConfig(cfg) {
return isLocalConfig(cfg) && cfg.disallowLegacyRuntimeConfig !== true;
}