From 5c5d95d74cb9295992d478358124c7a0299dab10 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Mon, 12 Aug 2024 09:35:24 +0200 Subject: [PATCH] Provide a type safe config with sane defaults on type level --- README.md | 12 +- config/custom-environment-variables.json | 16 -- config/default.json | 14 +- config/test.json | 6 +- http-client/lib/fetcher.ts | 12 +- http-client/lib/shared.ts | 15 ++ http-client/vite.config.ts | 50 ++++--- module.d.ts | 17 +-- package-lock.json | 48 +++++- package.json | 8 +- playwright.config.ts | 2 +- src/lib/router.ts | 14 +- src/views/nodes/SeedSelector.svelte | 4 +- src/views/nodes/router.ts | 4 +- src/views/repos/Header/CloneButton.svelte | 4 +- src/views/repos/History.svelte | 2 +- src/views/repos/Share.svelte | 2 +- src/views/repos/router.ts | 2 +- tsconfig.json | 2 +- vite.config.ts | 175 +++++++++++++++------- 20 files changed, 256 insertions(+), 153 deletions(-) delete mode 100644 config/custom-environment-variables.json diff --git a/README.md b/README.md index dd06da5ae..62bd82b03 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,15 @@ There's two ways to configure the UI: **Environment variables** -1. Check [custom-environment-variables.json][env] for all available environment - variables. -2. Set the desired environment variables when building the UI. +The following environment variables are available: + +- PUBLIC_EXPLORER_URL="https://app.radicle.xyz/nodes/$host/$rid$path" +- SUPPORTED_API_VERSION: "6.0.0" +- DEFAULT_HTTPD_API_PORT: 443 +- DEFAULT_HTTPD_SCHEME: "https" +- HISTORY_COMMITS_PER_PAGE: 30 +- SUPPORT_WEBSITE: "https://radicle.xyz" +- PREFERRED_SEEDS: '[{ "hostname": `hostname`, "port": `port number`, "scheme": `url scheme` }]' > For advanced configuration options, have a look at the [`node-config`][nco] > package. diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json deleted file mode 100644 index f2a45acb7..000000000 --- a/config/custom-environment-variables.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "nodes": { - "fallbackPublicExplorer": "FALLBACK_PUBLIC_EXPLORER", - "apiVersion": "API_VERSION", - "defaultHttpdPort": "DEFAULT_HTTPD_PORT", - "defaultHttpdScheme": "DEFAULT_HTTPD_SCHEME" - }, - "source": { - "commitsPerPage": "COMMITS_PER_PAGE" - }, - "supportWebsite": "SUPPORT_WEBSITE", - "fallbackPreferredSeed": { - "__name": "PREFERRED_SEEDS", - "__format": "json" - } -} diff --git a/config/default.json b/config/default.json index 5c8e19a2a..a28abd38d 100644 --- a/config/default.json +++ b/config/default.json @@ -1,13 +1,9 @@ { - "nodes": { - "fallbackPublicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path", - "apiVersion": "5.0.0", - "defaultHttpdPort": 443, - "defaultHttpdScheme": "https" - }, - "source": { - "commitsPerPage": 30 - }, + "publicExplorerUrl": "https://app.radicle.xyz/nodes/$host/$rid$path", + "supportedApiVersion": "6.0.0", + "defaultHttpdApiPort": 443, + "defaultHttpdScheme": "https", + "historyCommitsPerPage": 30, "supportWebsite": "https://radicle.zulipchat.com", "preferredSeeds": [ { diff --git a/config/test.json b/config/test.json index d8439a6e7..411e9cda6 100644 --- a/config/test.json +++ b/config/test.json @@ -1,8 +1,6 @@ { - "nodes": { - "defaultHttpdPort": 8081, - "defaultHttpdScheme": "http" - }, + "defaultHttpdApiPort": 8081, + "defaultHttpdScheme": "http", "preferredSeeds": [ { "hostname": "127.0.0.1", diff --git a/http-client/lib/fetcher.ts b/http-client/lib/fetcher.ts index 9ca1c5ccc..8ebbb6dab 100644 --- a/http-client/lib/fetcher.ts +++ b/http-client/lib/fetcher.ts @@ -61,14 +61,14 @@ export class ResponseParseError extends Error { let description: string; if ( apiVersion === undefined || - compare(apiVersion, config.nodes.apiVersion, "<") + compare(apiVersion, config.supportedApiVersion, "<") ) { - description = `The node you are fetching from seems to be outdated, make sure the httpd API version is at least ${config.nodes.apiVersion} currently ${apiVersion ?? "unknown"}.`; + description = `The node you are fetching from seems to be outdated, make sure the httpd API version is at least ${config.supportedApiVersion} currently ${apiVersion ?? "unknown"}.`; } else if ( - config.nodes.apiVersion === undefined || - compare(apiVersion, config.nodes.apiVersion, ">") + config.supportedApiVersion === undefined || + compare(apiVersion, config.supportedApiVersion, ">") ) { - description = `The web client you are using is outdated, make sure it supports at least ${apiVersion} to interact with this node currently ${config.nodes.apiVersion ?? "unknown"}.`; + description = `The web client you are using is outdated, make sure it supports at least ${apiVersion} to interact with this node currently ${config.supportedApiVersion ?? "unknown"}.`; } else { description = "This is usually due to a version mismatch between the seed and the web interface."; @@ -122,7 +122,7 @@ export class Fetcher { ): Promise> { const response = await this.fetch({ ...params, - query: { ...params.query, v: config.nodes.apiVersion }, + query: { ...params.query, v: config.supportedApiVersion }, }); if (!response.ok) { diff --git a/http-client/lib/shared.ts b/http-client/lib/shared.ts index 9c9e36fa8..0400b5243 100644 --- a/http-client/lib/shared.ts +++ b/http-client/lib/shared.ts @@ -1,6 +1,7 @@ import type { z } from "zod"; import { array, boolean, literal, number, object, string, union } from "zod"; +import * as defaults from "../../config/default.json"; export const scopeSchema = union([literal("followed"), literal("all")]); @@ -123,3 +124,17 @@ export const authorSchema = object({ id: string(), alias: string().optional(), }); + +export type WebConfig = z.infer; + +export const webConfigSchema = object({ + publicExplorerUrl: string().default(defaults.publicExplorerUrl), + supportedApiVersion: string().default(defaults.supportedApiVersion), + defaultHttpdApiPort: number().default(defaults.defaultHttpdApiPort), + defaultHttpdScheme: string().default(defaults.defaultHttpdScheme), + historyCommitsPerPage: number().default(defaults.historyCommitsPerPage), + supportWebsite: string().default(defaults.supportWebsite), + preferredSeeds: array( + object({ hostname: string(), port: number(), scheme: string() }), + ).default(defaults.preferredSeeds), +}); diff --git a/http-client/vite.config.ts b/http-client/vite.config.ts index aeca19da4..3643b0d7b 100644 --- a/http-client/vite.config.ts +++ b/http-client/vite.config.ts @@ -1,25 +1,37 @@ -import nodeConfig from "config"; import path from "node:path"; import virtual from "vite-plugin-virtual"; +import { configureAdapters } from "../vite.config"; import { defineConfig } from "vite"; +import { loadConfig } from "zod-config"; +import { webConfigSchema } from "./lib/shared"; -export default defineConfig({ - plugins: [ - virtual({ - "virtual:config": nodeConfig.util.toObject(), - }), - ], - test: { - environment: "node", - include: ["http-client/tests/*.test.ts"], - reporters: "verbose", - globalSetup: "./tests/support/globalSetup", - }, - resolve: { - alias: { - "@tests": path.resolve("./tests"), - "@app": path.resolve("./src"), - "@http-client": path.resolve("./http-client"), +export default defineConfig(async () => { + const adapters = await configureAdapters(); + const config = await loadConfig({ + schema: webConfigSchema, + adapters, + logger: { + warn: message => console.warn(`WARN [config]: ${message}`), }, - }, + }); + return { + plugins: [ + virtual({ + "virtual:config": config, + }), + ], + test: { + environment: "node", + include: ["http-client/tests/*.test.ts"], + reporters: "verbose", + globalSetup: "./tests/support/globalSetup", + }, + resolve: { + alias: { + "@tests": path.resolve("./tests"), + "@app": path.resolve("./src"), + "@http-client": path.resolve("./http-client"), + }, + }, + }; }); diff --git a/module.d.ts b/module.d.ts index b1f4abcb6..a23193eb1 100644 --- a/module.d.ts +++ b/module.d.ts @@ -1,19 +1,6 @@ declare module "virtual:*" { - const config: { - nodes: { - apiVersion: string; - fallbackPublicExplorer: string; - defaultHttpdPort: number; - defaultLocalHttpdPort: number; - defaultHttpdScheme: string; - }; - source: { - commitsPerPage: number; - }; - reactions: string[]; - supportWebsite: string; - preferredSeeds: BaseUrl[]; - }; + import type { WebConfig } from "@http-client/lib/shared"; + const config: WebConfig; export default config; } diff --git a/package-lock.json b/package-lock.json index 17de45644..fe4ea6866 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "hasInstallScript": true, "dependencies": { + "@absxn/process-env-parser": "^1.1.1", "@efstajas/svelte-stored-writable": "^0.2.0", "@radicle/gray-matter": "4.1.0", "@wooorm/starry-night": "^3.4.0", @@ -32,14 +33,14 @@ "plausible-tracker": "^0.3.9", "svelte": "^4.2.19", "twemoji": "^14.0.2", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zod-config": "^0.0.5" }, "devDependencies": { "@eslint/js": "^9.9.1", "@playwright/test": "^1.46.1", "@sveltejs/vite-plugin-svelte": "^3.1.2", "@tsconfig/svelte": "^5.0.4", - "@types/config": "^3.3.4", "@types/dompurify": "^3.0.5", "@types/katex": "^0.16.7", "@types/lodash": "^4.17.7", @@ -71,8 +72,16 @@ }, "engines": { "node": ">=18.17.1" + }, + "peerDependencies": { + "dotenv": "^16.4.5" } }, + "node_modules/@absxn/process-env-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@absxn/process-env-parser/-/process-env-parser-1.1.1.tgz", + "integrity": "sha512-QhkpC22/vs8zi9gbcli/7OUaQyQeuRTa/r5QAzkB58giF8wxES2DbC9ubePm0wjcIN6sjhpOS/ot36XASon6Iw==" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -1089,12 +1098,6 @@ "integrity": "sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q==", "dev": true }, - "node_modules/@types/config": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@types/config/-/config-3.3.4.tgz", - "integrity": "sha512-qFiTLnWy+TdPSMIXFHP+87lFXFRM4SXjRS+CSB66+56TrpLNw003y1sh7DGaaC1NGesxgKoT5FDy6dyA1Xju/g==", - "dev": true - }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -2064,6 +2067,18 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==" }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -5226,6 +5241,23 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-config": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/zod-config/-/zod-config-0.0.5.tgz", + "integrity": "sha512-tW7J4bwVPYD0eYeaDzIjEpv188118H+uCbb+DaBRIGwuZNCSL0WyMmpeOmgMpbsGzrJ9YjtC5XFpyWlj27vd8g==", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dotenv": ">=15", + "zod": "^3.x" + }, + "peerDependenciesMeta": { + "dotenv": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index c580ab097..8bbdba82d 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "@playwright/test": "^1.46.1", "@sveltejs/vite-plugin-svelte": "^3.1.2", "@tsconfig/svelte": "^5.0.4", - "@types/config": "^3.3.4", "@types/dompurify": "^3.0.5", "@types/katex": "^0.16.7", "@types/lodash": "^4.17.7", @@ -55,6 +54,7 @@ "wait-on": "^8.0.0" }, "dependencies": { + "@absxn/process-env-parser": "^1.1.1", "@efstajas/svelte-stored-writable": "^0.2.0", "@radicle/gray-matter": "4.1.0", "@wooorm/starry-night": "^3.4.0", @@ -78,6 +78,10 @@ "plausible-tracker": "^0.3.9", "svelte": "^4.2.19", "twemoji": "^14.0.2", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zod-config": "^0.0.5" + }, + "peerDependencies": { + "dotenv": "^16.4.5" } } diff --git a/playwright.config.ts b/playwright.config.ts index 0aeac7774..76d29705e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -92,7 +92,7 @@ const config: PlaywrightTestConfig = { command: "npm run start -- --strictPort --port 3002", port: 3002, // eslint-disable-next-line @typescript-eslint/naming-convention - env: { COMMITS_PER_PAGE: "4" }, + env: { HISTORY_COMMITS_PER_PAGE: "4" }, }, ], }; diff --git a/src/lib/router.ts b/src/lib/router.ts index 5437f1531..f1ef76020 100644 --- a/src/lib/router.ts +++ b/src/lib/router.ts @@ -148,15 +148,15 @@ export async function replace(newRoute: Route): Promise { function extractBaseUrl(hostAndPort: string): BaseUrl { if ( hostAndPort === "radicle.local" || - hostAndPort === `radicle.local:${config.nodes.defaultHttpdPort}` || + hostAndPort === `radicle.local:${config.defaultHttpdApiPort}` || hostAndPort === "0.0.0.0" || - hostAndPort === `0.0.0.0:${config.nodes.defaultHttpdPort}` || + hostAndPort === `0.0.0.0:${config.defaultHttpdApiPort}` || hostAndPort === "127.0.0.1" || - hostAndPort === `127.0.0.1:${config.nodes.defaultHttpdPort}` + hostAndPort === `127.0.0.1:${config.defaultHttpdApiPort}` ) { return { hostname: "127.0.0.1", - port: config.nodes.defaultHttpdPort, + port: config.defaultHttpdApiPort, scheme: "http", }; } else if (hostAndPort.includes(":")) { @@ -167,13 +167,13 @@ function extractBaseUrl(hostAndPort: string): BaseUrl { scheme: utils.isLocal(hostname) || utils.isOnion(hostname) ? "http" - : config.nodes.defaultHttpdScheme, + : config.defaultHttpdScheme, }; } else { return { hostname: hostAndPort, - port: config.nodes.defaultHttpdPort, - scheme: config.nodes.defaultHttpdScheme, + port: config.defaultHttpdApiPort, + scheme: config.defaultHttpdScheme, }; } } diff --git a/src/views/nodes/SeedSelector.svelte b/src/views/nodes/SeedSelector.svelte index 702ddd049..169c54339 100644 --- a/src/views/nodes/SeedSelector.svelte +++ b/src/views/nodes/SeedSelector.svelte @@ -47,8 +47,8 @@ loading = true; const seed = { hostname: seedAddressInput.trim(), - port: config.nodes.defaultHttpdPort, - scheme: config.nodes.defaultHttpdScheme, + port: config.defaultHttpdApiPort, + scheme: config.defaultHttpdScheme, }; validationMessage = await validateInput(seed); if (validationMessage === undefined) { diff --git a/src/views/nodes/router.ts b/src/views/nodes/router.ts index a186943f0..2538615e0 100644 --- a/src/views/nodes/router.ts +++ b/src/views/nodes/router.ts @@ -31,9 +31,9 @@ export interface NodesLoadedRoute { } export function nodePath(baseUrl: BaseUrl) { - const port = baseUrl.port ?? config.nodes.defaultHttpdPort; + const port = baseUrl.port ?? config.defaultHttpdApiPort; - if (port === config.nodes.defaultHttpdPort) { + if (port === config.defaultHttpdApiPort) { return `/nodes/${baseUrl.hostname}`; } else { return `/nodes/${baseUrl.hostname}:${port}`; diff --git a/src/views/repos/Header/CloneButton.svelte b/src/views/repos/Header/CloneButton.svelte index 5ec1ca671..1c76c96e2 100644 --- a/src/views/repos/Header/CloneButton.svelte +++ b/src/views/repos/Header/CloneButton.svelte @@ -19,8 +19,8 @@ $: radCloneUrl = `rad clone ${id}`; $: portFragment = - baseUrl.scheme === config.nodes.defaultHttpdScheme && - baseUrl.port === config.nodes.defaultHttpdPort + baseUrl.scheme === config.defaultHttpdScheme && + baseUrl.port === config.defaultHttpdApiPort ? "" : `:${baseUrl.port}`; $: gitCloneUrl = `git clone ${baseUrl.scheme}://${ diff --git a/src/views/repos/History.svelte b/src/views/repos/History.svelte index bcfef08cd..5fbfd065c 100644 --- a/src/views/repos/History.svelte +++ b/src/views/repos/History.svelte @@ -61,7 +61,7 @@ const response = await api.repo.getAllCommits(repo.rid, { parent: allCommitHeaders[0].id, page, - perPage: config.source.commitsPerPage, + perPage: config.historyCommitsPerPage, }); allCommitHeaders = [...allCommitHeaders, ...response]; } catch (e) { diff --git a/src/views/repos/Share.svelte b/src/views/repos/Share.svelte index 5feea5d78..771372a45 100644 --- a/src/views/repos/Share.svelte +++ b/src/views/repos/Share.svelte @@ -13,7 +13,7 @@ }, 1000); async function copy() { - const text = new URL(config.nodes.fallbackPublicExplorer).origin.concat( + const text = new URL(config.publicExplorerUrl).origin.concat( window.location.pathname, ); await toClipboard(text); diff --git a/src/views/repos/router.ts b/src/views/repos/router.ts index e9c262100..f690eae70 100644 --- a/src/views/repos/router.ts +++ b/src/views/repos/router.ts @@ -608,7 +608,7 @@ async function loadHistoryView( api.repo.getAllCommits(repo.rid, { parent: commitId, page: 0, - perPage: config.source.commitsPerPage, + perPage: config.historyCommitsPerPage, }), ]); diff --git a/tsconfig.json b/tsconfig.json index f6b360862..684d73347 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { "noEmit": true, "target": "es2021", - "module": "es2022", + "module": "esnext", "types": ["vite/client"], "sourceMap": true, "baseUrl": "./", diff --git a/vite.config.ts b/vite.config.ts index 916e6e2e6..8f2e59eb1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,65 +1,134 @@ -import config from "config"; import path from "node:path"; import virtual from "vite-plugin-virtual"; +import * as dotenv from "dotenv"; import { defineConfig } from "vite"; +import { directoryAdapter } from "zod-config/directory-adapter"; +import { jsonAdapter } from "zod-config/json-adapter"; +import { loadConfig, type Adapter } from "zod-config"; +import { parseEnvironmentVariables } from "@absxn/process-env-parser"; import { svelte } from "@sveltejs/vite-plugin-svelte"; -export default defineConfig({ - test: { - environment: "happy-dom", - include: ["tests/unit/**/*.test.ts"], - reporters: "verbose", - }, - plugins: [ - virtual({ - "virtual:config": config.util.toObject(), - }), - svelte({ - // Reference: https://github.com/sveltejs/vite-plugin-svelte/issues/270#issuecomment-1033190138 - dynamicCompileOptions({ filename }) { - if ( - path.basename(filename) === "Clipboard.svelte" || - path.basename(filename) === "ExternalLink.svelte" || - path.basename(filename) === "Icon.svelte" - ) { - return { customElement: true }; - } - }, - compilerOptions: { dev: process.env.NODE_ENV !== "production" }, - }), - ], - server: { - host: "localhost", - port: 3000, - watch: { - // reference: https://stackoverflow.com/a/75238360 - useFsEvents: false, +import { webConfigSchema } from "./http-client/lib/shared"; + +export default defineConfig(async () => { + const adapters = await configureAdapters(); + const config = await loadConfig({ + schema: webConfigSchema, + adapters, + logger: { + warn: message => console.warn(`WARN [config]: ${message}`), }, - }, - resolve: { - alias: { - "@app": path.resolve("./src"), - "@public": path.resolve("./public"), - "@http-client": path.resolve("./http-client"), - "@tests": path.resolve("./tests"), + }); + return { + test: { + environment: "happy-dom", + include: ["tests/unit/**/*.test.ts"], + reporters: "verbose", }, - }, - build: { - outDir: "build", - rollupOptions: { - output: { - manualChunks: id => { - if (id.includes("lodash")) { - return "lodash"; - } else if (id.includes("katex")) { - return "katex"; - } else if (id.includes("node_modules")) { - return "vendor"; - } else if (id.includes("components")) { - return "components"; + plugins: [ + virtual({ + "virtual:config": config, + }), + svelte({ + // Reference: https://github.com/sveltejs/vite-plugin-svelte/issues/270#issuecomment-1033190138 + dynamicCompileOptions({ filename }) { + if ( + path.basename(filename) === "Clipboard.svelte" || + path.basename(filename) === "ExternalLink.svelte" || + path.basename(filename) === "Icon.svelte" + ) { + return { customElement: true }; } }, + compilerOptions: { dev: process.env.NODE_ENV !== "production" }, + }), + ], + server: { + host: "localhost", + port: 3000, + watch: { + // reference: https://stackoverflow.com/a/75238360 + useFsEvents: false, + }, + }, + resolve: { + alias: { + "@app": path.resolve("./src"), + "@public": path.resolve("./public"), + "@http-client": path.resolve("./http-client"), + "@tests": path.resolve("./tests"), + }, + }, + build: { + outDir: "build", + rollupOptions: { + output: { + manualChunks: (id: string) => { + if (id.includes("lodash")) { + return "lodash"; + } else if (id.includes("katex")) { + return "katex"; + } else if (id.includes("node_modules")) { + return "vendor"; + } else if (id.includes("components")) { + return "components"; + } + }, + }, }, }, - }, + }; }); + +export async function configureAdapters() { + return [ + // Order is important here, options overwrite previous ones + directoryAdapter({ + paths: path.resolve("./config"), + adapters: [ + { + extensions: [".json"], + adapterFactory: (path: string) => jsonAdapter({ path }), + }, + ], + }), + envAdapter(), + ]; +} + +function envAdapter(): Adapter { + return { + name: "env adapter", + read: async () => { + dotenv.config(); + + /* eslint-disable @typescript-eslint/naming-convention */ + const parsedEnvVars = parseEnvironmentVariables({ + PUBLIC_EXPLORER_URL: { default: null }, + SUPPORTED_API_VERSION: { default: null }, + DEFAULT_HTTPD_SCHEME: { default: null }, + DEFAULT_HTTPD_API_PORT: { parser: parseInt, default: null }, + HISTORY_COMMITS_PER_PAGE: { parser: parseInt, default: null }, + SUPPORT_WEBSITE: { default: null }, + PREFERRED_SEEDS: { parser: JSON.parse, default: null }, + }); + /* eslint-enable @typescript-eslint/naming-convention */ + + if (parsedEnvVars.success) { + const env = parsedEnvVars.env; + return { + publicExplorerUrl: env.PUBLIC_EXPLORER_URL, + supportedApiVersion: env.SUPPORTED_API_VERSION, + defaultHttpdApiPort: env.DEFAULT_HTTPD_API_PORT, + defaultHttpdScheme: env.DEFAULT_HTTPD_SCHEME, + historyCommitsPerPage: env.HISTORY_COMMITS_PER_PAGE, + supportWebsite: env.SUPPORT_WEBSITE, + preferredSeeds: env.PREFERRED_SEEDS, + }; + } + + // In case we aren't able to parse env variables we fallback to the config directory + return {}; + }, + }; +}