diff --git a/scripts/generate-http-docs.js b/scripts/generate-http-docs.js index 715f751..70271ec 100644 --- a/scripts/generate-http-docs.js +++ b/scripts/generate-http-docs.js @@ -294,7 +294,7 @@ function getRequestConfigForFile(yamlFile, sourceDir) { return resolved.reduce((result, config) => mergeRequestConfig(result, config), {}); } -function buildRequestContent(request, requestName, requestConfig = {}) { +function buildRequestContent(request, requestName, requestConfig = {}, dotenvVariables = new Set()) { const lines = []; const variableDefinitions = []; const seenVariables = new Set(); @@ -304,13 +304,32 @@ function buildRequestContent(request, requestName, requestConfig = {}) { return; } const normalized = String(name).trim(); - if (!normalized || seenVariables.has(normalized)) { + if (!normalized || seenVariables.has(normalized) || dotenvVariables.has(normalized)) { return; } seenVariables.add(normalized); variableDefinitions.push({ name: normalized, value }); }; + const addReferencedVariables = (value, fallbackValue = 'YOUR_VALUE_HERE') => { + for (const placeholder of collectPlaceholders(String(value))) { + addVariable(placeholder, fallbackValue); + } + }; + + const renderValue = (value) => { + if (typeof value !== 'string') { + return value; + } + return value.replace(/\{\{([^{}]+)\}\}/g, (match, name) => { + const normalized = String(name).trim(); + if (normalized && dotenvVariables.has(normalized)) { + return `{{$dotenv ${normalized}}}`; + } + return match; + }); + }; + const configVariables = Array.isArray(requestConfig.variables) ? requestConfig.variables : []; for (const variable of configVariables) { if (variable && variable.name) { @@ -320,10 +339,9 @@ function buildRequestContent(request, requestName, requestConfig = {}) { let url = request.url || ''; if (url) { - for (const placeholder of collectPlaceholders(url)) { - addVariable(placeholder, 'YOUR_VALUE_HERE'); - } + addReferencedVariables(url); url = url.replace(/:([A-Za-z0-9_]+)/g, (_, name) => `{{${name}}}`); + url = renderValue(url); // Since Bruno puts enabled params in the URL, this avoids duplicate query params url = url.split('?')[0]; } @@ -335,10 +353,8 @@ function buildRequestContent(request, requestName, requestConfig = {}) { if (!header || !header.name) { continue; } - for (const placeholder of collectPlaceholders(String(header.value ?? ''))) { - addVariable(placeholder, 'YOUR_VALUE_HERE'); - } - headers.push({ name: header.name, value: header.value ?? '' }); + addReferencedVariables(header.value ?? ''); + headers.push({ name: header.name, value: renderValue(header.value ?? '') }); } for (const param of request.params || []) { @@ -351,40 +367,32 @@ function buildRequestContent(request, requestName, requestConfig = {}) { continue; } - for (const placeholder of collectPlaceholders(String(value))) { - addVariable(placeholder, 'YOUR_VALUE_HERE'); - } + addReferencedVariables(value); if (type === 'header') { - headers.push({ name, value }); + headers.push({ name, value: renderValue(value) }); } else { - queryParams.push({ name, value }); + queryParams.push({ name, value: renderValue(value) }); } } if (requestConfig.auth) { if (requestConfig.auth.type === 'bearer') { - for (const placeholder of collectPlaceholders(String(requestConfig.auth.token ?? ''))) { - addVariable(placeholder, 'YOUR_VALUE_HERE'); - } + addReferencedVariables(requestConfig.auth.token ?? ''); headers.push({ name: 'Authorization', - value: `Bearer ${requestConfig.auth.token ?? ''}`, + value: `Bearer ${renderValue(requestConfig.auth.token ?? '')}`, }); } else if (requestConfig.auth.type === 'basic') { const username = requestConfig.auth.username ?? ''; const password = requestConfig.auth.password ?? ''; - for (const placeholder of collectPlaceholders(String(username))) { - addVariable(placeholder, 'YOUR_VALUE_HERE'); - } - for (const placeholder of collectPlaceholders(String(password))) { - addVariable(placeholder, 'YOUR_VALUE_HERE'); - } + addReferencedVariables(username); + addReferencedVariables(password); // VSCode REST Client can manage username:password format directly! headers.push({ name: 'Authorization', - value: `Basic ${username}:${password}`, + value: `Basic ${renderValue(username)}:${renderValue(password)}`, }); } else { headers.push({ @@ -440,8 +448,30 @@ function walkYamlFiles(rootDir) { return results; } +function getDotenvVariablesForTargetDir(targetDir, outputRoot, dotenvVariablesByTarget) { + const variables = new Set(); + let currentDir = targetDir; + + while (true) { + const vars = dotenvVariablesByTarget.get(currentDir); + if (vars) { + for (const variable of vars) { + variables.add(variable); + } + } + + if (currentDir === outputRoot || path.dirname(currentDir) === currentDir) { + break; + } + currentDir = path.dirname(currentDir); + } + + return variables; +} + function writeEnvironmentTemplates(sourceDir, outputRoot) { const targets = []; + const dotenvVariablesByTarget = new Map(); const visit = (currentDir) => { const entries = fs.readdirSync(currentDir, { withFileTypes: true }); @@ -496,7 +526,10 @@ function writeEnvironmentTemplates(sourceDir, outputRoot) { const templateContent = variableNames.length > 0 ? `${variableNames.map(v => `${v}={{${v}}}`).join('\n')}\n` : ''; fs.writeFileSync(path.join(targetDir, '.env.template'), templateContent, 'utf8'); + dotenvVariablesByTarget.set(targetDir, new Set(variableNames)); } + + return dotenvVariablesByTarget; } function main() { @@ -522,7 +555,7 @@ function main() { const outputRoot = path.join(workspaceRoot, 'autodocs', 'http', collection.name); ensureDir(outputRoot); - writeEnvironmentTemplates(sourceDir, outputRoot); + const dotenvVariablesByTarget = writeEnvironmentTemplates(sourceDir, outputRoot); const yamlFiles = walkYamlFiles(sourceDir); let processed = 0; @@ -542,7 +575,8 @@ function main() { const requestConfig = getRequestConfigForFile(yamlFile, sourceDir); const outputFile = path.join(targetDir, `${parsedPath.name}.http`); - const requestContent = buildRequestContent(httpBlock, requestName, requestConfig); + const dotenvVariables = getDotenvVariablesForTargetDir(targetDir, outputRoot, dotenvVariablesByTarget); + const requestContent = buildRequestContent(httpBlock, requestName, requestConfig, dotenvVariables); fs.writeFileSync(outputFile, `${requestContent}\n`, 'utf8'); processed += 1; }