diff --git a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-fns.ts b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-fns.ts
index ff778ab2c..bbb0f354e 100644
--- a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-fns.ts
+++ b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-fns.ts
@@ -29,27 +29,65 @@ const parseEnv = (str: string) => {
const lines = str.split("\n");
const parsed: { key: string; value: string }[] = [];
- lines.forEach((line) => {
- const trimmed = line.trim();
+ let i = 0;
+ while (i < lines.length) {
+ const trimmed = lines[i].trim();
- const isInvalidLine = trimmed === "" || trimmed.startsWith("#");
+ // Skip empty lines and comments
+ if (trimmed === "" || trimmed.startsWith("#")) {
+ i += 1;
+ // eslint-disable-next-line no-continue
+ continue;
+ }
- if (!isInvalidLine && trimmed.includes("=")) {
+ if (trimmed.includes("=")) {
const equalIndex = trimmed.indexOf("=");
const key = trimmed.substring(0, equalIndex).trim();
const valueRaw = trimmed.substring(equalIndex + 1).trim();
- let value = valueRaw;
- if ((value.startsWith(`"`) && value.endsWith(`"`)) || (value.startsWith(`'`) && value.endsWith(`'`))) {
- value = value.slice(1, -1);
+ // Check if value starts with a quote
+ const startsWithDoubleQuote = valueRaw.startsWith('"');
+ const startsWithSingleQuote = valueRaw.startsWith("'");
+
+ if (startsWithDoubleQuote || startsWithSingleQuote) {
+ const quoteChar = startsWithDoubleQuote ? '"' : "'";
+
+ const closingQuoteIndex = valueRaw.indexOf(quoteChar, 1);
+
+ if (closingQuoteIndex !== -1) {
+ // Single-line quoted value
+ const value = valueRaw.slice(1, closingQuoteIndex);
+ parsed.push({ key, value });
+ i += 1;
+ } else {
+ // Multiline quoted value - collect lines until closing quote
+ let value = valueRaw.slice(1);
+ i += 1;
+
+ while (i < lines.length) {
+ const nextLine = lines[i];
+ const closingIndex = nextLine.indexOf(quoteChar);
+
+ if (closingIndex !== -1) {
+ value += `\n${nextLine.substring(0, closingIndex)}`;
+ parsed.push({ key, value });
+ i += 1;
+ break;
+ } else {
+ value += `\n${nextLine}`;
+ i += 1;
+ }
+ }
+ }
+ } else {
+ // Unquoted value
+ parsed.push({ key, value: valueRaw });
+ i += 1;
}
-
- parsed.push({
- key,
- value
- });
+ } else {
+ i += 1;
}
- });
+ }
return parsed;
};
@@ -70,12 +108,22 @@ const getLaravelForgeSecrets = async (secretSync: TLaravelForgeSyncWithCredentia
};
const buildEnvString = (secrets: LaravelForgeSecret[]) => {
+ if (secrets.length === 0) {
+ return "# .env";
+ }
+
return secrets
.map((secret) => {
- if (secret.value.includes(" ")) {
- return `${secret.key}="${secret.value}"`;
+ const { value } = secret;
+
+ if (value.includes(`"`)) {
+ return `${secret.key}='${value}'`;
}
- return `${secret.key}=${secret.value}`;
+
+ if (value.includes(" ") || value.includes("\n") || value.includes(`'`)) {
+ return `${secret.key}="${value}"`;
+ }
+ return `${secret.key}=${value}`;
})
.join("\n");
};
diff --git a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts
index 66632d2dd..069638da0 100644
--- a/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts
+++ b/backend/src/services/secret-sync/laravel-forge/laravel-forge-sync-schemas.ts
@@ -12,14 +12,11 @@ import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"
const LaravelForgeSyncDestinationConfigSchema = z.object({
orgSlug: z.string().min(1, "Org Slug is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.orgSlug),
- orgName: z.string().min(1, "Org Name is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.orgName),
+ orgName: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.orgName),
serverId: z.string().min(1, "Server ID is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.serverId),
- serverName: z
- .string()
- .min(1, "Server Name is required")
- .describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.serverName),
+ serverName: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.serverName),
siteId: z.string().min(1, "Site ID is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.siteId),
- siteName: z.string().min(1, "Site Name is required").describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.siteName)
+ siteName: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.LARAVEL_FORGE.siteName)
});
const LaravelForgeSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
diff --git a/docs/integrations/secret-syncs/laravel-forge.mdx b/docs/integrations/secret-syncs/laravel-forge.mdx
index 2e3fd9c98..6ed77d67f 100644
--- a/docs/integrations/secret-syncs/laravel-forge.mdx
+++ b/docs/integrations/secret-syncs/laravel-forge.mdx
@@ -149,11 +149,8 @@ description: "Learn how to configure a Laravel Forge Sync for Infisical."
"destination": "laravel-forge",
"destinationConfig": {
"orgSlug": "org-abc123",
- "orgName": "org-name",
"serverId": "server-abc123",
- "serverName": "server-name",
"siteId": "site-abc123",
- "siteName": "site-name"
}
}
}
diff --git a/frontend/src/components/secret-syncs/SecretSyncModalHeader.tsx b/frontend/src/components/secret-syncs/SecretSyncModalHeader.tsx
index a9882ecec..ebbe34d75 100644
--- a/frontend/src/components/secret-syncs/SecretSyncModalHeader.tsx
+++ b/frontend/src/components/secret-syncs/SecretSyncModalHeader.tsx
@@ -17,7 +17,7 @@ export const SecretSyncModalHeader = ({ destination, isConfigured }: Props) => {