Streamline management of API collections #1
@@ -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));
|
||||
}
|
||||
|
||||
Generated
+14
-1
@@ -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",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"strip-json-comments": "^5.0.3",
|
||||
"yaml": "^2.9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user