refactor: centralize platform infra yaml parser helpers (#358)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -18,9 +18,30 @@ import {
|
||||
shQuote,
|
||||
type FrpcSecretMaterial,
|
||||
} from "./platform-infra-public-service";
|
||||
import {
|
||||
yamlAbsolutePathField,
|
||||
yamlApiPathField,
|
||||
yamlArrayOfRecords,
|
||||
yamlBooleanField,
|
||||
yamlEnumField,
|
||||
yamlEnvKeyField,
|
||||
yamlHostField,
|
||||
yamlHttpsUrlField,
|
||||
yamlIntegerArrayField,
|
||||
yamlIntegerField,
|
||||
yamlKubernetesNameField,
|
||||
yamlObjectField,
|
||||
yamlPgIdentifierField,
|
||||
yamlPortField,
|
||||
yamlRecord,
|
||||
yamlSourceRefField,
|
||||
yamlStringArrayField,
|
||||
yamlStringField,
|
||||
} from "./platform-infra-ops-library";
|
||||
import { fingerprintSecretValues, parseEnvFile, readEnvSourceFile, readTextFile, redactRepoPath, requiredEnvValue } from "./secrets";
|
||||
|
||||
const configFile = rootPath("config", "platform-infra", "langbot.yaml");
|
||||
const configLabel = "config/platform-infra/langbot.yaml";
|
||||
const serviceName = "langbot";
|
||||
const pluginRuntimeServiceName = "langbot-plugin-runtime";
|
||||
const fieldManager = "unidesk-platform-langbot";
|
||||
@@ -1441,114 +1462,75 @@ function boolField(value: Record<string, unknown> | null, key: string, fallback:
|
||||
}
|
||||
|
||||
function asRecord(value: unknown, path: string): Record<string, unknown> {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be a YAML object`);
|
||||
return value as Record<string, unknown>;
|
||||
return yamlRecord(value, path);
|
||||
}
|
||||
|
||||
function objectField(obj: Record<string, unknown>, key: string, path: string): Record<string, unknown> {
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
return asRecord(obj[key], `config/platform-infra/langbot.yaml.${prefix}${key}`);
|
||||
return yamlObjectField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function arrayOfRecords(value: unknown, path: string): Record<string, unknown>[] {
|
||||
if (!Array.isArray(value)) throw new Error(`config/platform-infra/langbot.yaml.${path} must be an array`);
|
||||
return value.map((item, index) => asRecord(item, `config/platform-infra/langbot.yaml.${path}[${index}]`));
|
||||
return yamlArrayOfRecords(value, configLabel, path);
|
||||
}
|
||||
|
||||
function stringField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`config/platform-infra/langbot.yaml.${prefix}${key} must be a non-empty string`);
|
||||
return value.trim();
|
||||
return yamlStringField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function integerField(obj: Record<string, unknown>, key: string, path: string): number {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`config/platform-infra/langbot.yaml.${prefix}${key} must be an integer`);
|
||||
return value;
|
||||
return yamlIntegerField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function booleanField(obj: Record<string, unknown>, key: string, path: string): boolean {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (typeof value !== "boolean") throw new Error(`config/platform-infra/langbot.yaml.${prefix}${key} must be a boolean`);
|
||||
return value;
|
||||
return yamlBooleanField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function stringArrayField(obj: Record<string, unknown>, key: string, path: string): string[] {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim().length === 0)) throw new Error(`config/platform-infra/langbot.yaml.${prefix}${key} must be a string array`);
|
||||
return value.map((item) => (item as string).trim());
|
||||
return yamlStringArrayField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function numberArrayField(obj: Record<string, unknown>, key: string, path: string): number[] {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== "number" || !Number.isInteger(item))) throw new Error(`config/platform-infra/langbot.yaml.${prefix}${key} must be an integer array`);
|
||||
return value as number[];
|
||||
return yamlIntegerArrayField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function enumField<const T extends readonly string[]>(obj: Record<string, unknown>, key: string, path: string, values: T): T[number] {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!(values as readonly string[]).includes(value)) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} must be one of ${values.join(", ")}`);
|
||||
return value as T[number];
|
||||
return yamlEnumField(obj, key, configLabel, path, values);
|
||||
}
|
||||
|
||||
function kubernetesNameField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u.test(value)) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} must be a Kubernetes name`);
|
||||
return value;
|
||||
return yamlKubernetesNameField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function sourceRefField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (value.startsWith("/") || value.includes("..") || !/^[A-Za-z0-9_./-]+$/u.test(value)) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} must be a relative source ref without ..`);
|
||||
return value;
|
||||
return yamlSourceRefField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function envKeyField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!/^[A-Z_][A-Z0-9_]*$/u.test(value)) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} must be an env key`);
|
||||
return value;
|
||||
return yamlEnvKeyField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function pgIdentifierField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(value)) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} must be a PostgreSQL identifier`);
|
||||
return value;
|
||||
return yamlPgIdentifierField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function hostField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!/^[A-Za-z0-9._:-]+$/u.test(value)) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} has an unsupported host format`);
|
||||
return value;
|
||||
return yamlHostField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function portField(obj: Record<string, unknown>, key: string, path: string): number {
|
||||
const value = integerField(obj, key, path);
|
||||
if (value < 1 || value > 65535) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} must be a TCP port`);
|
||||
return value;
|
||||
return yamlPortField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function absolutePathField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!value.startsWith("/")) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} must be absolute`);
|
||||
return value;
|
||||
return yamlAbsolutePathField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function apiPathField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!value.startsWith("/") || value.includes("..")) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} must be an absolute path without ..`);
|
||||
return value;
|
||||
return yamlApiPathField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function httpsUrlField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
const url = new URL(value);
|
||||
if (url.protocol !== "https:" || url.search || url.hash) throw new Error(`config/platform-infra/langbot.yaml.${path}.${key} must be an https URL without query or hash`);
|
||||
return value.replace(/\/+$/u, "");
|
||||
return yamlHttpsUrlField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function pvcSpec(record: Record<string, unknown>, path: string): PvcSpec {
|
||||
|
||||
@@ -20,6 +20,25 @@ import {
|
||||
type PublicServiceExposure,
|
||||
type PublicServiceTarget,
|
||||
} from "./platform-infra-public-service";
|
||||
import {
|
||||
yamlAbsolutePathField,
|
||||
yamlArrayOfRecords,
|
||||
yamlBooleanField,
|
||||
yamlEnumField,
|
||||
yamlEnvKeyField,
|
||||
yamlHostField,
|
||||
yamlHttpsUrlField,
|
||||
yamlIntegerArrayField,
|
||||
yamlIntegerField,
|
||||
yamlKubernetesNameField,
|
||||
yamlObjectField,
|
||||
yamlPgIdentifierField,
|
||||
yamlPortField,
|
||||
yamlRecord,
|
||||
yamlSourceRefField,
|
||||
yamlStringArrayField,
|
||||
yamlStringField,
|
||||
} from "./platform-infra-ops-library";
|
||||
import { fingerprintSecretValues, parseEnvFile, readEnvSourceFile, readTextFile, redactRepoPath, requiredEnvValue } from "./secrets";
|
||||
|
||||
const configFile = rootPath("config", "platform-infra", "n8n.yaml");
|
||||
@@ -1047,108 +1066,71 @@ function boolField(value: Record<string, unknown> | null, key: string, fallback:
|
||||
}
|
||||
|
||||
function asRecord(value: unknown, path: string): Record<string, unknown> {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be a YAML object`);
|
||||
return value as Record<string, unknown>;
|
||||
return yamlRecord(value, path);
|
||||
}
|
||||
|
||||
function objectField(obj: Record<string, unknown>, key: string, path: string): Record<string, unknown> {
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
return asRecord(obj[key], `${configLabel}.${prefix}${key}`);
|
||||
return yamlObjectField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function arrayOfRecords(value: unknown, path: string): Record<string, unknown>[] {
|
||||
if (!Array.isArray(value)) throw new Error(`${configLabel}.${path} must be an array`);
|
||||
return value.map((item, index) => asRecord(item, `${configLabel}.${path}[${index}]`));
|
||||
return yamlArrayOfRecords(value, configLabel, path);
|
||||
}
|
||||
|
||||
function stringField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${configLabel}.${prefix}${key} must be a non-empty string`);
|
||||
return value.trim();
|
||||
return yamlStringField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function integerField(obj: Record<string, unknown>, key: string, path: string): number {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${configLabel}.${prefix}${key} must be an integer`);
|
||||
return value;
|
||||
return yamlIntegerField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function booleanField(obj: Record<string, unknown>, key: string, path: string): boolean {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (typeof value !== "boolean") throw new Error(`${configLabel}.${prefix}${key} must be a boolean`);
|
||||
return value;
|
||||
return yamlBooleanField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function stringArrayField(obj: Record<string, unknown>, key: string, path: string): string[] {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim().length === 0)) throw new Error(`${configLabel}.${prefix}${key} must be a string array`);
|
||||
return value.map((item) => (item as string).trim());
|
||||
return yamlStringArrayField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function numberArrayField(obj: Record<string, unknown>, key: string, path: string): number[] {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== "number" || !Number.isInteger(item))) throw new Error(`${configLabel}.${prefix}${key} must be an integer array`);
|
||||
return value as number[];
|
||||
return yamlIntegerArrayField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function enumField<const T extends readonly string[]>(obj: Record<string, unknown>, key: string, path: string, values: T): T[number] {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!(values as readonly string[]).includes(value)) throw new Error(`${configLabel}.${path}.${key} must be one of ${values.join(", ")}`);
|
||||
return value as T[number];
|
||||
return yamlEnumField(obj, key, configLabel, path, values);
|
||||
}
|
||||
|
||||
function kubernetesNameField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u.test(value)) throw new Error(`${configLabel}.${path}.${key} must be a Kubernetes name`);
|
||||
return value;
|
||||
return yamlKubernetesNameField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function sourceRefField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (value.startsWith("/") || value.includes("..") || !/^[A-Za-z0-9_./-]+$/u.test(value)) throw new Error(`${configLabel}.${path}.${key} must be a relative source ref without ..`);
|
||||
return value;
|
||||
return yamlSourceRefField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function envKeyField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!/^[A-Z_][A-Z0-9_]*$/u.test(value)) throw new Error(`${configLabel}.${path}.${key} must be an env key`);
|
||||
return value;
|
||||
return yamlEnvKeyField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function pgIdentifierField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(value)) throw new Error(`${configLabel}.${path}.${key} must be a PostgreSQL identifier`);
|
||||
return value;
|
||||
return yamlPgIdentifierField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function hostField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!/^[A-Za-z0-9._:-]+$/u.test(value)) throw new Error(`${configLabel}.${path}.${key} has an unsupported host format`);
|
||||
return value;
|
||||
return yamlHostField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function portField(obj: Record<string, unknown>, key: string, path: string): number {
|
||||
const value = integerField(obj, key, path);
|
||||
if (value < 1 || value > 65535) throw new Error(`${configLabel}.${path}.${key} must be a TCP port`);
|
||||
return value;
|
||||
return yamlPortField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function absolutePathField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
if (!value.startsWith("/")) throw new Error(`${configLabel}.${path}.${key} must be absolute`);
|
||||
return value;
|
||||
return yamlAbsolutePathField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function httpsUrlField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = stringField(obj, key, path);
|
||||
const url = new URL(value);
|
||||
if (url.protocol !== "https:" || url.search || url.hash) throw new Error(`${configLabel}.${path}.${key} must be an https URL without query or hash`);
|
||||
return value.replace(/\/+$/u, "");
|
||||
return yamlHttpsUrlField(obj, key, configLabel, path);
|
||||
}
|
||||
|
||||
function pvcSpec(record: Record<string, unknown>, path: string): PvcSpec {
|
||||
|
||||
@@ -180,6 +180,116 @@ export function stringListField(obj: Record<string, unknown>, key: string, path:
|
||||
return value as string[];
|
||||
}
|
||||
|
||||
export function yamlFieldLabel(configLabel: string, path: string, key: string): string {
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
return `${configLabel}.${prefix}${key}`;
|
||||
}
|
||||
|
||||
export function yamlRecord(value: unknown, label: string): Record<string, unknown> {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} must be a YAML object`);
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function yamlObjectField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): Record<string, unknown> {
|
||||
return yamlRecord(obj[key], yamlFieldLabel(configLabel, path, key));
|
||||
}
|
||||
|
||||
export function yamlArrayOfRecords(value: unknown, configLabel: string, path: string): Record<string, unknown>[] {
|
||||
if (!Array.isArray(value)) throw new Error(`${configLabel}.${path} must be an array`);
|
||||
return value.map((item, index) => yamlRecord(item, `${configLabel}.${path}[${index}]`));
|
||||
}
|
||||
|
||||
export function yamlStringField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string {
|
||||
const value = obj[key];
|
||||
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be a non-empty string`);
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
export function yamlIntegerField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): number {
|
||||
const value = obj[key];
|
||||
if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be an integer`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlBooleanField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): boolean {
|
||||
const value = obj[key];
|
||||
if (typeof value !== "boolean") throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be a boolean`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlStringArrayField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string[] {
|
||||
const value = obj[key];
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim().length === 0)) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be a string array`);
|
||||
return value.map((item) => (item as string).trim());
|
||||
}
|
||||
|
||||
export function yamlIntegerArrayField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): number[] {
|
||||
const value = obj[key];
|
||||
if (!Array.isArray(value) || value.some((item) => typeof item !== "number" || !Number.isInteger(item))) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be an integer array`);
|
||||
return value as number[];
|
||||
}
|
||||
|
||||
export function yamlEnumField<const T extends readonly string[]>(obj: Record<string, unknown>, key: string, configLabel: string, path: string, values: T): T[number] {
|
||||
const value = yamlStringField(obj, key, configLabel, path);
|
||||
if (!(values as readonly string[]).includes(value)) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be one of ${values.join(", ")}`);
|
||||
return value as T[number];
|
||||
}
|
||||
|
||||
export function yamlKubernetesNameField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string {
|
||||
const value = yamlStringField(obj, key, configLabel, path);
|
||||
if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u.test(value)) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be a Kubernetes name`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlSourceRefField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string {
|
||||
const value = yamlStringField(obj, key, configLabel, path);
|
||||
if (value.startsWith("/") || value.includes("..") || !/^[A-Za-z0-9_./-]+$/u.test(value)) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be a relative source ref without ..`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlEnvKeyField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string {
|
||||
const value = yamlStringField(obj, key, configLabel, path);
|
||||
if (!/^[A-Z_][A-Z0-9_]*$/u.test(value)) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be an env key`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlPgIdentifierField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string {
|
||||
const value = yamlStringField(obj, key, configLabel, path);
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(value)) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be a PostgreSQL identifier`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlHostField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string {
|
||||
const value = yamlStringField(obj, key, configLabel, path);
|
||||
if (!/^[A-Za-z0-9._:-]+$/u.test(value)) throw new Error(`${yamlFieldLabel(configLabel, path, key)} has an unsupported host format`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlPortField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): number {
|
||||
const value = yamlIntegerField(obj, key, configLabel, path);
|
||||
if (value < 1 || value > 65535) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be a TCP port`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlAbsolutePathField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string {
|
||||
const value = yamlStringField(obj, key, configLabel, path);
|
||||
if (!value.startsWith("/")) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be absolute`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlApiPathField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string {
|
||||
const value = yamlStringField(obj, key, configLabel, path);
|
||||
if (!value.startsWith("/") || value.includes("..")) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be an absolute path without ..`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function yamlHttpsUrlField(obj: Record<string, unknown>, key: string, configLabel: string, path: string): string {
|
||||
const value = yamlStringField(obj, key, configLabel, path);
|
||||
const url = new URL(value);
|
||||
if (url.protocol !== "https:" || url.search || url.hash) throw new Error(`${yamlFieldLabel(configLabel, path, key)} must be an https URL without query or hash`);
|
||||
return value.replace(/\/+$/u, "");
|
||||
}
|
||||
|
||||
export function repoRelative(path: string): string {
|
||||
const rel = relative(rootPath(), path);
|
||||
return rel.startsWith("..") ? path : rel;
|
||||
|
||||
Reference in New Issue
Block a user