74 lines
2.2 KiB
JavaScript
74 lines
2.2 KiB
JavaScript
import { z } from "zod";
|
|
|
|
const version = z.string().regex(/^\d+(?:\.\d+){0,2}$/);
|
|
|
|
export const runtimeConfigSchema = z
|
|
.object({
|
|
APP_ENV: z.enum(["local", "development", "staging", "production"]),
|
|
API_BASE_URL: z.url(),
|
|
REQUEST_TIMEOUT_MS: z.int().min(100).max(60_000).default(10_000),
|
|
MAX_RETRY_ATTEMPTS: z.int().min(0).max(2).default(2),
|
|
TELEMETRY_ENABLED: z.boolean(),
|
|
TELEMETRY_ENDPOINT: z.url().optional(),
|
|
AUTH_MODE: z.enum(["external", "demo"]),
|
|
CONFIG_SCHEMA_VERSION: version,
|
|
API_CONTRACT_VERSION: version,
|
|
RELEASE_MANIFEST_URL: z.string().min(1).default("/release-manifest.json"),
|
|
RELEASE_ID: z.string().min(1).optional(),
|
|
BUILD_ID: z.string().min(1).optional(),
|
|
})
|
|
.strict()
|
|
.superRefine((config, context) => {
|
|
if (config.TELEMETRY_ENABLED && !config.TELEMETRY_ENDPOINT) {
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["TELEMETRY_ENDPOINT"],
|
|
message: "required when telemetry is enabled",
|
|
});
|
|
}
|
|
|
|
const local = config.APP_ENV === "local" || config.APP_ENV === "development";
|
|
if (!local && config.AUTH_MODE === "demo") {
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["AUTH_MODE"],
|
|
message: "demo authentication is limited to local environments",
|
|
});
|
|
}
|
|
const endpointEntries =
|
|
/** @type {Array<[string, string | undefined]>} */ ([
|
|
["API_BASE_URL", config.API_BASE_URL],
|
|
["TELEMETRY_ENDPOINT", config.TELEMETRY_ENDPOINT],
|
|
]);
|
|
|
|
for (const [key, value] of endpointEntries) {
|
|
if (value && !local && new URL(value).protocol !== "https:") {
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: [key],
|
|
message: "HTTPS is required outside local environments",
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
/** @param {unknown} value */
|
|
export function validateRuntimeConfig(value) {
|
|
const result = runtimeConfigSchema.safeParse(value);
|
|
|
|
if (!result.success) {
|
|
return {
|
|
success: /** @type {false} */ (false),
|
|
issues: result.error.issues.map((issue) => ({
|
|
path: issue.path.join("."),
|
|
code: issue.code,
|
|
})),
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: /** @type {true} */ (true),
|
|
data: structuredClone(result.data),
|
|
};
|
|
}
|