Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions frontend/src/components/utilities/parseSecrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,91 @@ export const parseJson = (src: ArrayBuffer | string) => {
});
return env;
};

/**
* Parses simple flat YAML with support for multiline strings using |, |-, and >.
* @param {ArrayBuffer | string} src
* @returns {Record<string, { value: string, comments: string[] }>}
*/
export function parseYaml(src: ArrayBuffer | string) {
const result: Record<string, { value: string; comments: string[] }> = {};

const content = src.toString().replace(/\r\n?/g, "\n");
const lines = content.split("\n");

let i = 0;
let comments: string[] = [];

while (i < lines.length) {
const line = lines[i].trim();

// Collect comment
if (line.startsWith("#")) {
comments.push(line.slice(1).trim());
i += 1; // move to next line
} else {
// Match key: value or key: |, key: >, etc.
const keyMatch = lines[i].match(/^([\w.-]+)\s*:\s*(.*)$/);
if (keyMatch) {
const [, key, rawValue] = keyMatch;
let value = rawValue.trim();

// Multiline string handling
if (value === "|-" || value === "|" || value === ">") {
const isFolded = value === ">";

const baseIndent = lines[i + 1]?.match(/^(\s*)/)?.[1]?.length ?? 0;
const collectedLines: string[] = [];

i += 1; // move to first content line

while (i < lines.length) {
const current = lines[i];
const currentIndent = current.match(/^(\s*)/)?.[1]?.length ?? 0;

if (current.trim() === "" || currentIndent >= baseIndent) {
collectedLines.push(current.slice(baseIndent));
i += 1; // move to next line
} else {
break;
}
}

if (isFolded) {
// Join lines with space for `>` folded style
value = collectedLines.map((l) => l.trim()).join(" ");
} else {
// Keep lines with newlines for `|` and `|-`
value = collectedLines.join("\n");
}
} else {
// Inline value — strip quotes and inline comment
const commentIndex = value.indexOf(" #");
if (commentIndex !== -1) {
value = value.slice(0, commentIndex).trim();
}

// Remove surrounding quotes
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}

i += 1; // advance to next line
}

result[key] = {
value,
comments: [...comments]
};
comments = []; // reset
} else {
i += 1; // skip unknown line
}
}
}

return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality
import { parseDotEnv, parseJson } from "@app/components/utilities/parseSecrets";
import { parseDotEnv, parseJson, parseYaml } from "@app/components/utilities/parseSecrets";
import { Button, Lottie, Modal, ModalContent } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks";
Expand Down Expand Up @@ -127,7 +127,7 @@ export const SecretDropzone = ({
}
};

const parseFile = (file?: File, isJson?: boolean) => {
const parseFile = (file?: File) => {
const reader = new FileReader();
if (!file) {
createNotification({
Expand All @@ -140,10 +140,25 @@ export const SecretDropzone = ({
setIsLoading.on();
reader.onload = (event) => {
if (!event?.target?.result) return;
// parse function's argument looks like to be ArrayBuffer
const env = isJson
? parseJson(event.target.result as ArrayBuffer)
: parseDotEnv(event.target.result as ArrayBuffer);

let env: TParsedEnv;

const src = event.target.result as ArrayBuffer;

switch (file.type) {
case "application/json":
env = parseJson(src);
break;
case "text/yaml":
case "application/x-yaml":
case "application/yaml":
env = parseYaml(src);
break;

default:
env = parseDotEnv(src);
break;
}
setIsLoading.off();
handleParsedEnv(env);
};
Expand All @@ -165,12 +180,12 @@ export const SecretDropzone = ({

e.dataTransfer.dropEffect = "copy";
setDragActive.off();
parseFile(e.dataTransfer.files[0], e.dataTransfer.files[0].type === "application/json");
parseFile(e.dataTransfer.files[0]);
};

const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
parseFile(e.target?.files?.[0], e.target?.files?.[0]?.type === "application/json");
parseFile(e.target?.files?.[0]);
};

const handleSaveSecrets = async () => {
Expand Down