Repository: temporalio/ui
Pull request #300 (2022-03-11 00:42:20)

Fix IsCloud function by exposing it through VITE_ vars
<!--- Note to EXTERNAL Contributors -->
<!-- Thanks for opening a PR! 
If it is a significant code change, please **make sure there is an open issue** for this. 
We work best with you when we have accepted the idea first before you code. -->

<!--- For ALL Contributors 👇 -->

## What was changed
<!-- Describe what has changed in this PR -->

## Why?
<!-- Tell your future self why have you made these changes -->

## Checklist
<!--- add/delete as needed --->

1. Closes <!-- add issue number here -->

2. How was this tested:
<!--- Please describe how you tested your changes/how we can test them -->

3. Any docs updates needed?
<!--- update README if applicable
      or point out where to update docs.temporal.io -->


cloudflare-pages[bot]: ## Deploying with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>8e2566e</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://25217c14.ui-c3x.pages.dev'>https://25217c14.ui-c3x.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/temporal-pizza/25217c14-634d-4978-bc1b-4491c57120b1)


Patch:
diff --git a/README.md b/README.md
index bd0e39c3..5c0aadd8 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ Developing the UI has the same prequisites as trying it out. Once you've created
 npm start
 ```
 
-By default, the application will start up with a version of the UI for the local version of Temporal. You can start the UI for Temporal Cloud by setting the `TEMPORAL_UI_BUILD_TARGET` target to `cloud`. Alternatively, you can use either of the following scripts:
+By default, the application will start up with a version of the UI for the local version of Temporal. You can start the UI for Temporal Cloud by setting the `VITE_TEMPORAL_UI_BUILD_TARGET` target to `cloud`. Alternatively, you can use either of the following scripts:
 
 ```bash
 npm run dev:local
@@ -42,7 +42,7 @@ npm run dev:cloud
 
 ## Building
 
-The Temporal UI _must_ be built for either the local version or Temporal Cloud. You must set the `TEMPORAL_UI_BUILD_TARGET` environment variable in order to build the assets. This will be set for you if you use either of the following `npm` scripts.
+The Temporal UI _must_ be built for either the local version or Temporal Cloud. You must set the `VITE_TEMPORAL_UI_BUILD_TARGET` environment variable in order to build the assets. This will be set for you if you use either of the following `npm` scripts.
 
 ```bash
 npm run build:local
diff --git a/package.json b/package.json
index 1fd961de..ed0fc6ea 100644
--- a/package.json
+++ b/package.json
@@ -4,19 +4,19 @@
   "type": "module",
   "scripts": {
     "start": "npm run dev:local -- --open",
-    "dev:local": ". ./.env && TEMPORAL_UI_BUILD_TARGET=local svelte-kit dev",
-    "dev:cloud": "TEMPORAL_UI_BUILD_TARGET=cloud svelte-kit dev",
-    "build:local": "TEMPORAL_UI_BUILD_TARGET=local svelte-kit build",
-    "build:cloud": "TEMPORAL_UI_BUILD_TARGET=cloud svelte-kit build",
+    "dev:local": ". ./.env && VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-kit dev",
+    "dev:cloud": "VITE_TEMPORAL_UI_BUILD_TARGET=cloud svelte-kit dev",
+    "build:local": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-kit build",
+    "build:cloud": "VITE_TEMPORAL_UI_BUILD_TARGET=cloud svelte-kit build",
     "test": "TZ=UTC jest",
-    "preview:local": "TEMPORAL_UI_BUILD_TARGET=local svelte-kit preview",
-    "preview:cloud": "TEMPORAL_UI_BUILD_TARGET=cloud svelte-kit preview",
-    "check": "TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json",
-    "check:watch": "TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch",
-    "check:local": "TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json",
-    "check:local:watch": "TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch",
-    "check:cloud": "TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json",
-    "check:cloud:watch": "TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch",
+    "preview:local": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-kit preview",
+    "preview:cloud": "VITE_TEMPORAL_UI_BUILD_TARGET=cloud svelte-kit preview",
+    "check": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json",
+    "check:watch": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch",
+    "check:local": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json",
+    "check:local:watch": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch",
+    "check:cloud": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json",
+    "check:cloud:watch": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch",
     "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
     "format": "prettier --write --plugin-search-dir=. .",
     "cypress:open": "TEMPORAL_API_HOST=http://localhost:3000 cypress open"
diff --git a/src/env.d.ts b/src/env.d.ts
new file mode 100644
index 00000000..bd6df49a
--- /dev/null
+++ b/src/env.d.ts
@@ -0,0 +1,10 @@
+/// <reference types="vite/client" />
+
+interface ImportMetaEnv {
+  readonly VITE_TEMPORAL_UI_BUILD_TARGET: string;
+  readonly VITE_API: string;
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv;
+}
diff --git a/src/lib/components/select/namespace-select.svelte b/src/lib/components/select/namespace-select.svelte
index cde2954c..443e734b 100644
--- a/src/lib/components/select/namespace-select.svelte
+++ b/src/lib/components/select/namespace-select.svelte
@@ -20,7 +20,7 @@
 
 <div class="w-full">
   <Select dark bind:value={selectedNamespace} on:change={switchNamespace}>
-    {#if isCloud}
+    {#if isCloud()}
       <Option value={selectedNamespace}>{selectedNamespace}</Option>
     {:else}
       {#each namespaces as namespace}
diff --git a/src/lib/stores/cluster.ts b/src/lib/stores/cluster.ts
index 0f354a0c..02793ca3 100644
--- a/src/lib/stores/cluster.ts
+++ b/src/lib/stores/cluster.ts
@@ -7,7 +7,7 @@ import { isCloud } from '$lib/utilities/env';
 export const cluster = writable<GetClusterInfoResponse>({});
 
 export const loadCluster = async (): Promise<void> => {
-  if (isCloud) {
+  if (isCloud()) {
     return Promise.resolve();
   }
   const clusterRes = await fetchCluster();
diff --git a/src/lib/stores/user.ts b/src/lib/stores/user.ts
index 7ba19610..cf74a21b 100644
--- a/src/lib/stores/user.ts
+++ b/src/lib/stores/user.ts
@@ -2,7 +2,7 @@ import { writable } from 'svelte/store';
 
 import { fetchUser } from '$lib/services/user-service';
 
-export const user = writable<User>(null);
+export const user = writable<User | null>(null);
 
 export const loadUser = async (): Promise<void> => {
   const userRes = await fetchUser();
diff --git a/src/lib/utilities/decode-payload.ts b/src/lib/utilities/decode-payload.ts
index 343a87a0..b3ac5f67 100644
--- a/src/lib/utilities/decode-payload.ts
+++ b/src/lib/utilities/decode-payload.ts
@@ -5,14 +5,14 @@ import type { DataConverterWebsocketInterface } from '$lib/utilities/data-conver
 import { convertPayload } from '$lib/services/data-converter';
 import { browser } from '$app/env';
 
-const atob = browser ? window.atob : (str) => str;
+const atob = browser ? window.atob : (str: string) => str;
 
 export function decodePayload(
   payload: Payload,
   // This could decode to any object. So we either use the payload object passed in or decode it
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
 ): Payload | Record<any, any> | string {
-  const encoding = atob(String(payload.metadata.encoding));
+  const encoding = atob(String(payload?.metadata?.encoding ?? ''));
   // Help users out with an english encoding
   (payload.metadata.encodingDecoded as unknown as string) = encoding;
 
diff --git a/src/lib/utilities/env.ts b/src/lib/utilities/env.ts
index 244a0625..97f55e7f 100644
--- a/src/lib/utilities/env.ts
+++ b/src/lib/utilities/env.ts
@@ -1,3 +1,5 @@
+import { getEnvironment } from './get-environment';
+
 const validTargets = ['local', 'cloud'];
 
 const throwErrorIfInvalid = (callback: () => boolean) => () => {
@@ -5,27 +7,23 @@ const throwErrorIfInvalid = (callback: () => boolean) => () => {
   const validOptions = `Valid options: ${validTargets.join(', ')}.`;
 
   if (process.env.NODE_ENV !== 'production' && !buildTarget) {
-    process.env.TEMPORAL_UI_BUILD_TARGET = 'local';
+    process.env.VITE_TEMPORAL_UI_BUILD_TARGET = 'local';
   }
 
   if (!buildTarget) {
     throw new Error(
-      `No TEMPORAL_UI_BUILD_TARGET environment variable provided. ${validOptions}`,
+      `No VITE_TEMPORAL_UI_BUILD_TARGET environment variable provided. ${validOptions}`,
     );
   }
 
   if (!validTargets.includes(buildTarget)) {
     throw new Error(
-      `Invalid TEMPORAL_UI_BUILD_TARGET. You provided: ${buildTarget}. ${validOptions}`,
+      `Invalid VITE_TEMPORAL_UI_BUILD_TARGET. You provided: ${buildTarget}. ${validOptions}`,
     );
   }
 
   return callback();
 };
 
-export const getEnvironment: () => string = () => {
-  return process.env.TEMPORAL_UI_BUILD_TARGET || 'local';
-};
-
 export const isLocal = throwErrorIfInvalid(() => getEnvironment() === 'local');
 export const isCloud = throwErrorIfInvalid(() => getEnvironment() === 'cloud');
diff --git a/src/lib/utilities/get-environment.ts b/src/lib/utilities/get-environment.ts
new file mode 100644
index 00000000..18ec0d24
--- /dev/null
+++ b/src/lib/utilities/get-environment.ts
@@ -0,0 +1,3 @@
+export function getEnvironment(): string {
+  return import.meta.env.VITE_TEMPORAL_UI_BUILD_TARGET ?? 'local';
+}
diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte
index 9a523b38..806bc33b 100644
--- a/src/routes/__layout.svelte
+++ b/src/routes/__layout.svelte
@@ -8,6 +8,7 @@
 
   import { loadUser } from '$lib/stores/user';
   import { loadCluster } from '$lib/stores/cluster';
+  import { isCloud } from '$lib/utilities/env';
 
   import type {
     DescribeNamespaceResponse,
@@ -21,7 +22,7 @@
   };
 
   async function loadNamespaces(): Promise<ListNamespacesResponse> {
-    if (isCloud) {
+    if (isCloud()) {
       return Promise.resolve(emptyNamespace);
     }
 
@@ -59,7 +60,6 @@
   import Notifications from '$lib/components/notifications.svelte';
   import Banner from '$lib/components/banner.svelte';
   import { ErrorBoundary } from '$lib/components/error-boundary';
-  import { isCloud } from '$lib/utilities/env';
 
   export let namespaces: DescribeNamespaceResponse[];
 
diff --git a/svelte.config.js b/svelte.config.js
index 23977a8d..38625a83 100644
--- a/svelte.config.js
+++ b/svelte.config.js
@@ -3,12 +3,12 @@ import preprocess from 'svelte-preprocess';
 import adapter from '@sveltejs/adapter-static';
 import chalk from 'chalk';
 
-const buildTarget = process.env.TEMPORAL_UI_BUILD_TARGET;
+const buildTarget = process.env.VITE_TEMPORAL_UI_BUILD_TARGET;
 const validBuildTargets = ['local', 'cloud'];
 const isProduction = process.env.NODE_ENV === 'production';
 
 if (!isProduction && !buildTarget) {
-  process.env.TEMPORAL_UI_BUILD_TARGET = 'local';
+  process.env.VITE_TEMPORAL_UI_BUILD_TARGET = 'local';
 }
 
 // Don't build anything unless a valid build target has been set.
@@ -17,7 +17,7 @@ if (!buildTarget || !validBuildTargets.includes(buildTarget)) {
 
   console.error(
     chalk.red('ERROR:'),
-    `You must provide a TEMPORAL_UI_BUILD_TARGET environment variable. Valid targets: ${valid}.`,
+    `You must provide a VITE_TEMPORAL_UI_BUILD_TARGET environment variable. Valid targets: ${valid}.`,
   );
 
   if (buildTarget) {
diff --git a/tsconfig.json b/tsconfig.json
index 0f039c98..8233db1d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,7 @@
 {
   "compilerOptions": {
     "moduleResolution": "node",
+    // "strict": true,
     "module": "es2020",
     "lib": ["es2020", "dom", "DOM.Iterable"],
     "target": "es2019",


Test patch:
diff --git a/src/lib/utilities/env.test.ts b/src/lib/utilities/env.test.ts
index 1c4065b2..a04d5031 100644
--- a/src/lib/utilities/env.test.ts
+++ b/src/lib/utilities/env.test.ts
@@ -1,59 +1,44 @@
-import { isLocal, isCloud, getEnvironment } from './env';
+// Need to mock out the getEnvironment out to the jest mock function
+/* eslint-disable @typescript-eslint/no-explicit-any */
+jest.mock('./get-environment');
 
-const initialValue = process.env.TEMPORAL_UI_BUILD_TARGET;
+import { isLocal, isCloud } from './env';
+import { getEnvironment } from './get-environment';
 
-describe('Build Environment', () => {
-  afterEach(() => {
-    process.env.TEMPORAL_UI_BUILD_TARGET = initialValue;
-  });
-
-  describe('getEnvironment', () => {
-    it('should return "local" is TEMPORAL_UI_BUILD_TARGET is set to "local"', () => {
-      process.env.TEMPORAL_UI_BUILD_TARGET = 'local';
-      expect(getEnvironment()).toBe('local');
-    });
-
-    it('should return "cloud" is TEMPORAL_UI_BUILD_TARGET is set to "cloud"', () => {
-      process.env.TEMPORAL_UI_BUILD_TARGET = 'cloud';
-      expect(getEnvironment()).toBe('cloud');
-    });
-
-    it('should return "local" is TEMPORAL_UI_BUILD_TARGET is undefined', () => {
-      delete process.env.TEMPORAL_UI_BUILD_TARGET;
-      expect(getEnvironment()).toBe('local');
-    });
-  });
+const setEnvLocal = () => 'local';
+const setEnvCloud = () => 'cloud';
 
+describe('Build Environment', () => {
   describe('isLocal', () => {
-    it('should return true if TEMPORAL_UI_BUILD_TARGET is set to "local"', () => {
-      process.env.TEMPORAL_UI_BUILD_TARGET = 'local';
+    it('should return true if VITE_TEMPORAL_UI_BUILD_TARGET is set to "local"', () => {
+      (getEnvironment as any).mockImplementation(setEnvLocal);
       expect(isLocal()).toBe(true);
     });
 
-    it('should return false if TEMPORAL_UI_BUILD_TARGET is set to "local"', () => {
-      process.env.TEMPORAL_UI_BUILD_TARGET = 'cloud';
+    it('should return false if VITE_TEMPORAL_UI_BUILD_TARGET is set to "local"', () => {
+      (getEnvironment as any).mockImplementation(setEnvCloud);
       expect(isLocal()).toBe(false);
     });
 
-    it('should throw an error if TEMPORAL_UI_BUILD_TARGET is set to something other than "cloud" or "local"', () => {
-      process.env.TEMPORAL_UI_BUILD_TARGET = 'not_valid';
+    it('should throw an error if VITE_TEMPORAL_UI_BUILD_TARGET is set to something other than "cloud" or "local"', () => {
+      (getEnvironment as any).mockImplementation(() => 'not_valid');
       expect(() => isLocal()).toThrow();
     });
   });
 
   describe('isCloud', () => {
-    it('should return true if TEMPORAL_UI_BUILD_TARGET is set to "cloud"', () => {
-      process.env.TEMPORAL_UI_BUILD_TARGET = 'cloud';
+    it('should return true if VITE_TEMPORAL_UI_BUILD_TARGET is set to "cloud"', () => {
+      (getEnvironment as any).mockImplementation(setEnvCloud);
       expect(isCloud()).toBe(true);
     });
 
-    it('should return false if TEMPORAL_UI_BUILD_TARGET is set to "cloud"', () => {
-      process.env.TEMPORAL_UI_BUILD_TARGET = 'local';
+    it('should return false if VITE_TEMPORAL_UI_BUILD_TARGET is set to "local"', () => {
+      (getEnvironment as any).mockImplementation(setEnvLocal);
       expect(isCloud()).toBe(false);
     });
 
-    it('should throw an error if TEMPORAL_UI_BUILD_TARGET is set to something other than "cloud" or "local"', () => {
-      process.env.TEMPORAL_UI_BUILD_TARGET = 'not_valid';
+    it('should throw an error if VITE_TEMPORAL_UI_BUILD_TARGET is set to something other than "cloud" or "local"', () => {
+      (getEnvironment as any).mockImplementation(() => 'not_valid');
       expect(() => isCloud()).toThrow();
     });
   });