export const ENV_VAR_WRITE_APIS = ['setEnvVar', 'deleteEnvVar', 'setGlobalEnvVar']; export const GITHUB_DISCUSSION_URL = 'https://github.com/usebruno/bruno/discussions/8257'; const COMMENT_OR_STRING = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`|\/\/[^\n]*|\/\*[\s\S]*?\*\//g; export const ENV_VAR_WRITE_OPEN_PATTERN = new RegExp(`\\bbru\\s*\\.\\s*(?:${ENV_VAR_WRITE_APIS.join('|')})\\s*\\(`); const PERSIST_FALSE = /persist\s*:\s*false/; // Guard for the parenthesis walk below. A well-formed call stops at its closing ')' long before this // — `bru.setEnvVar(key, value, { persist: false })` is at most a couple hundred characters. The cap // only bites on malformed/unbalanced input (no closing paren), bounding the walk so the whole scan // stays linear instead of O(n^2); on hitting it we fail safe and treat the call as a write. const MAX_ARG_SCAN = 1024; // A write persists to disk unless its own options carry `{ persist: false }`. We locate each // `bru.(` opening, then bound its arguments by walking parentheses manually — a regex can only // balance nesting to a fixed depth, which let deeply-nested real writes slip past undetected. Only // the call's top-level argument text is checked for persist:false, so a decoy buried inside a nested // sub-call no longer suppresses the warning. export const scriptWritesEnvVar = (code?: string | null) => { if (!code || typeof code !== 'string') return false; const stripped = code.replace(COMMENT_OR_STRING, ''); const openPattern = new RegExp(ENV_VAR_WRITE_OPEN_PATTERN.source, 'g'); let match; while ((match = openPattern.exec(stripped)) !== null) { let depth = 1; let topLevelArgs = ''; const start = match.index + match[0].length; const end = Math.min(stripped.length, start + MAX_ARG_SCAN); for (let i = start; i < end && depth > 0; i++) { const ch = stripped[i]; if (ch === '(') depth++; else if (ch === ')') depth--; else if (depth === 1) topLevelArgs += ch; } if (!PERSIST_FALSE.test(topLevelArgs)) return true; } return false; };