From 0185c0a0ab028b9cb17d1f282693530b3f079888 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:39:31 -0500 Subject: [PATCH 01/51] init functinoality --- .../environment-variables/ActionsCell.svelte | 71 ++++ .../environment-variables/AddEnvDialog.svelte | 356 ++++++++++++++++++ .../DeleteEnvDialog.svelte | 49 +++ .../EditEnvDialog.svelte | 181 +++++++++ .../EnvVariablesTable.svelte | 44 +++ .../environment-variables/EnvWorkspace.svelte | 179 +++++++++ .../environment-variables/KeyCell.svelte | 44 +++ .../environment-variables/ValueCell.svelte | 65 ++++ .../features/environment-variables/types.ts | 4 + .../features/environment-variables/utils.ts | 32 ++ .../(workspace)/files/[...file]/+page.svelte | 5 + 11 files changed, 1030 insertions(+) create mode 100644 web-common/src/features/environment-variables/ActionsCell.svelte create mode 100644 web-common/src/features/environment-variables/AddEnvDialog.svelte create mode 100644 web-common/src/features/environment-variables/DeleteEnvDialog.svelte create mode 100644 web-common/src/features/environment-variables/EditEnvDialog.svelte create mode 100644 web-common/src/features/environment-variables/EnvVariablesTable.svelte create mode 100644 web-common/src/features/environment-variables/EnvWorkspace.svelte create mode 100644 web-common/src/features/environment-variables/KeyCell.svelte create mode 100644 web-common/src/features/environment-variables/ValueCell.svelte create mode 100644 web-common/src/features/environment-variables/types.ts create mode 100644 web-common/src/features/environment-variables/utils.ts diff --git a/web-common/src/features/environment-variables/ActionsCell.svelte b/web-common/src/features/environment-variables/ActionsCell.svelte new file mode 100644 index 00000000000..96dd86ced4b --- /dev/null +++ b/web-common/src/features/environment-variables/ActionsCell.svelte @@ -0,0 +1,71 @@ + + +
+ + + + + + + + { + isEditDialogOpen = true; + }} + > + + Edit + + { + isDeleteDialogOpen = true; + }} + > + + Delete + + + +
+ + + diff --git a/web-common/src/features/environment-variables/AddEnvDialog.svelte b/web-common/src/features/environment-variables/AddEnvDialog.svelte new file mode 100644 index 00000000000..181eb461672 --- /dev/null +++ b/web-common/src/features/environment-variables/AddEnvDialog.svelte @@ -0,0 +1,356 @@ + + + { + if (!isOpen) { + handleReset(); + } + }} + onOutsideClick={() => { + open = false; + handleReset(); + }} +> + + + + + + Add environment variables + +
+
+ + +
+
Variables
+
+ {#each $form.variables as variable, index} +
+ handleKeyChange(index, e)} + onBlur={() => { + checkForExistingKeys(); + }} + /> + handleValueChange(index, e)} + /> + { + if ($form.variables.length === 1) { + handleReset(); + } else { + handleRemove(index); + } + }} + > + + +
+ {/each} +
+ +
+ {#if $allErrors.length} +
    + {#each $allErrors as error} +
  • + {$form.variables[getKeyFromError(error)].key} + + {error.messages} + +
  • + {/each} +
+ {/if} + {#if isKeyAlreadyExists} +
+

+ {#if Object.values(inputErrors).every((err) => err.type === "draft")} + {Object.keys(inputErrors).length > 1 + ? "Duplicate keys are not allowed" + : "This key is duplicated"} + {:else if Object.values(inputErrors).every((err) => err.type === "existing")} + {Object.keys(inputErrors).length > 1 + ? "These keys already exist" + : "This key already exists"} + {:else} + Some keys are duplicated or already exist + {/if} +

+
+ {/if} +
+
+
+
+ + + + + +
+
diff --git a/web-common/src/features/environment-variables/DeleteEnvDialog.svelte b/web-common/src/features/environment-variables/DeleteEnvDialog.svelte new file mode 100644 index 00000000000..29d6823559c --- /dev/null +++ b/web-common/src/features/environment-variables/DeleteEnvDialog.svelte @@ -0,0 +1,49 @@ + + + + + + + + + Delete environment variable + + + Are you sure you want to delete the environment variable {keyName}? + + + + + + + diff --git a/web-common/src/features/environment-variables/EditEnvDialog.svelte b/web-common/src/features/environment-variables/EditEnvDialog.svelte new file mode 100644 index 00000000000..0ffeb1c07e9 --- /dev/null +++ b/web-common/src/features/environment-variables/EditEnvDialog.svelte @@ -0,0 +1,181 @@ + + + { + if (!isOpen) { + handleReset(); + } + }} +> + + + + + + Edit environment variable + +
+
+
+
Variable
+
+
+ + +
+ {#if $errors.key} +
+

+ {$errors.key} +

+
+ {/if} + {#if isKeyAlreadyExists} +
+

+ This key already exists +

+
+ {/if} +
+
+
+
+ + + + + +
+
diff --git a/web-common/src/features/environment-variables/EnvVariablesTable.svelte b/web-common/src/features/environment-variables/EnvVariablesTable.svelte new file mode 100644 index 00000000000..86083120601 --- /dev/null +++ b/web-common/src/features/environment-variables/EnvVariablesTable.svelte @@ -0,0 +1,44 @@ + + + diff --git a/web-common/src/features/environment-variables/EnvWorkspace.svelte b/web-common/src/features/environment-variables/EnvWorkspace.svelte new file mode 100644 index 00000000000..640fbc85a45 --- /dev/null +++ b/web-common/src/features/environment-variables/EnvWorkspace.svelte @@ -0,0 +1,179 @@ + + + +
+ +
+
+
+ {#each [ + { view: "code", icon: Code2Icon, label: "Code view" }, + { view: "viz", icon: Settings, label: "No-code view" }, + ] as { view, icon: Icon, label } (view)} + + + + {label} + + + {/each} + +
+
+ {#if viewMode === "viz"} + + {/if} +
+
+ + + {#if viewMode === "code"} + + {:else} +
+ +
+ {/if} +
+
+ + + + diff --git a/web-common/src/features/environment-variables/KeyCell.svelte b/web-common/src/features/environment-variables/KeyCell.svelte new file mode 100644 index 00000000000..2c7f7697647 --- /dev/null +++ b/web-common/src/features/environment-variables/KeyCell.svelte @@ -0,0 +1,44 @@ + + +
+ + + + + {copied ? "Copied!" : "Click to copy"} + + + + {#if subtitle} + + {subtitle} + + {/if} +
+ + diff --git a/web-common/src/features/environment-variables/ValueCell.svelte b/web-common/src/features/environment-variables/ValueCell.svelte new file mode 100644 index 00000000000..cc9bc97232c --- /dev/null +++ b/web-common/src/features/environment-variables/ValueCell.svelte @@ -0,0 +1,65 @@ + + +
+ + + {#if showValue} + + + + {copied ? "Copied!" : "Click to copy"} + + + {:else} + ••••••••••• + {/if} +
diff --git a/web-common/src/features/environment-variables/types.ts b/web-common/src/features/environment-variables/types.ts new file mode 100644 index 00000000000..0ca923035b1 --- /dev/null +++ b/web-common/src/features/environment-variables/types.ts @@ -0,0 +1,4 @@ +export type EnvVariable = { + key: string; + value: string; +}; diff --git a/web-common/src/features/environment-variables/utils.ts b/web-common/src/features/environment-variables/utils.ts new file mode 100644 index 00000000000..5910f4f112c --- /dev/null +++ b/web-common/src/features/environment-variables/utils.ts @@ -0,0 +1,32 @@ +/** + * Check if a key is a duplicate (case-insensitive) + */ +export function isDuplicateKey( + key: string, + existingKeys: string[], + currentKey?: string, +): boolean { + const normalizedKey = key.toLowerCase(); + const normalizedCurrentKey = currentKey?.toLowerCase(); + + return existingKeys.some((existingKey) => { + const normalizedExistingKey = existingKey.toLowerCase(); + + // Skip if this is the current key being edited + if ( + normalizedCurrentKey && + normalizedExistingKey === normalizedCurrentKey + ) { + return false; + } + + return normalizedExistingKey === normalizedKey; + }); +} + +/** + * Validate key format + */ +export function isValidKey(key: string): boolean { + return /^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(key); +} diff --git a/web-local/src/routes/(application)/(workspace)/files/[...file]/+page.svelte b/web-local/src/routes/(application)/(workspace)/files/[...file]/+page.svelte index 7965f3f3022..634727d4033 100644 --- a/web-local/src/routes/(application)/(workspace)/files/[...file]/+page.svelte +++ b/web-local/src/routes/(application)/(workspace)/files/[...file]/+page.svelte @@ -13,6 +13,7 @@ import ExploreWorkspace from "@rilldata/web-common/features/workspaces/ExploreWorkspace.svelte"; import MetricsWorkspace from "@rilldata/web-common/features/workspaces/MetricsWorkspace.svelte"; import ModelWorkspace from "@rilldata/web-common/features/workspaces/ModelWorkspace.svelte"; + import EnvWorkspace from "@rilldata/web-common/features/environment-variables/EnvWorkspace.svelte"; import WorkspaceContainer from "@rilldata/web-common/layout/workspace/WorkspaceContainer.svelte"; import WorkspaceEditorContainer from "@rilldata/web-common/layout/workspace/WorkspaceEditorContainer.svelte"; import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient.js"; @@ -53,6 +54,8 @@ $: workspace = workspaces.get(resourceKind ?? $inferredResourceKind); + $: isEnvFile = path === "/.env"; + $: resourceQuery = getResource(queryClient, instanceId); $: resource = $resourceQuery.data; @@ -95,6 +98,8 @@
{#if workspace} + {:else if isEnvFile} + {:else} Date: Tue, 16 Dec 2025 10:46:45 -0500 Subject: [PATCH 02/51] nit fix for saving --- .../environment-variables/EnvWorkspace.svelte | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/web-common/src/features/environment-variables/EnvWorkspace.svelte b/web-common/src/features/environment-variables/EnvWorkspace.svelte index 640fbc85a45..90669f2231b 100644 --- a/web-common/src/features/environment-variables/EnvWorkspace.svelte +++ b/web-common/src/features/environment-variables/EnvWorkspace.svelte @@ -49,31 +49,34 @@ return variables.map((v) => `${v.key}=${v.value}`).join("\n") + "\n"; } - function updateEnvFile(variables: EnvVariable[]) { + async function updateEnvFile(variables: EnvVariable[]) { const newContent = serializeEnvFile(variables); + // Update editor content without autosave fileArtifact.updateEditorContent(newContent, false, false); + // Force save since .env has autosave disabled + await fileArtifact.saveLocalContent(true); } function handleToggleView() { viewMode = viewMode === "code" ? "viz" : "code"; } - function handleAddVariables(event: CustomEvent<{ variables: EnvVariable[] }>) { + async function handleAddVariables(event: CustomEvent<{ variables: EnvVariable[] }>) { const newVariables = event.detail.variables; const updatedVariables = [...envVariables, ...newVariables]; - updateEnvFile(updatedVariables); + await updateEnvFile(updatedVariables); } - function handleEditVariable(oldKey: string, key: string, value: string) { + async function handleEditVariable(oldKey: string, key: string, value: string) { const updatedVariables = envVariables.map((v) => v.key === oldKey ? { key, value } : v, ); - updateEnvFile(updatedVariables); + await updateEnvFile(updatedVariables); } - function handleDeleteVariable(key: string) { + async function handleDeleteVariable(key: string) { const updatedVariables = envVariables.filter((v) => v.key !== key); - updateEnvFile(updatedVariables); + await updateEnvFile(updatedVariables); } $: actionsColumn = { From 8b57993dad446bb3df96c10bfddda94529ced419 Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Tue, 16 Dec 2025 10:49:36 -0500 Subject: [PATCH 03/51] prettier --- .../environment-variables/ActionsCell.svelte | 4 ++- .../environment-variables/AddEnvDialog.svelte | 5 ++- .../DeleteEnvDialog.svelte | 4 ++- .../environment-variables/EnvWorkspace.svelte | 34 ++++++++++++------- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/web-common/src/features/environment-variables/ActionsCell.svelte b/web-common/src/features/environment-variables/ActionsCell.svelte index 96dd86ced4b..16affb6bfec 100644 --- a/web-common/src/features/environment-variables/ActionsCell.svelte +++ b/web-common/src/features/environment-variables/ActionsCell.svelte @@ -17,7 +17,9 @@ let isEditDialogOpen = false; let isDeleteDialogOpen = false; - function handleSave(event: CustomEvent<{ oldKey: string; key: string; value: string }>) { + function handleSave( + event: CustomEvent<{ oldKey: string; key: string; value: string }>, + ) { onSave(event.detail.oldKey, event.detail.key, event.detail.value); } diff --git a/web-common/src/features/environment-variables/AddEnvDialog.svelte b/web-common/src/features/environment-variables/AddEnvDialog.svelte index 181eb461672..23b6914b4f6 100644 --- a/web-common/src/features/environment-variables/AddEnvDialog.svelte +++ b/web-common/src/features/environment-variables/AddEnvDialog.svelte @@ -181,7 +181,10 @@ if (key) { variables.push({ key: key.trim(), - value: valueParts.join("=").trim().replace(/^["']|["']$/g, ""), + value: valueParts + .join("=") + .trim() + .replace(/^["']|["']$/g, ""), }); } } diff --git a/web-common/src/features/environment-variables/DeleteEnvDialog.svelte b/web-common/src/features/environment-variables/DeleteEnvDialog.svelte index 29d6823559c..de4e2bfcb45 100644 --- a/web-common/src/features/environment-variables/DeleteEnvDialog.svelte +++ b/web-common/src/features/environment-variables/DeleteEnvDialog.svelte @@ -43,7 +43,9 @@ - + diff --git a/web-common/src/features/environment-variables/EnvWorkspace.svelte b/web-common/src/features/environment-variables/EnvWorkspace.svelte index 90669f2231b..13c60a5a19a 100644 --- a/web-common/src/features/environment-variables/EnvWorkspace.svelte +++ b/web-common/src/features/environment-variables/EnvWorkspace.svelte @@ -61,13 +61,19 @@ viewMode = viewMode === "code" ? "viz" : "code"; } - async function handleAddVariables(event: CustomEvent<{ variables: EnvVariable[] }>) { + async function handleAddVariables( + event: CustomEvent<{ variables: EnvVariable[] }>, + ) { const newVariables = event.detail.variables; const updatedVariables = [...envVariables, ...newVariables]; await updateEnvFile(updatedVariables); } - async function handleEditVariable(oldKey: string, key: string, value: string) { + async function handleEditVariable( + oldKey: string, + key: string, + value: string, + ) { const updatedVariables = envVariables.map((v) => v.key === oldKey ? { key, value } : v, ); @@ -108,10 +114,7 @@ >
- {#each [ - { view: "code", icon: Code2Icon, label: "Code view" }, - { view: "viz", icon: Settings, label: "No-code view" }, - ] as { view, icon: Icon, label } (view)} + {#each [{ view: "code", icon: Code2Icon, label: "Code view" }, { view: "viz", icon: Settings, label: "No-code view" }] as { view, icon: Icon, label } (view)}
- {#if viewMode === "viz"} +
+ {#if rcUrl} + + {/if} - {/if} + type="secondary" + small + onClick={() => (pullDialogOpen = true)} + class="flex items-center gap-2" + > + + + + {#if viewMode === "viz"} + + + {/if} +
@@ -174,6 +235,10 @@ on:add={handleAddVariables} /> + + + + diff --git a/web-common/src/features/environment-variables/PullEnvDialog.svelte b/web-common/src/features/environment-variables/PullEnvDialog.svelte index 4d20726ebc6..30e391cce00 100644 --- a/web-common/src/features/environment-variables/PullEnvDialog.svelte +++ b/web-common/src/features/environment-variables/PullEnvDialog.svelte @@ -69,8 +69,9 @@ Pull Environment Variables - Replace your local .env files with cloud variables for {environment || - "all"} environment{environment === "" ? "s" : ""}. + Merge cloud variables into your local .env files for {environment || + "all"} environment{environment === "" ? "s" : ""}. Shared keys will be + overwritten; local-only variables are preserved. diff --git a/web-common/src/features/environment-variables/PushEnvDialog.svelte b/web-common/src/features/environment-variables/PushEnvDialog.svelte index c0d708fc284..bb28cd53fcd 100644 --- a/web-common/src/features/environment-variables/PushEnvDialog.svelte +++ b/web-common/src/features/environment-variables/PushEnvDialog.svelte @@ -69,8 +69,8 @@ Merge your local .env files with cloud for {environment || "all"} environment{environment === "" ? "s" - : ""}. Existing cloud variables will be updated with your local - values. + : ""}. Shared keys will be updated with your local values; + cloud-only variables are preserved. From 3c2247c13ce146ffac275412b72a32bf13e2839a Mon Sep 17 00:00:00 2001 From: royendo <67675319+royendo@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:44:08 -0500 Subject: [PATCH 28/51] 8-15 --- .../environment-variables/AddEnvDialog.svelte | 54 +++++++++---------- .../EditEnvDialog.svelte | 2 +- .../EnvVariablesTable.svelte | 2 +- .../environment-variables/EnvWorkspace.svelte | 23 +++++--- web-common/src/features/organization/utils.ts | 21 ++++---- 5 files changed, 56 insertions(+), 46 deletions(-) diff --git a/web-common/src/features/environment-variables/AddEnvDialog.svelte b/web-common/src/features/environment-variables/AddEnvDialog.svelte index 90464839e91..7a5270fc0a5 100644 --- a/web-common/src/features/environment-variables/AddEnvDialog.svelte +++ b/web-common/src/features/environment-variables/AddEnvDialog.svelte @@ -15,9 +15,14 @@ import { yup } from "sveltekit-superforms/adapters"; import { array, object, string } from "yup"; import { createEventDispatcher } from "svelte"; + import { parse as parseDotenv } from "dotenv"; import { isDuplicateKey } from "./utils"; import type { EnvVariable } from "./types"; + const isMac = + typeof window !== "undefined" && + window.navigator.userAgent.includes("Macintosh"); + export let open = false; export let existingVariables: EnvVariable[] = []; @@ -28,6 +33,8 @@ let inputErrors: { [key: number]: { type: string } } = {}; let isKeyAlreadyExists = false; let fileInput: HTMLInputElement; + let nextId = 1; + let rowIds: number[] = [nextId++]; $: hasExistingKeys = Object.keys(inputErrors).length > 0; $: hasNewChanges = $form.variables.some( @@ -85,6 +92,7 @@ function handleAdd() { $form.variables = [...$form.variables, { key: "", value: "" }]; + rowIds = [...rowIds, nextId++]; } function handleKeyChange(index: number, event: Event) { @@ -101,6 +109,7 @@ function handleRemove(index: number) { $form.variables = $form.variables.filter((_, i) => i !== index); + rowIds = rowIds.filter((_, i) => i !== index); checkForExistingKeys(); } @@ -108,6 +117,8 @@ $form = initialValues; inputErrors = {}; isKeyAlreadyExists = false; + nextId = 1; + rowIds = [nextId++]; } function checkForExistingKeys() { @@ -171,32 +182,22 @@ } function parseFile(contents: string) { - const lines = contents.split("\n"); - const variables: EnvVariable[] = []; - - for (const line of lines) { - const trimmed = line.trim(); - if (trimmed && !trimmed.startsWith("#")) { - const [key, ...valueParts] = trimmed.split("="); - if (key) { - variables.push({ - key: key.trim(), - value: valueParts - .join("=") - .trim() - .replace(/^["']|["']$/g, ""), - }); - } - } - } + const parsed = parseDotenv(contents); + const variables: EnvVariable[] = Object.entries(parsed).map( + ([key, value]) => ({ key, value: value ?? "" }), + ); if (variables.length > 0) { - const filteredVariables = $form.variables.filter( - (variable) => - variable.key.trim() !== "" || variable.value.trim() !== "", - ); + const keepIndices = $form.variables + .map((v, i) => (v.key.trim() !== "" || v.value.trim() !== "") ? i : -1) + .filter((i) => i !== -1); + + const filteredVariables = keepIndices.map((i) => $form.variables[i]); + const filteredIds = keepIndices.map((i) => rowIds[i]); + const newIds = variables.map(() => nextId++); $form.variables = [...filteredVariables, ...variables]; + rowIds = [...filteredIds, ...newIds]; } } @@ -226,7 +227,7 @@ - + Add environment variables @@ -239,7 +240,7 @@

- Press ⌘⇧. to show .env in file picker + Press {isMac ? "⌘⇧." : "Ctrl+Shift+."} to show .env in file picker