Skip to content
Open
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
204 changes: 163 additions & 41 deletions src/renderer/actions/local-sync/fs-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { v4 as uuidv4 } from "uuid";
import * as Sentry from "@sentry/browser";
import {
API,
APIEntity,
Expand Down Expand Up @@ -275,17 +276,35 @@ export async function writeContent(
};
}
}

const parsedContentResult = parseRaw(content, fileType.validator);
if (parsedContentResult.type === "error") {
// Send analytics event for validation failure (avoid sending raw content)
const contentMeta =
typeof content === "string"
? { contentLength: content.length }
: { keys: Object.keys(content).length };

Sentry.captureEvent({
message: `parseRaw() - Content validation error: ${parsedContentResult.error.message}`,
level: "error",
tags: {
fileType: fileType.type,
errorType: "validation_failed",
},
extra: {
path: resource.path,
error: parsedContentResult.error.message,
contentMeta,
},
});
return createFileSystemError(
{ message: parsedContentResult.error.message },
resource.path,
fileType.type
);
}

console.log("writing at", resource.path);
const serializedContent = serializeContentForWriting(content);
if (writeWithElevatedAccess) {
await FsService.writeFileWithElevatedAccess(
Expand All @@ -307,6 +326,20 @@ export async function writeContent(
},
};
} catch (e: any) {
// Send analytics event for write exception (without raw content)
Sentry.captureEvent({
message: `writeContent() - File write error: ${e.message}`,
level: "error",
tags: {
fileType: fileType.type,
errorType: "write_exception",
},
extra: {
path: resource.path,
error: e.message,
stack: e.stack,
},
});
return createFileSystemError(e, resource.path, fileType.type);
}
}
Expand Down Expand Up @@ -1060,55 +1093,144 @@ export function sanitizeFsResourceList(
export async function parseFileToEnv(
file: FileResource
): Promise<FileSystemResult<Environment>> {
const parsedFileResult = await parseFile({
resource: file,
fileType: new EnvironmentRecordFileType(),
});

if (parsedFileResult.type === "error") {
return parsedFileResult;
}
try {
const parsedFileResult = await parseFile({
resource: file,
fileType: new EnvironmentRecordFileType(),
});

const { content } = parsedFileResult;
if (parsedFileResult.type === "error") {
Sentry.captureEvent({
message: `parseFileToEnv() - Environment load failed: ${parsedFileResult.error.message}`,
level: "error",
tags: {
fileType: "environment",
errorType: "environment_load_failed",
},
extra: {
path: file.path,
error: parsedFileResult.error.message,
errorCode: parsedFileResult.error.code,
},
});

const isGlobal = file.path.endsWith(`/${GLOBAL_ENV_FILE}`);
const environment: Environment = {
type: "environment",
id: getIdFromPath(file.path),
name: content.name,
variables: content.variables,
isGlobal,
};
return parsedFileResult;
Comment on lines 1102 to 1117
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Misleading Sentry message - "not found" vs actual error type.

The message "Environment not found" is inaccurate. The error could be a validation failure, parse error, or permission issue - not necessarily a missing file. The actual error code is available via parsedContentResult.error.code but the message doesn't reflect it.

✏️ Suggested fix
      Sentry.captureEvent({
-       message: `Environment not found: ${parsedFileResult.error.message}`,
+       message: `Environment load failed: ${parsedFileResult.error.message}`,
        level: "error",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (parsedFileResult.type === "error") {
Sentry.captureEvent({
message: `Environment not found: ${parsedFileResult.error.message}`,
level: "error",
tags: {
fileType: "environment",
errorType: "environment_load_failed",
},
extra: {
path: file.path,
error: parsedFileResult.error.message,
errorCode: parsedFileResult.error.code,
},
});
const isGlobal = file.path.endsWith(`/${GLOBAL_ENV_FILE}`);
const environment: Environment = {
type: "environment",
id: getIdFromPath(file.path),
name: content.name,
variables: content.variables,
isGlobal,
};
return parsedFileResult;
if (parsedFileResult.type === "error") {
Sentry.captureEvent({
message: `Environment load failed: ${parsedFileResult.error.message}`,
level: "error",
tags: {
fileType: "environment",
errorType: "environment_load_failed",
},
extra: {
path: file.path,
error: parsedFileResult.error.message,
errorCode: parsedFileResult.error.code,
},
});
return parsedFileResult;
🤖 Prompt for AI Agents
In @src/renderer/actions/local-sync/fs-utils.ts around lines 1097 - 1112, The
Sentry event message "Environment not found" is misleading; update the
Sentry.captureEvent call (around parsedFileResult handling) to use a neutral,
accurate message that includes the actual error message and code from
parsedFileResult.error (e.g., "Environment load failed") and add the error code
into the top-level message or a dedicated extra field so the event reflects the
real failure type rather than assuming "not found".

}

const result: FileSystemResult<Environment> = {
type: "success",
content: environment,
};
const { content } = parsedFileResult;
const isGlobal = file.path.endsWith(`/${GLOBAL_ENV_FILE}`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Cross-platform path separator issue persists.

This issue was flagged in a previous review but remains unaddressed. Using a hardcoded forward slash / will fail on Windows where the path separator is \.

🐛 Recommended fix
-    const isGlobal = file.path.endsWith(`/${GLOBAL_ENV_FILE}`);
+    const isGlobal = path.basename(file.path) === GLOBAL_ENV_FILE;

Note: The path module is already imported at line 61.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isGlobal = file.path.endsWith(`/${GLOBAL_ENV_FILE}`);
const isGlobal = path.basename(file.path) === GLOBAL_ENV_FILE;
🤖 Prompt for AI Agents
In @src/renderer/actions/local-sync/fs-utils.ts at line 1121, The code uses a
hardcoded forward slash in the isGlobal check (const isGlobal =
file.path.endsWith(`/${GLOBAL_ENV_FILE}`)), which breaks on Windows; replace
this with a cross-platform check such as comparing the basename (e.g.,
path.basename(file.path) === GLOBAL_ENV_FILE) or using path.sep (e.g.,
file.path.endsWith(path.sep + GLOBAL_ENV_FILE)) so the GLOBAL_ENV_FILE detection
works on all OSes; update the isGlobal assignment to use path.basename or
path.sep accordingly (path is already imported).

const environment: Environment = {
type: "environment",
id: getIdFromPath(file.path),
name: content.name,
variables: content.variables,
isGlobal,
};

return result;
return {
type: "success",
content: environment,
};
} catch (e: any) {
Sentry.captureEvent({
message: `parseFileToEnv() - Environment load exception: ${e.message}`,
level: "error",
tags: {
fileType: "environment",
errorType: "environment_load_exception",
},
extra: {
path: file.path,
error: e.message,
stack: e.stack,
},
});
return createFileSystemError(e, file.path, FileTypeEnum.ENVIRONMENT);
}
}

export function parseToEnvironmentEntity(
variables: Record<string, EnvironmentVariableValue>
) {
const newVariables: Record<
string,
Static<(typeof EnvironmentRecord)["variables"]>
> = {};
// eslint-disable-next-line
for (const key in variables) {
newVariables[key] = {
value: variables[key].syncValue,
type:
variables[key].type === EnvironmentVariableType.Secret
? EnvironmentVariableType.String
: variables[key].type,
id: variables[key].id,
isSecret: variables[key].type === EnvironmentVariableType.Secret,
};
}
try {
const newVariables: Record<
string,
Static<(typeof EnvironmentRecord)["variables"]>
> = {};
const missingFieldsPerVariable: Record<string, string[]> = {};

// eslint-disable-next-line
for (const key in variables) {
const variable = variables[key];
const missingFields: string[] = [];

const resolvedValue =
variable.localValue !== undefined
? variable.localValue
: variable.syncValue !== undefined
? variable.syncValue
: (variable as any).value !== undefined
? (variable as any).value
: undefined;

if (variable.id === undefined || variable.id === null) {
missingFields.push("id");
}
if (resolvedValue === undefined) {
missingFields.push("value");
}
if (!variable.type) {
missingFields.push("type");
}

if (missingFields.length > 0) {
missingFieldsPerVariable[key] = missingFields;
}

newVariables[key] = {
value: resolvedValue,
type:
variable.type === EnvironmentVariableType.Secret
? EnvironmentVariableType.String
: variable.type,
id: variable.id,
isSecret: variable.type === EnvironmentVariableType.Secret,
};
}

return newVariables;
// If any variables have missing fields, send analytics event
if (Object.keys(missingFieldsPerVariable).length > 0) {
Sentry.captureEvent({
message: `parseToEnvironmentEntity() - variables with missing fields: ${Object.keys(missingFieldsPerVariable).length}`,
level: "warning",
tags: {
fileType: "environment",
errorType: "invalid_variable_structure",
},
extra: {
totalVariables: Object.keys(variables).length,
variablesWithIssues: Object.keys(missingFieldsPerVariable).length,
missingFields: missingFieldsPerVariable,
},
});
}

return newVariables;
} catch (e: any) {
Sentry.captureEvent({
message: `parseToEnvironmentEntity() - Environment variable transform exception: ${e.message}`,
level: "error",
tags: {
fileType: "environment",
errorType: "variable_transform_exception",
},
extra: {
error: e.message,
stack: e.stack,
},
});
throw e;
}
}

export function getFileNameFromPath(filePath: string) {
Expand Down