Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
Allow overriding configuration at run-time
Rūdolfs Ošiņš committed 1 year ago
commit 775cbd1c3730687547be18270e14dbc3746fc689
parent af334bb
16 files changed +105 -22
modified README.md
@@ -42,24 +42,54 @@ There are several ways to deploy the UI publicly. Here are two common options:
1. Fork this repository to create your own version
2. Configure your Vercel account to deploy the forked repository

+

## Configuration

-
There's two ways to configure the UI:
+
There are two ways to configure the UI: **at build time** and **at run time**.
+

+
### Build-Time Configuration

-
**Create a `local.json` config file**
+
This method is recommended when deploying to static hosting platforms such
+
as Vercel.

-
1. Copy [default.json][def] to a new file in the same folder called
+
#### Option 1: Create a `local.json` file
+

+
1. Copy [`default.json`][def] to a new file in the same directory called
   `local.json`.
-
2. Modify the properties in `local.json` to your preference.
+
2. Modify the properties in `local.json` to suit your setup.
+

+
#### Option 2: Use environment variables
+

+
1. Refer to [`custom-environment-variables.json`][env] for a list of supported
+
   environment variables.
+
2. Set the desired variables in your environment before building the UI.
+

+
> For advanced configuration options, refer to the [`node-config`][nco]
+
> documentation.
+

+

+
### Run-Time Configuration
+

+
This method is useful when the app is distributed as a precompiled static
+
JS/HTML bundle, such as when installed via a package manager.
+

+
You can build the app in a mode that loads configuration dynamically from the
+
server it's deployed to, instead of bundling it at build time.
+

+
To enable this behavior, set the environment variable `VITE_RUNTIME_CONFIG=true`
+
during the build:
+

+
```bash
+
VITE_RUNTIME_CONFIG=true npm run build
+
```

-
**Environment variables**
+
This will inject a blocking script into the `index.html` that attempts to load
+
the configuration from a pre-defined location (`/config.json`) on the server.

-
1. Check [custom-environment-variables.json][env] for all available environment
-
   variables.
-
2. Set the desired environment variables when building the UI.
+
The config file must be served as static content and must be publicly accessible.
+
The structure of the runtime `config.json` must match the shape of the
+
application's base configuration defined in `config/default.json`.

-
> For advanced configuration options, have a look at the [`node-config`][nco]
-
> package.

## Contributing

added global.d.ts
@@ -0,0 +1,8 @@
+
import type { Config } from "./module.d.ts";
+

+
declare global {
+
  // eslint-disable-next-line no-var, @typescript-eslint/naming-convention
+
  var __CONFIG__: Config;
+
}
+

+
export {};
modified http-client/lib/fetcher.ts
@@ -3,7 +3,7 @@

import type { ZodIssue, ZodType, TypeOf } from "zod";

-
import config from "virtual:config";
+
import config from "@app/lib/config";
import { satisfies } from "compare-versions";

export interface BaseUrl {
modified src/components/ErrorMessage.svelte
@@ -2,7 +2,7 @@
  import type { ComponentProps } from "svelte";
  import type { ErrorParam } from "@app/lib/router/definitions";

-
  import config from "virtual:config";
+
  import config from "@app/lib/config";
  import Command from "./Command.svelte";
  import ExternalLink from "./ExternalLink.svelte";
  import IconLarge from "./IconLarge.svelte";
added src/lib/config.ts
@@ -0,0 +1,19 @@
+
import buildTimeConfig from "virtual:config";
+
import mergeWith from "lodash/mergeWith";
+
import { isArray } from "lodash";
+

+
const customMerge = <T>(objValue: T, srcValue: T): T | undefined => {
+
  if (isArray(objValue)) {
+
    return srcValue;
+
  }
+
  return undefined;
+
};
+

+
const config = mergeWith(
+
  {},
+
  buildTimeConfig,
+
  globalThis.__CONFIG__ || {},
+
  customMerge,
+
) as typeof buildTimeConfig;
+

+
export default config;
modified src/lib/router.ts
@@ -5,7 +5,7 @@ import { get, writable } from "svelte/store";

import * as mutexExecutor from "@app/lib/mutexExecutor";
import * as utils from "@app/lib/utils";
-
import config from "virtual:config";
+
import config from "@app/lib/config";
import {
  repoRouteToPath,
  repoTitle,
modified src/modals/ErrorModal.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
-
  import config from "virtual:config";
+
  import config from "@app/lib/config";

  import Command from "@app/components/Command.svelte";
  import ExternalLink from "@app/components/ExternalLink.svelte";
modified src/views/nodes/SeedSelector.svelte
@@ -4,7 +4,7 @@
  import isEqual from "lodash/isEqual";
  import some from "lodash/some";

-
  import config from "virtual:config";
+
  import config from "@app/lib/config";
  import { HttpdClient } from "@http-client";
  import {
    addBookmark,
modified src/views/nodes/SeedSelector.ts
@@ -5,7 +5,7 @@ import storedWritable from "@app/lib/localStore";
import { array, number, string, object } from "zod";
import { get } from "svelte/store";

-
import config from "virtual:config";
+
import config from "@app/lib/config";

const seedSchema = object({
  hostname: string(),
modified src/views/nodes/router.ts
@@ -1,7 +1,7 @@
import type { BaseUrl, Node, NodeStats } from "@http-client";
import type { ErrorRoute, NotFoundRoute } from "@app/lib/router/definitions";

-
import config from "virtual:config";
+
import config from "@app/lib/config";
import { HttpdClient } from "@http-client";
import { ResponseError, ResponseParseError } from "@http-client/lib/fetcher";
import { baseUrlToString, isLocal } from "@app/lib/utils";
modified src/views/repos/Header/CloneButton.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
  import type { BaseUrl } from "@http-client";

-
  import config from "virtual:config";
+
  import config from "@app/lib/config";
  import { parseRepositoryId } from "@app/lib/utils";

  import Button from "@app/components/Button.svelte";
modified src/views/repos/History.svelte
@@ -9,7 +9,7 @@
  } from "@http-client";
  import type { RepoRoute } from "./router";

-
  import config from "virtual:config";
+
  import config from "@app/lib/config";
  import { HttpdClient } from "@http-client";
  import { baseUrlToString } from "@app/lib/utils";
  import { groupCommits } from "@app/lib/commit";
modified src/views/repos/Share.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
-
  import config from "virtual:config";
+
  import config from "@app/lib/config";
  import debounce from "lodash/debounce";
  import { toClipboard } from "@app/lib/utils";

modified src/views/repos/router.ts
@@ -24,7 +24,7 @@ import type {
} from "@http-client";

import * as Syntax from "@app/lib/syntax";
-
import config from "virtual:config";
+
import config from "@app/lib/config";
import { HttpdClient } from "@http-client";
import { ResponseError, ResponseParseError } from "@http-client/lib/fetcher";
import { cached } from "@app/lib/cache";
modified src/views/users/router.ts
@@ -2,7 +2,7 @@ import type { BaseUrl, NodeIdentity, NodeStats } from "@http-client";
import type { ErrorRoute, NotFoundRoute } from "@app/lib/router/definitions";

import * as utils from "@app/lib/utils";
-
import config from "virtual:config";
+
import config from "@app/lib/config";
import { HttpdClient } from "@http-client";
import { ResponseError, ResponseParseError } from "@http-client/lib/fetcher";
import { handleError } from "@app/views/nodes/error";
modified vite.config.ts
@@ -27,6 +27,32 @@ export default defineConfig({
      },
      compilerOptions: { dev: process.env.NODE_ENV !== "production" },
    }),
+
    {
+
      name: "inject-config-loader",
+
      transformIndexHtml() {
+
        if (process.env.VITE_RUNTIME_CONFIG === "true") {
+
          return [
+
            {
+
              tag: "script",
+
              attrs: {
+
                type: "text/javascript",
+
              },
+
              children: `
+
      try {
+
        const xhr = new XMLHttpRequest();
+
        xhr.open("GET", "/config.json", false);
+
        xhr.send(null);
+
        window.__CONFIG__ = JSON.parse(xhr.responseText);
+
      } catch {
+
        console.warn("Couldn't load config.json from the server, using built-in fallback config.");
+
      }
+
    `,
+
              injectTo: "head-prepend",
+
            },
+
          ];
+
        }
+
      },
+
    },
  ],
  server: {
    host: "localhost",