Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
Provide a type safe config with sane defaults on type level
Open did:key:z6MkkfM3...sVz5 opened 1 year ago

also adds multiline eslint ignores where needed

check check-visual check-unit-test check-http-client-unit-test check-radicle-httpd check-e2e check-build check-http

👉 Preview 👉 Workflow runs 👉 Branch on GitHub

23 files changed +263 -184 995e2393 → d66bc13a
added .env.example
@@ -0,0 +1,7 @@
+
PUBLIC_EXPLORER_URL="https://app.radicle.xyz/nodes/$host/$rid$path"
+
SUPPORTED_API_VERSION: "4.0.0"
+
DEFAULT_HTTPD_API_PORT: 443
+
DEFAULT_HTTPD_SCHEME: "https"
+
HISTORY_COMMITS_PER_PAGE: 30
+
SUPPORT_WEBSITE: "https://radicle.xyz"
+
PREFERRED_SEEDS: '[{ "hostname": "seed.radicle.garden", "port": 443, "scheme": "https" }]'
deleted config/custom-environment-variables.json
@@ -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"
-
  }
-
}
modified config/default.json
@@ -1,13 +1,9 @@
{
-
  "nodes": {
-
    "fallbackPublicExplorer": "https://app.radicle.xyz/nodes/$host/$rid$path",
-
    "apiVersion": "4.0.0",
-
    "defaultHttpdPort": 443,
-
    "defaultHttpdScheme": "https"
-
  },
-
  "source": {
-
    "commitsPerPage": 30
-
  },
+
  "publicExplorerUrl": "https://app.radicle.xyz/nodes/$host/$rid$path",
+
  "supportedApiVersion": "4.0.0",
+
  "defaultHttpdApiPort": 443,
+
  "defaultHttpdScheme": "https",
+
  "historyCommitsPerPage": 30,
  "supportWebsite": "https://radicle.zulipchat.com",
  "preferredSeeds": [
    {
modified config/test.json
@@ -1,8 +1,6 @@
{
-
  "nodes": {
-
    "defaultHttpdPort": 8081,
-
    "defaultHttpdScheme": "http"
-
  },
+
  "defaultHttpdApiPort": 8081,
+
  "defaultHttpdScheme": "http",
  "preferredSeeds": [
    {
      "hostname": "127.0.0.1",
modified 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<TypeOf<T>> {
    const response = await this.fetch({
      ...params,
-
      query: { ...params.query, v: config.nodes.apiVersion },
+
      query: { ...params.query, v: config.supportedApiVersion },
    });

    if (!response.ok) {
modified 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 defaults from "../../config/default.json";

export const scopeSchema = union([literal("followed"), literal("all")]);

@@ -121,3 +122,17 @@ export const authorSchema = object({
  id: string(),
  alias: string().optional(),
});
+

+
export type WebConfig = z.infer<typeof webConfigSchema>;
+

+
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),
+
});
modified 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"),
+
      },
+
    },
+
  };
});
modified 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;
}
modified 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.18",
        "twemoji": "^14.0.2",
-
        "zod": "^3.23.8"
+
        "zod": "^3.23.8",
+
        "zod-config": "^0.0.5"
      },
      "devDependencies": {
        "@eslint/js": "^9.8.0",
        "@playwright/test": "^1.46.1",
        "@sveltejs/vite-plugin-svelte": "^3.1.1",
        "@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",
@@ -49,7 +50,6 @@
        "@types/wait-on": "^5.3.4",
        "@typescript-eslint/parser": "^8.1.0",
        "chalk": "^5.3.0",
-
        "config": "^3.3.12",
        "eslint": "^9.9.0",
        "eslint-config-prettier": "^9.1.0",
        "eslint-plugin-svelte": "^2.43.0",
@@ -71,8 +71,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",
@@ -1118,12 +1126,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",
@@ -1951,18 +1953,6 @@
      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
      "dev": true
    },
-
    "node_modules/config": {
-
      "version": "3.3.12",
-
      "resolved": "https://registry.npmjs.org/config/-/config-3.3.12.tgz",
-
      "integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==",
-
      "dev": true,
-
      "dependencies": {
-
        "json5": "^2.2.3"
-
      },
-
      "engines": {
-
        "node": ">= 10.0.0"
-
      }
-
    },
    "node_modules/cross-spawn": {
      "version": "7.0.3",
      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -2114,6 +2104,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",
@@ -3274,18 +3276,6 @@
      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
      "dev": true
    },
-
    "node_modules/json5": {
-
      "version": "2.2.3",
-
      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
-
      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
-
      "dev": true,
-
      "bin": {
-
        "json5": "lib/cli.js"
-
      },
-
      "engines": {
-
        "node": ">=6"
-
      }
-
    },
    "node_modules/jsonfile": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz",
@@ -5416,6 +5406,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",
modified package.json
@@ -24,7 +24,6 @@
    "@playwright/test": "^1.46.1",
    "@sveltejs/vite-plugin-svelte": "^3.1.1",
    "@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",
@@ -34,7 +33,6 @@
    "@types/wait-on": "^5.3.4",
    "@typescript-eslint/parser": "^8.1.0",
    "chalk": "^5.3.0",
-
    "config": "^3.3.12",
    "eslint": "^9.9.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-plugin-svelte": "^2.43.0",
@@ -55,6 +53,7 @@
    "wait-on": "^7.2.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 +77,10 @@
    "plausible-tracker": "^0.3.9",
    "svelte": "^4.2.18",
    "twemoji": "^14.0.2",
-
    "zod": "^3.23.8"
+
    "zod": "^3.23.8",
+
    "zod-config": "^0.0.5"
+
  },
+
  "peerDependencies": {
+
    "dotenv": "^16.4.5"
  }
}
modified 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" },
    },
  ],
};
modified src/lib/markdown.ts
@@ -13,10 +13,10 @@ import { routeToPath } from "@app/lib/router";
import { canonicalize, isUrl } from "@app/lib/utils";

dompurify.setConfig({
-
  // eslint-disable-next-line @typescript-eslint/naming-convention
+
  /* eslint-disable @typescript-eslint/naming-convention */
  SANITIZE_DOM: false,
-
  // eslint-disable-next-line @typescript-eslint/naming-convention
  FORBID_TAGS: ["textarea", "style"],
+
  /* eslint-enable @typescript-eslint/naming-convention */
});

// Converts self closing anchor tags into empty anchor tags, to avoid erratic wrapping behaviour
modified src/lib/router.ts
@@ -148,15 +148,15 @@ export async function replace(newRoute: Route): Promise<void> {
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,
    };
  }
}
modified src/modals/ColorPaletteModal.svelte
@@ -11,22 +11,18 @@
        (acc, sheet) =>
          (acc = [
            ...acc,
-
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+
            /* eslint-disable @typescript-eslint/ban-ts-comment */
            // @ts-ignore
            ...Array.from(sheet.cssRules).reduce(
              (def, rule) =>
-
                // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                // @ts-ignore
                (def =
-
                  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                  // @ts-ignore
                  rule.selectorText === ":root"
                    ? [
                        ...def,
-
                        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                        // @ts-ignore
                        ...Array.from(rule.style).filter(name =>
-
                          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
                          // @ts-ignore
                          name.startsWith(variableName),
                        ),
@@ -34,6 +30,7 @@
                    : def),
              [],
            ),
+
            /* eslint-enable @typescript-eslint/ban-ts-comment */
          ]),
        [],
      );
modified 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) {
modified 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}`;
modified src/views/projects/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}://${
modified src/views/projects/History.svelte
@@ -61,7 +61,7 @@
      const response = await api.project.getAllCommits(project.id, {
        parent: allCommitHeaders[0].id,
        page,
-
        perPage: config.source.commitsPerPage,
+
        perPage: config.historyCommitsPerPage,
      });
      allCommitHeaders = [...allCommitHeaders, ...response];
    } catch (e) {
modified src/views/projects/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);
modified src/views/projects/router.ts
@@ -594,7 +594,7 @@ async function loadHistoryView(
    api.project.getAllCommits(project.id, {
      parent: commitId,
      page: 0,
-
      perPage: config.source.commitsPerPage,
+
      perPage: config.historyCommitsPerPage,
    }),
  ]);

modified tests/support/fixtures.ts
@@ -1,4 +1,3 @@
-
/* eslint-disable @typescript-eslint/naming-convention */
import type { Config } from "@http-client";
import type { PeerManager, RadiclePeer } from "./peerManager.js";
import type * as Stream from "node:stream";
@@ -617,6 +616,7 @@ export const nodeRemote = "z6MktULudTtAsAhRegYPiZ6631RV3viv12qd4GQF8z1xB22S";
export const shortNodeRemote = "z6MktU…1xB22S";
export const defaultHttpdPort = 8081;
export const gitOptions = {
+
  /* eslint-disable @typescript-eslint/naming-convention */
  alice: {
    GIT_AUTHOR_NAME: "Alice Liddell",
    GIT_AUTHOR_EMAIL: "alice@radicle.xyz",
@@ -633,6 +633,7 @@ export const gitOptions = {
    GIT_COMMITTER_EMAIL: "bob@radicle.xyz",
    GIT_COMMITTER_DATE: "1671627600",
  },
+
  /* eslint-enable @typescript-eslint/naming-convention */
};
export const defaultConfig: Config = {
  publicExplorer: "https://app.radicle.xyz/nodes/$host/$rid$path",
modified tests/support/peerManager.ts
@@ -1,4 +1,3 @@
-
/* eslint-disable @typescript-eslint/naming-convention */
import type { Config, BaseUrl } from "@http-client";
import type * as Execa from "execa";

@@ -209,10 +208,12 @@ export class RadiclePeer {

    const env = {
      ...gitOptions,
+
      /* eslint-disable @typescript-eslint/naming-convention */
      RAD_HOME: radHome,
      RAD_PASSPHRASE: "asdf",
      RAD_KEYGEN_SEED: node,
      RAD_SOCKET: socket,
+
      /* eslint-enable @typescript-eslint/naming-convention */
    };

    await execa("rad", ["auth", "--alias", name], { env });
@@ -380,6 +381,7 @@ export class RadiclePeer {
    const childProcess = execa(cmd, args, {
      ...opts,
      env: {
+
        /* eslint-disable @typescript-eslint/naming-convention */
        GIT_CONFIG_GLOBAL: "/dev/null",
        GIT_CONFIG_NOSYSTEM: "1",
        RAD_HOME: this.#radHome,
@@ -387,6 +389,7 @@ export class RadiclePeer {
        RAD_LOCAL_TIME: "1671125284",
        RAD_KEYGEN_SEED: this.#radSeed,
        RAD_SOCKET: this.#socket,
+
        /* eslint-enable @typescript-eslint/naming-convention */
        ...opts?.env,
        ...this.#gitOptions,
      },
modified 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 {};
+
    },
+
  };
+
}