refactor
- add YAML module and remove python child process - add collection headers to each request - fix authorization header emission - create .env.template file with environment variables
This commit is contained in:
+487
-255
@@ -1,325 +1,557 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const YAML = require('yaml');
|
||||||
|
|
||||||
|
const interpolationVariableRegex = /^{{(.*?)}}$/
|
||||||
|
|
||||||
function findWorkspaceRoot(startDir) {
|
function findWorkspaceRoot(startDir) {
|
||||||
let current = startDir;
|
let current = startDir;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (fs.existsSync(path.join(current, 'workspace.yml'))) {
|
if (fs.existsSync(path.join(current, 'workspace.yml'))) {
|
||||||
return current;
|
return current;
|
||||||
|
}
|
||||||
|
const parent = path.dirname(current);
|
||||||
|
if (parent === current) {
|
||||||
|
throw new Error('workspace.yml not found from the provided start directory');
|
||||||
|
}
|
||||||
|
current = parent;
|
||||||
}
|
}
|
||||||
const parent = path.dirname(current);
|
}
|
||||||
if (parent === current) {
|
|
||||||
throw new Error('workspace.yml not found from the provided start directory');
|
function parseYaml(filePath) {
|
||||||
|
try {
|
||||||
|
const text = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const parsed = YAML.parse(text);
|
||||||
|
return parsed || {};
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to parse YAML file ${filePath}: ${error.message}`);
|
||||||
}
|
}
|
||||||
current = parent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function readText(filePath) {
|
function readText(filePath) {
|
||||||
return fs.readFileSync(filePath, 'utf8');
|
return fs.readFileSync(filePath, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripQuotes(value) {
|
function stripQuotes(value) {
|
||||||
const trimmed = String(value).trim();
|
const trimmed = String(value).trim();
|
||||||
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
||||||
return trimmed.slice(1, -1);
|
return trimmed.slice(1, -1);
|
||||||
}
|
}
|
||||||
return trimmed;
|
return trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseWorkspace(workspacePath) {
|
function parseWorkspace(workspacePath) {
|
||||||
const text = readText(workspacePath);
|
const text = readText(workspacePath);
|
||||||
const lines = text.split(/\r?\n/);
|
const lines = text.split(/\r?\n/);
|
||||||
const collections = [];
|
const collections = [];
|
||||||
let inCollections = false;
|
let inCollections = false;
|
||||||
let currentCollection = null;
|
let currentCollection = null;
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (!inCollections && trimmed === 'collections:') {
|
if (!inCollections && trimmed === 'collections:') {
|
||||||
inCollections = true;
|
inCollections = true;
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inCollections) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line.startsWith(' ') && !line.startsWith('\t') && trimmed) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameMatch = line.match(/^\s*-\s+name:\s*(.+)$/);
|
||||||
|
if (nameMatch) {
|
||||||
|
currentCollection = { name: stripQuotes(nameMatch[1]) };
|
||||||
|
collections.push(currentCollection);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathMatch = line.match(/^\s*path:\s*(.+)$/);
|
||||||
|
if (pathMatch && currentCollection) {
|
||||||
|
currentCollection.path = stripQuotes(pathMatch[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!inCollections) {
|
return { collections };
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!line.startsWith(' ') && !line.startsWith('\t') && trimmed) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nameMatch = line.match(/^\s*-\s+name:\s*(.+)$/);
|
|
||||||
if (nameMatch) {
|
|
||||||
currentCollection = { name: stripQuotes(nameMatch[1]) };
|
|
||||||
collections.push(currentCollection);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathMatch = line.match(/^\s*path:\s*(.+)$/);
|
|
||||||
if (pathMatch && currentCollection) {
|
|
||||||
currentCollection.path = stripQuotes(pathMatch[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { collections };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeVarName(value) {
|
function sanitizeVarName(value) {
|
||||||
return String(value)
|
return String(value)
|
||||||
.trim()
|
.trim()
|
||||||
.replace(/[{}]/g, '')
|
.replace(/[{}]/g, '')
|
||||||
.replace(/[^A-Za-z0-9_]/g, '_')
|
.replace(/[^A-Za-z0-9_]/g, '_')
|
||||||
.replace(/^([0-9])/, '_$1') || 'value';
|
.replace(/^([0-9])/, '_$1') || 'value';
|
||||||
}
|
}
|
||||||
|
|
||||||
function collectPlaceholders(value) {
|
function collectPlaceholders(value) {
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const placeholders = [];
|
const placeholders = [];
|
||||||
const regex = /\{\{([^{}]+)\}\}/g;
|
const regex = /\{\{([^{}]+)\}\}/g;
|
||||||
let match;
|
let match;
|
||||||
while ((match = regex.exec(value)) !== null) {
|
while ((match = regex.exec(value)) !== null) {
|
||||||
placeholders.push(match[1]);
|
placeholders.push(match[1]);
|
||||||
}
|
}
|
||||||
return placeholders;
|
return placeholders;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findBlock(lines, keyName) {
|
function findBlock(lines, keyName) {
|
||||||
for (let i = 0; i < lines.length; i += 1) {
|
for (let i = 0; i < lines.length; i += 1) {
|
||||||
const trimmed = lines[i].trim();
|
const trimmed = lines[i].trim();
|
||||||
if (trimmed !== keyName && !trimmed.startsWith(`${keyName}:`)) {
|
if (trimmed !== keyName && !trimmed.startsWith(`${keyName}:`)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineIndent = lines[i].match(/^\s*/)[0].length;
|
const lineIndent = lines[i].match(/^\s*/)[0].length;
|
||||||
const block = [];
|
const block = [];
|
||||||
for (let j = i + 1; j < lines.length; j += 1) {
|
for (let j = i + 1; j < lines.length; j += 1) {
|
||||||
const currentLine = lines[j];
|
const currentLine = lines[j];
|
||||||
const currentTrimmed = currentLine.trim();
|
const currentTrimmed = currentLine.trim();
|
||||||
if (!currentTrimmed) {
|
if (!currentTrimmed) {
|
||||||
block.push(currentLine);
|
block.push(currentLine);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const currentIndent = currentLine.match(/^\s*/)[0].length;
|
const currentIndent = currentLine.match(/^\s*/)[0].length;
|
||||||
if (currentIndent <= lineIndent && !currentLine.startsWith(' ')) {
|
if (currentIndent <= lineIndent && !currentLine.startsWith(' ')) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (currentIndent <= lineIndent && currentTrimmed.startsWith('#')) {
|
if (currentIndent <= lineIndent && currentTrimmed.startsWith('#')) {
|
||||||
block.push(currentLine);
|
block.push(currentLine);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (currentIndent <= lineIndent) {
|
if (currentIndent <= lineIndent) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
block.push(currentLine);
|
block.push(currentLine);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
}
|
}
|
||||||
return block;
|
return [];
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRequestInfo(text) {
|
function parseRequestInfo(text) {
|
||||||
const lines = text.split(/\r?\n/);
|
const lines = text.split(/\r?\n/);
|
||||||
const infoLines = findBlock(lines, 'info');
|
const infoLines = findBlock(lines, 'info');
|
||||||
const nameMatch = infoLines.join('\n').match(/^\s*name:\s*(.+)$/m);
|
const nameMatch = infoLines.join('\n').match(/^\s*name:\s*(.+)$/m);
|
||||||
return nameMatch ? stripQuotes(nameMatch[1]) : '';
|
return nameMatch ? stripQuotes(nameMatch[1]) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseHttpBlock(text) {
|
function parseHttpBlock(text) {
|
||||||
const lines = text.split(/\r?\n/);
|
const lines = text.split(/\r?\n/);
|
||||||
const blockLines = findBlock(lines, 'http');
|
const blockLines = findBlock(lines, 'http');
|
||||||
const blockText = blockLines.join('\n');
|
const blockText = blockLines.join('\n');
|
||||||
const methodMatch = blockText.match(/^\s*method:\s*(.+)$/m);
|
const methodMatch = blockText.match(/^\s*method:\s*(.+)$/m);
|
||||||
const urlMatch = blockText.match(/^\s*url:\s*(.+)$/m);
|
const urlMatch = blockText.match(/^\s*url:\s*(.+)$/m);
|
||||||
|
|
||||||
const params = [];
|
const params = [];
|
||||||
const paramLines = blockLines.join('\n').split(/\r?\n/);
|
const paramLines = blockLines.join('\n').split(/\r?\n/);
|
||||||
let inParamsBlock = false;
|
let inParamsBlock = false;
|
||||||
let paramsIndent = 0;
|
let paramsIndent = 0;
|
||||||
let currentParam = null;
|
let currentParam = null;
|
||||||
|
|
||||||
for (const line of paramLines) {
|
for (const line of paramLines) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (!inParamsBlock) {
|
if (!inParamsBlock) {
|
||||||
if (trimmed === 'params:') {
|
if (trimmed === 'params:') {
|
||||||
inParamsBlock = true;
|
inParamsBlock = true;
|
||||||
paramsIndent = line.match(/^\s*/)[0].length;
|
paramsIndent = line.match(/^\s*/)[0].length;
|
||||||
}
|
}
|
||||||
continue;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!trimmed) {
|
return {
|
||||||
continue;
|
method: methodMatch ? stripQuotes(methodMatch[1]) : 'GET',
|
||||||
}
|
url: urlMatch ? stripQuotes(urlMatch[1]) : '',
|
||||||
|
params,
|
||||||
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 {
|
|
||||||
method: methodMatch ? stripQuotes(methodMatch[1]) : 'GET',
|
|
||||||
url: urlMatch ? stripQuotes(urlMatch[1]) : '',
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRequestContent(request, requestName) {
|
function formatVariableValue(value) {
|
||||||
const lines = [];
|
if (value === null || value === undefined) {
|
||||||
const placeholders = new Set();
|
return '""';
|
||||||
|
|
||||||
const addPlaceholders = (value) => {
|
|
||||||
for (const placeholder of collectPlaceholders(value)) {
|
|
||||||
placeholders.add(placeholder);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let url = request.url || '';
|
|
||||||
if (url) {
|
|
||||||
addPlaceholders(url);
|
|
||||||
url = url.replace(/:([A-Za-z0-9_]+)/g, (_, name) => `{{${name}}}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryParams = [];
|
|
||||||
const headers = [];
|
|
||||||
for (const param of request.params || []) {
|
|
||||||
const name = param.name || '';
|
|
||||||
const value = param.value || '';
|
|
||||||
const type = (param.type || 'query').toLowerCase();
|
|
||||||
const disabled = String(param.disabled).toLowerCase() === 'true';
|
|
||||||
|
|
||||||
if (disabled) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addPlaceholders(value);
|
if (typeof value === 'string') {
|
||||||
if (type === 'header') {
|
if (value.trim() === '') {
|
||||||
headers.push({ name, value });
|
return '""';
|
||||||
} else {
|
}
|
||||||
queryParams.push({ name, value });
|
if (/\s/.test(value)) {
|
||||||
|
return `"${value.replace(/"/g, '\\"')}"`;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (placeholders.size > 0) {
|
return JSON.stringify(value);
|
||||||
lines.push(`# Variables for ${requestName}`);
|
}
|
||||||
for (const placeholder of [...placeholders].sort()) {
|
|
||||||
lines.push(`@${sanitizeVarName(placeholder)} = YOUR_VALUE_HERE`);
|
function mergeRequestConfig(base, updates) {
|
||||||
|
if (!updates || typeof updates !== 'object') {
|
||||||
|
return base;
|
||||||
}
|
}
|
||||||
lines.push('');
|
|
||||||
}
|
|
||||||
|
|
||||||
const method = (request.method || 'GET').toUpperCase();
|
const merged = { ...(base || {}) };
|
||||||
let requestUrl = url;
|
if (updates.auth) {
|
||||||
for (const param of queryParams) {
|
if (updates.auth === 'inherit' && merged.auth && typeof merged.auth === 'object') {
|
||||||
if (!param.name) {
|
merged.auth = merged.auth;
|
||||||
continue;
|
} else if (typeof updates.auth === 'object') {
|
||||||
|
merged.auth = updates.auth;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const separator = requestUrl.includes('?') ? '&' : '?';
|
if (Array.isArray(updates.variables)) {
|
||||||
requestUrl = `${requestUrl}${separator}${param.name}=${param.value}`;
|
const variables = [...(Array.isArray(base.variables) ? base.variables : [])];
|
||||||
}
|
const byName = new Map();
|
||||||
|
for (const variable of variables) {
|
||||||
|
if (variable && variable.name) {
|
||||||
|
byName.set(String(variable.name), variable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const variable of updates.variables) {
|
||||||
|
if (variable && variable.name) {
|
||||||
|
byName.set(String(variable.name), variable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
merged.variables = [...byName.values()];
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
lines.push(`${method} ${requestUrl}`);
|
function getRequestConfigForFile(yamlFile, sourceDir) {
|
||||||
for (const header of headers) {
|
const resolved = [];
|
||||||
lines.push(`${header.name}: ${header.value}`);
|
const seenFiles = new Set();
|
||||||
}
|
|
||||||
return lines.join('\n');
|
const addFile = (filePath) => {
|
||||||
|
if (!filePath || seenFiles.has(filePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seenFiles.add(filePath);
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = parseYaml(filePath);
|
||||||
|
if (parsed && parsed.request && typeof parsed.request === 'object') {
|
||||||
|
resolved.push(parsed.request);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore files that cannot be parsed as YAML for request inheritance.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addFile(yamlFile);
|
||||||
|
|
||||||
|
const dirChain = [];
|
||||||
|
let currentDir = path.dirname(yamlFile);
|
||||||
|
while (true) {
|
||||||
|
dirChain.unshift(currentDir);
|
||||||
|
if (currentDir === sourceDir) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const parentDir = path.dirname(currentDir);
|
||||||
|
if (parentDir === currentDir) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentDir = parentDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dir of dirChain) {
|
||||||
|
addFile(path.join(dir, 'opencollection.yml'));
|
||||||
|
addFile(path.join(dir, 'folder.yml'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved.reduce((result, config) => mergeRequestConfig(result, config), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRequestContent(request, requestName, requestConfig = {}) {
|
||||||
|
const lines = [];
|
||||||
|
const variableDefinitions = [];
|
||||||
|
const seenVariables = new Set();
|
||||||
|
|
||||||
|
const addVariable = (name, value) => {
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const normalized = String(name).trim();
|
||||||
|
if (!normalized || seenVariables.has(normalized)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seenVariables.add(normalized);
|
||||||
|
variableDefinitions.push({ name: normalized, value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const configVariables = Array.isArray(requestConfig.variables) ? requestConfig.variables : [];
|
||||||
|
for (const variable of configVariables) {
|
||||||
|
if (variable && variable.name) {
|
||||||
|
addVariable(variable.name, variable.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = request.url || '';
|
||||||
|
if (url) {
|
||||||
|
for (const placeholder of collectPlaceholders(url)) {
|
||||||
|
addVariable(placeholder, 'YOUR_VALUE_HERE');
|
||||||
|
}
|
||||||
|
url = url.replace(/:([A-Za-z0-9_]+)/g, (_, name) => `{{${name}}}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryParams = [];
|
||||||
|
const headers = [];
|
||||||
|
|
||||||
|
for (const header of Array.isArray(requestConfig.headers) ? requestConfig.headers : []) {
|
||||||
|
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 ?? '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const param of request.params || []) {
|
||||||
|
const name = param.name || '';
|
||||||
|
const value = param.value || '';
|
||||||
|
const type = (param.type || 'query').toLowerCase();
|
||||||
|
const disabled = String(param.disabled).toLowerCase() === 'true';
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const placeholder of collectPlaceholders(String(value))) {
|
||||||
|
addVariable(placeholder, 'YOUR_VALUE_HERE');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'header') {
|
||||||
|
headers.push({ name, value });
|
||||||
|
} else {
|
||||||
|
queryParams.push({ name, value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestConfig.auth)
|
||||||
|
{
|
||||||
|
if (requestConfig.auth.type === 'bearer') {
|
||||||
|
for (const placeholder of collectPlaceholders(String(requestConfig.auth.token ?? ''))) {
|
||||||
|
addVariable(placeholder, 'YOUR_VALUE_HERE');
|
||||||
|
}
|
||||||
|
headers.push({
|
||||||
|
name: 'Authorization',
|
||||||
|
value: `Bearer ${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');
|
||||||
|
}
|
||||||
|
// VSCode REST Client can manage username:password format directly!
|
||||||
|
headers.push({
|
||||||
|
name: 'Authorization',
|
||||||
|
value: `Basic ${username}:${password}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
headers.push({
|
||||||
|
name: `UNKNOWN_${requestConfig.auth.type}`,
|
||||||
|
value: `Basic ${requestConfig.auth.token}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variableDefinitions.length > 0) {
|
||||||
|
lines.push(`# Variables for ${requestName}`);
|
||||||
|
for (const variable of variableDefinitions) {
|
||||||
|
lines.push(`@${sanitizeVarName(variable.name)} = ${formatVariableValue(variable.value)}`);
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = (request.method || 'GET').toUpperCase();
|
||||||
|
let requestUrl = url;
|
||||||
|
for (const param of queryParams) {
|
||||||
|
if (!param.name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const separator = requestUrl.includes('?') ? '&' : '?';
|
||||||
|
requestUrl = `${requestUrl}${separator}${param.name}=${param.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(`${method} ${requestUrl}`);
|
||||||
|
for (const header of headers) {
|
||||||
|
lines.push(`${header.name}: ${header.value}`);
|
||||||
|
}
|
||||||
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureDir(dirPath) {
|
function ensureDir(dirPath) {
|
||||||
fs.mkdirSync(dirPath, { recursive: true });
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
function walkYamlFiles(rootDir) {
|
function walkYamlFiles(rootDir) {
|
||||||
const results = [];
|
const results = [];
|
||||||
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
const fullPath = path.join(rootDir, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
results.push(...walkYamlFiles(fullPath));
|
||||||
|
} else if (entry.isFile() && /\.ya?ml$/i.test(entry.name)) {
|
||||||
|
results.push(fullPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const fullPath = path.join(rootDir, entry.name);
|
return results;
|
||||||
if (entry.isDirectory()) {
|
}
|
||||||
results.push(...walkYamlFiles(fullPath));
|
|
||||||
} else if (entry.isFile() && /\.ya?ml$/i.test(entry.name)) {
|
function writeEnvironmentTemplates(sourceDir, outputRoot) {
|
||||||
results.push(fullPath);
|
const targets = [];
|
||||||
|
|
||||||
|
const visit = (currentDir) => {
|
||||||
|
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||||
|
const hasEnvironmentsDir = entries.some((entry) => entry.isDirectory() && entry.name === 'environments');
|
||||||
|
|
||||||
|
if (hasEnvironmentsDir) {
|
||||||
|
targets.push(currentDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
visit(path.join(currentDir, entry.name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
visit(sourceDir);
|
||||||
|
|
||||||
|
for (const dir of targets) {
|
||||||
|
const relativeDir = path.relative(sourceDir, dir);
|
||||||
|
const targetDir = relativeDir && relativeDir !== '.' ? path.join(outputRoot, relativeDir) : outputRoot;
|
||||||
|
ensureDir(targetDir);
|
||||||
|
|
||||||
|
const environmentsDir = path.join(dir, 'environments');
|
||||||
|
if (!fs.existsSync(environmentsDir)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const envFiles = fs.readdirSync(environmentsDir, { withFileTypes: true })
|
||||||
|
.filter((entry) => entry.isFile() && /\.ya?ml$/i.test(entry.name))
|
||||||
|
.map((entry) => path.join(environmentsDir, entry.name));
|
||||||
|
|
||||||
|
const variableNames = [];
|
||||||
|
const seenNames = new Set();
|
||||||
|
|
||||||
|
for (const envFile of envFiles) {
|
||||||
|
const parsed = parseYaml(envFile);
|
||||||
|
const variables = Array.isArray(parsed.variables) ? parsed.variables : [];
|
||||||
|
for (const variable of variables) {
|
||||||
|
if (!variable || !variable.name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const name = String(variable.name).trim();
|
||||||
|
if (!name || seenNames.has(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seenNames.add(name);
|
||||||
|
variableNames.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateContent = variableNames.length > 0 ? `${variableNames.join('\n')}\n` : '';
|
||||||
|
fs.writeFileSync(path.join(targetDir, '.env.template'), templateContent, 'utf8');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
const workspaceRoot = findWorkspaceRoot(__dirname);
|
const workspaceRoot = findWorkspaceRoot(__dirname);
|
||||||
const workspaceFile = path.join(workspaceRoot, 'workspace.yml');
|
const workspaceFile = path.join(workspaceRoot, 'workspace.yml');
|
||||||
const workspace = parseWorkspace(workspaceFile);
|
const workspace = parseWorkspace(workspaceFile);
|
||||||
const collections = Array.isArray(workspace.collections) ? workspace.collections : [];
|
const collections = Array.isArray(workspace.collections) ? workspace.collections : [];
|
||||||
|
|
||||||
if (collections.length === 0) {
|
if (collections.length === 0) {
|
||||||
throw new Error('No collections found in workspace.yml');
|
throw new Error('No collections found in workspace.yml');
|
||||||
}
|
|
||||||
|
|
||||||
for (const collection of collections) {
|
|
||||||
if (!collection || !collection.name || !collection.path) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceDir = path.join(workspaceRoot, collection.path);
|
for (const collection of collections) {
|
||||||
if (!fs.existsSync(sourceDir)) {
|
if (!collection || !collection.name || !collection.path) {
|
||||||
console.warn(`Skipping missing collection path: ${collection.path}`);
|
continue;
|
||||||
continue;
|
}
|
||||||
|
|
||||||
|
const sourceDir = path.join(workspaceRoot, collection.path);
|
||||||
|
if (!fs.existsSync(sourceDir)) {
|
||||||
|
console.warn(`Skipping missing collection path: ${collection.path}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputRoot = path.join(workspaceRoot, 'autodocs', 'http', collection.name);
|
||||||
|
ensureDir(outputRoot);
|
||||||
|
writeEnvironmentTemplates(sourceDir, outputRoot);
|
||||||
|
|
||||||
|
const yamlFiles = walkYamlFiles(sourceDir);
|
||||||
|
let processed = 0;
|
||||||
|
|
||||||
|
for (const yamlFile of yamlFiles) {
|
||||||
|
const content = readText(yamlFile);
|
||||||
|
const requestName = parseRequestInfo(content) || path.basename(yamlFile, path.extname(yamlFile));
|
||||||
|
const httpBlock = parseHttpBlock(content);
|
||||||
|
if (!httpBlock || !httpBlock.url) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativePath = path.relative(sourceDir, yamlFile);
|
||||||
|
const parsedPath = path.parse(relativePath);
|
||||||
|
const targetDir = path.join(outputRoot, parsedPath.dir);
|
||||||
|
ensureDir(targetDir);
|
||||||
|
|
||||||
|
const requestConfig = getRequestConfigForFile(yamlFile, sourceDir);
|
||||||
|
const outputFile = path.join(targetDir, `${parsedPath.name}.http`);
|
||||||
|
const requestContent = buildRequestContent(httpBlock, requestName, requestConfig);
|
||||||
|
fs.writeFileSync(outputFile, `${requestContent}\n`, 'utf8');
|
||||||
|
processed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Generated ${processed} .http file(s) for ${collection.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputRoot = path.join(workspaceRoot, 'autodocs', 'http', collection.name);
|
|
||||||
ensureDir(outputRoot);
|
|
||||||
|
|
||||||
const yamlFiles = walkYamlFiles(sourceDir);
|
|
||||||
let processed = 0;
|
|
||||||
|
|
||||||
for (const yamlFile of yamlFiles) {
|
|
||||||
const content = readText(yamlFile);
|
|
||||||
const requestName = parseRequestInfo(content) || path.basename(yamlFile, path.extname(yamlFile));
|
|
||||||
const httpBlock = parseHttpBlock(content);
|
|
||||||
if (!httpBlock || !httpBlock.url) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const relativePath = path.relative(sourceDir, yamlFile);
|
|
||||||
const parsedPath = path.parse(relativePath);
|
|
||||||
const targetDir = path.join(outputRoot, parsedPath.dir);
|
|
||||||
ensureDir(targetDir);
|
|
||||||
|
|
||||||
const outputFile = path.join(targetDir, `${parsedPath.name}.http`);
|
|
||||||
const requestContent = buildRequestContent(httpBlock, requestName);
|
|
||||||
fs.writeFileSync(outputFile, `${requestContent}\n`, 'utf8');
|
|
||||||
processed += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Generated ${processed} .http file(s) for ${collection.name}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
main();
|
main();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "scripts",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/eemeli"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.9.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user