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
This commit is contained in:
2026-06-30 12:24:10 +02:00
parent ba01e76c75
commit 2727ec67e3
3 changed files with 108 additions and 65 deletions
+91 -63
View File
@@ -1,9 +1,14 @@
#!/usr/bin/env node #!/usr/bin/env node
const fs = require('fs'); import fs from 'fs';
const path = require('path'); import path from 'path';
const YAML = require('yaml'); import YAML from 'yaml';
import stripJsonComments from 'strip-json-comments';
import { fileURLToPath } from 'url';
const interpolationVariableRegex = /^{{(.*?)}}$/ const interpolationVariableRegex = /^{{(.*?)}}$/
const DEFAULT_ENV_VAR_VALUE = 'EDIT_VALUE_HERE'
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
function findWorkspaceRoot(startDir) { function findWorkspaceRoot(startDir) {
let current = startDir; let current = startDir;
@@ -142,70 +147,37 @@ function parseRequestInfo(text) {
} }
function parseHttpBlock(text) { function parseHttpBlock(text) {
const lines = text.split(/\r?\n/); const parsed = YAML.parse(text) || {};
const blockLines = findBlock(lines, 'http'); const http = parsed.http || {};
const blockText = blockLines.join('\n'); const headers = [];
const methodMatch = blockText.match(/^\s*method:\s*(.+)$/m);
const urlMatch = blockText.match(/^\s*url:\s*(.+)$/m);
const params = []; for (const header of Array.isArray(http.headers) ? http.headers : []) {
const paramLines = blockLines.join('\n').split(/\r?\n/); if (!header || typeof header !== 'object') {
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;
}
continue; continue;
} }
headers.push({
if (!trimmed) { name: String(header.name || '').trim(),
continue; value: String(header.value ?? '').trim(),
} });
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);
}
} }
return { return {
method: methodMatch ? stripQuotes(methodMatch[1]) : 'GET', method: http.method || 'GET',
url: urlMatch ? stripQuotes(urlMatch[1]) : '', url: http.url || '',
params, params: Array.isArray(http.params) ? http.params : [],
headers,
body: http.body && typeof http.body === 'object' ? http.body : null,
}; };
} }
function formatVariableValue(value) { function formatVariableValue(value) {
if (value === null || value === undefined) { if (value === null || value === undefined) {
return '""'; return '';
} }
if (typeof value === 'string') { if (typeof value === 'string') {
if (value.trim() === '') { if (value.trim() === '') {
return '""'; return '';
} }
if (/\s/.test(value)) { if (/\s/.test(value)) {
return `"${value.replace(/"/g, '\\"')}"`; 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) => { const addParameterVariables = (name, value) => {
addParameterVariable(name, value); addParameterVariable(name, value);
}; };
@@ -382,13 +369,26 @@ function buildRequestContent(request, requestName, requestConfig = {}, dotenvVar
const queryParams = []; const queryParams = [];
const headers = []; 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 : []) { for (const header of Array.isArray(requestConfig.headers) ? requestConfig.headers : []) {
if (!header || !header.name) { if (!header || !header.name) {
continue; continue;
} }
addReferencedVariables(header.value ?? ''); addHeader(header.name, header.value ?? '');
headers.push({ name: header.name, value: renderValue(header.value ?? '') });
} }
for (const param of request.params || []) { for (const param of request.params || []) {
@@ -416,20 +416,14 @@ function buildRequestContent(request, requestName, requestConfig = {}, dotenvVar
{ {
if (requestConfig.auth.type === 'bearer') { if (requestConfig.auth.type === 'bearer') {
addReferencedVariables(requestConfig.auth.token ?? ''); addReferencedVariables(requestConfig.auth.token ?? '');
headers.push({ addHeader('Authorization', `Bearer ${renderValue(requestConfig.auth.token ?? '')}`);
name: 'Authorization',
value: `Bearer ${renderValue(requestConfig.auth.token ?? '')}`,
});
} else if (requestConfig.auth.type === 'basic') { } else if (requestConfig.auth.type === 'basic') {
const username = requestConfig.auth.username ?? ''; const username = requestConfig.auth.username ?? '';
const password = requestConfig.auth.password ?? ''; const password = requestConfig.auth.password ?? '';
addReferencedVariables(username); addReferencedVariables(username);
addReferencedVariables(password); addReferencedVariables(password);
// VSCode REST Client can manage username:password format directly! // VSCode REST Client can manage username:password format directly!
headers.push({ addHeader('Authorization', `Basic ${renderValue(username)}:${renderValue(password)}`);
name: 'Authorization',
value: `Basic ${renderValue(username)}:${renderValue(password)}`,
});
} else { } else {
headers.push({ headers.push({
name: `UNKNOWN_${requestConfig.auth.type}`, name: `UNKNOWN_${requestConfig.auth.type}`,
@@ -454,6 +448,36 @@ function buildRequestContent(request, requestName, requestConfig = {}, dotenvVar
lines.push(''); 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) { if (variableDefinitions.length > 0) {
lines.push(`# Variables for ${requestName}`); lines.push(`# Variables for ${requestName}`);
for (const variable of variableDefinitions) { for (const variable of variableDefinitions) {
@@ -476,6 +500,10 @@ function buildRequestContent(request, requestName, requestConfig = {}, dotenvVar
for (const header of headers) { for (const header of headers) {
lines.push(`${header.name}: ${header.value}`); lines.push(`${header.name}: ${header.value}`);
} }
if (requestBody) {
lines.push('');
lines.push(requestBody);
}
return lines.join('\n'); 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'); fs.writeFileSync(path.join(targetDir, '.env.template'), templateContent, 'utf8');
dotenvVariablesByTarget.set(targetDir, new Set(variableNames)); dotenvVariablesByTarget.set(targetDir, new Set(variableNames));
} }
+14 -1
View File
@@ -1,13 +1,26 @@
{ {
"name": "scripts", "name": "generate-http-docs",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"strip-json-comments": "^5.0.3",
"yaml": "^2.9.0" "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": { "node_modules/yaml": {
"version": "2.9.0", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+2
View File
@@ -1,5 +1,7 @@
{ {
"type": "module",
"dependencies": { "dependencies": {
"strip-json-comments": "^5.0.3",
"yaml": "^2.9.0" "yaml": "^2.9.0"
} }
} }