hypertune
Version:
[Hypertune](https://www.hypertune.com/) is the most flexible platform for feature flags, A/B testing, analytics and app configuration. Built with full end-to-end type-safety, Git-style version control and local, synchronous, in-memory flag evaluation. Opt
195 lines • 7.78 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withOtherOptionSources = void 0;
exports.withValidation = withValidation;
exports.throwIfOptionIsUndefined = throwIfOptionIsUndefined;
exports.parseOptionValueWithSchema = parseOptionValueWithSchema;
/* eslint-disable no-underscore-dangle */
const joycon_1 = __importDefault(require("joycon"));
/**
* Processes default options behavior:
* - removes `--` option as we never use it
* - file options (e.g. hypertune.config.js, hypertune.json, hypertune key in package.json)
* - environment variable options (e.g. HYPERTUNE_TOKEN)
* - any options passed in to the returned handler (i.e. CLI args)
*/
// eslint-disable-next-line func-style
const withOtherOptionSources = (handler, schema) => {
// In ascending priority order
// i.e. if an option is defined by wrapper[1], the same option defined by wrapper[0] will be ignored
// Passing in arguments to the returned handler has the highest priority
const wrappers = [
withoutDoubleDashOption,
withFileOptions,
withEnvVarOptions,
];
let wrapped = handler;
for (const wrapper of wrappers) {
wrapped = wrapper(wrapped, schema);
}
return wrapped;
};
exports.withOtherOptionSources = withOtherOptionSources;
// eslint-disable-next-line func-style
const withoutDoubleDashOption = (handler) => {
return (options) => {
if (typeof options === "object" && options !== null && "--" in options) {
// eslint-disable-next-line no-param-reassign
delete options["--"];
}
return handler(options);
};
};
// eslint-disable-next-line func-style
const withFileOptions = (handler) => {
return (options) => __awaiter(void 0, void 0, void 0, function* () {
const joycon = new joycon_1.default({
files: [
"hypertune.config.js",
"hypertune.config.cjs",
"hypertune.json",
"package.json",
],
packageKey: "hypertune",
});
const res = yield joycon.load();
if (typeof res.data === "object" && res.data !== null) {
return handler(mergeOptions(res.data, options));
}
if (res.path) {
console.warn(`Warning: Ignoring hypertune config at ${res.path}, as couldn't read it as a JavaScript object`);
}
return handler(options);
});
};
// eslint-disable-next-line func-style
const withEnvVarOptions = (handler, schema) => {
return (options) => {
// eslint-disable-next-line no-constant-binary-expression
if (typeof process !== undefined && process.env) {
const envOptions = Object.fromEntries(Object.entries(process.env)
.filter(([k]) => /^(.*_)?HYPERTUNE_/.test(k))
.map(([k, v]) => [envNameToOptionName(k), v])
.map(([k, v]) => [
k,
schema && schema[k]
? parseOptionValueWithSchema(schema[k], v)
: v,
]));
return handler(mergeOptions(envOptions, options));
}
return handler(options);
};
};
function mergeOptions(options, overridingOptions) {
const commonKeys = Object.keys(options).filter((k) => k in overridingOptions);
commonKeys.forEach((k) => {
console.warn(`Warning: option "${String(k)}" defined multiple times, using value ${JSON.stringify(overridingOptions[k])}`);
});
return Object.assign(Object.assign({}, options), overridingOptions);
}
/**
* Adds options validation to a handler based on a Zod schema.
*
* As Zod schemas can specify defaults, also changes the input type to what is actually required (e.g. okay not to provide something where it has a default).
*/
function withValidation(schema, handler) {
return (options) => {
Object.entries(options).forEach(([option, value]) => {
if (!(option in schema)) {
console.warn(`Warning: Ignoring unrecognized option ${option}`);
return;
}
switch (schema[option]) {
case "any":
return;
case "string":
if (typeof value !== "string") {
throw new Error(`Option "${option}" must be a string, but ${typeof value} was provided`);
}
return;
case "boolean":
if (typeof value !== "boolean") {
throw new Error(`Option "${option}" must be a boolean, but ${typeof value} was provided`);
}
return;
case "number":
if (typeof value !== "number") {
throw new Error(`Option "${option}" must be a number, but ${typeof value} was provided`);
}
return;
default:
throw new Error(`Unexpected option type "${schema[option]}" for option "${option}"`);
}
});
return handler(options);
};
}
function throwIfOptionIsUndefined(optionName, value) {
if (value === undefined) {
throw new Error(`${optionName}: Missing required argument. Set it in your hypertune config (such as hypertune.json) as ${optionName}, use the ${optionNameToCliFlag(optionName)} argument, or the ${optionNameToEnvName(optionName)} environment variable.`);
}
return value;
}
function parseOptionValueWithSchema(valueType, value) {
switch (valueType) {
case "boolean": {
switch (value.toLowerCase().trim()) {
case "1":
case "yes":
case "true":
return true;
case "0":
case "no":
case "false":
return false;
default:
return value;
}
}
case "number": {
if (/^-?\d+\.?\d*$/.test(value)) {
return Number(value);
}
return value;
}
default:
return value;
}
}
/**
* @example envNameToOptionName("NEXT_PUBLIC_HYPERTUNE_OUTPUT_FILE_PATH") == "outputFilePath"
*/
function envNameToOptionName(envName) {
const afterPrefix = envName.replace(/^(.*?_?)HYPERTUNE_/, "");
return afterPrefix
.toLowerCase()
.replace(/_+(.)/g, (_, char) => char.toUpperCase());
}
/**
* @example optionNameToEnvName("outputFilePath") == "HYPERTUNE_OUTPUT_FILE_PATH"
*/
function optionNameToEnvName(optionName) {
return `HYPERTUNE_${optionName
.replace(/[A-Z]/g, (letter) => `_${letter}`)
.toUpperCase()}`;
}
/**
* @example optionNameToCliFlag("outputFilePath") == "--outputFilePath"
*/
function optionNameToCliFlag(optionName) {
return `--${optionName}`;
}
//# sourceMappingURL=helpers.js.map