From 2727ec67e32d40dc03e0ec9b3e408c46eac0b2c8 Mon Sep 17 00:00:00 2001 From: Pier-Paolo Mammi Date: Mon, 29 Jun 2026 10:18:40 +0200 Subject: [PATCH] ai refactor - add missing headers and body with relative content-type - fix double quotes for empty variables (not needed by VSCode REST client) - strip comments from JSON data --- scripts/generate-http-docs.js | 154 ++++++++++++++++++++-------------- scripts/package-lock.json | 15 +++- scripts/package.json | 4 +- 3 files changed, 108 insertions(+), 65 deletions(-) diff --git a/scripts/generate-http-docs.js b/scripts/generate-http-docs.js index 5fbf1f7..ff6f841 100644 --- a/scripts/generate-http-docs.js +++ b/scripts/generate-http-docs.js @@ -1,9 +1,14 @@ #!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); -const YAML = require('yaml'); +import fs from 'fs'; +import path from 'path'; +import YAML from 'yaml'; +import stripJsonComments from 'strip-json-comments'; +import { fileURLToPath } from 'url'; const interpolationVariableRegex = /^{{(.*?)}}$/ +const DEFAULT_ENV_VAR_VALUE = 'EDIT_VALUE_HERE' +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); function findWorkspaceRoot(startDir) { let current = startDir; @@ -142,70 +147,37 @@ function parseRequestInfo(text) { } function parseHttpBlock(text) { - const lines = text.split(/\r?\n/); - const blockLines = findBlock(lines, 'http'); - const blockText = blockLines.join('\n'); - const methodMatch = blockText.match(/^\s*method:\s*(.+)$/m); - const urlMatch = blockText.match(/^\s*url:\s*(.+)$/m); + const parsed = YAML.parse(text) || {}; + const http = parsed.http || {}; + const headers = []; - const params = []; - const paramLines = blockLines.join('\n').split(/\r?\n/); - let inParamsBlock = false; - let paramsIndent = 0; - let currentParam = null; - - for (const line of paramLines) { - const trimmed = line.trim(); - if (!inParamsBlock) { - if (trimmed === 'params:') { - inParamsBlock = true; - paramsIndent = line.match(/^\s*/)[0].length; - } + for (const header of Array.isArray(http.headers) ? http.headers : []) { + if (!header || typeof header !== 'object') { continue; } - - if (!trimmed) { - continue; - } - - const indent = line.match(/^\s*/)[0].length; - if (indent <= paramsIndent) { - break; - } - - const listItemMatch = line.match(/^\s*-\s+name:\s*(.+)$/); - if (listItemMatch) { - currentParam = { name: stripQuotes(listItemMatch[1]) }; - params.push(currentParam); - continue; - } - - if (!currentParam) { - continue; - } - - const propertyMatch = line.match(/^\s*([A-Za-z0-9_]+):\s*(.+)$/); - if (propertyMatch) { - const [, key, value] = propertyMatch; - currentParam[key] = stripQuotes(value); - } + headers.push({ + name: String(header.name || '').trim(), + value: String(header.value ?? '').trim(), + }); } return { - method: methodMatch ? stripQuotes(methodMatch[1]) : 'GET', - url: urlMatch ? stripQuotes(urlMatch[1]) : '', - params, + method: http.method || 'GET', + url: http.url || '', + params: Array.isArray(http.params) ? http.params : [], + headers, + body: http.body && typeof http.body === 'object' ? http.body : null, }; } function formatVariableValue(value) { if (value === null || value === undefined) { - return '""'; + return ''; } if (typeof value === 'string') { if (value.trim() === '') { - return '""'; + return ''; } if (/\s/.test(value)) { return `"${value.replace(/"/g, '\\"')}"`; @@ -343,6 +315,21 @@ function buildRequestContent(request, requestName, requestConfig = {}, dotenvVar } }; + const renderJsonValue = (value) => { + if (typeof value === 'string') { + return renderValue(value); + } + if (Array.isArray(value)) { + return value.map((item) => renderJsonValue(item)); + } + if (value && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value).map(([key, nestedValue]) => [key, renderJsonValue(nestedValue)]), + ); + } + return value; + }; + const addParameterVariables = (name, value) => { addParameterVariable(name, value); }; @@ -382,13 +369,26 @@ function buildRequestContent(request, requestName, requestConfig = {}, dotenvVar const queryParams = []; const headers = []; + const addHeader = (name, value) => { + if (!name) { + return; + } + addReferencedVariables(value ?? ''); + headers.push({ name: String(name).trim(), value: renderValue(value ?? '') }); + }; + + for (const header of Array.isArray(request.headers) ? request.headers : []) { + if (!header || !header.name) { + continue; + } + addHeader(header.name, header.value ?? ''); + } for (const header of Array.isArray(requestConfig.headers) ? requestConfig.headers : []) { if (!header || !header.name) { continue; } - addReferencedVariables(header.value ?? ''); - headers.push({ name: header.name, value: renderValue(header.value ?? '') }); + addHeader(header.name, header.value ?? ''); } for (const param of request.params || []) { @@ -416,20 +416,14 @@ function buildRequestContent(request, requestName, requestConfig = {}, dotenvVar { if (requestConfig.auth.type === 'bearer') { addReferencedVariables(requestConfig.auth.token ?? ''); - headers.push({ - name: 'Authorization', - value: `Bearer ${renderValue(requestConfig.auth.token ?? '')}`, - }); + addHeader('Authorization', `Bearer ${renderValue(requestConfig.auth.token ?? '')}`); } else if (requestConfig.auth.type === 'basic') { const username = requestConfig.auth.username ?? ''; const password = requestConfig.auth.password ?? ''; addReferencedVariables(username); addReferencedVariables(password); // VSCode REST Client can manage username:password format directly! - headers.push({ - name: 'Authorization', - value: `Basic ${renderValue(username)}:${renderValue(password)}`, - }); + addHeader('Authorization', `Basic ${renderValue(username)}:${renderValue(password)}`); } else { headers.push({ name: `UNKNOWN_${requestConfig.auth.type}`, @@ -454,6 +448,36 @@ function buildRequestContent(request, requestName, requestConfig = {}, dotenvVar lines.push(''); } + let requestBody = ''; + if (request.body && typeof request.body === 'object') { + const bodyType = String(request.body.type || '').toLowerCase(); + if (bodyType === 'json') { + const jsonData = stripJsonComments(request.body.data); + if (jsonData !== undefined && jsonData !== null) { + if (typeof jsonData === 'string') { + requestBody = renderValue(jsonData); + } else { + const renderedData = Array.isArray(jsonData) + ? jsonData.map((item) => renderJsonValue(item)) + : renderJsonValue(jsonData); + requestBody = JSON.stringify(renderedData, null, 2); + } + } + addHeader('Content-Type', 'application/json'); + } else if (bodyType === 'form-urlencoded') { + const parts = []; + for (const entry of Array.isArray(request.body.data) ? request.body.data : []) { + if (!entry || !entry.name) { + continue; + } + addReferencedVariables(entry.value ?? ''); + parts.push(`${entry.name}=${renderValue(entry.value ?? '')}`); + } + requestBody = parts.join('&'); + addHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + } + if (variableDefinitions.length > 0) { lines.push(`# Variables for ${requestName}`); for (const variable of variableDefinitions) { @@ -476,6 +500,10 @@ function buildRequestContent(request, requestName, requestConfig = {}, dotenvVar for (const header of headers) { lines.push(`${header.name}: ${header.value}`); } + if (requestBody) { + lines.push(''); + lines.push(requestBody); + } return lines.join('\n'); } @@ -576,7 +604,7 @@ function writeEnvironmentTemplates(sourceDir, outputRoot) { } } - const templateContent = variableNames.length > 0 ? `${variableNames.map(v => `${v}=EDIT_VALUE_HERE`).join('\n')}\n` : ''; + const templateContent = variableNames.length > 0 ? `${variableNames.map(v => `${v}=${DEFAULT_ENV_VAR_VALUE}`).join('\n')}\n` : ''; fs.writeFileSync(path.join(targetDir, '.env.template'), templateContent, 'utf8'); dotenvVariablesByTarget.set(targetDir, new Set(variableNames)); } diff --git a/scripts/package-lock.json b/scripts/package-lock.json index 6f47507..fec65a9 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -1,13 +1,26 @@ { - "name": "scripts", + "name": "generate-http-docs", "lockfileVersion": 3, "requires": true, "packages": { "": { "dependencies": { + "strip-json-comments": "^5.0.3", "yaml": "^2.9.0" } }, + "node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", diff --git a/scripts/package.json b/scripts/package.json index dab2d48..f9114af 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -1,5 +1,7 @@ { + "type": "module", "dependencies": { + "strip-json-comments": "^5.0.3", "yaml": "^2.9.0" } -} +} \ No newline at end of file