add AI generated script
add gitignore for automatically generated stuff
This commit is contained in:
@@ -8,3 +8,6 @@ node_modules
|
|||||||
# OS files
|
# OS files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# Automatically generated stuff
|
||||||
|
autodocs/**/*
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function findWorkspaceRoot(startDir) {
|
||||||
|
let current = startDir;
|
||||||
|
while (true) {
|
||||||
|
if (fs.existsSync(path.join(current, 'workspace.yml'))) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
const parent = path.dirname(current);
|
||||||
|
if (parent === current) {
|
||||||
|
throw new Error('workspace.yml not found from the provided start directory');
|
||||||
|
}
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readText(filePath) {
|
||||||
|
return fs.readFileSync(filePath, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripQuotes(value) {
|
||||||
|
const trimmed = String(value).trim();
|
||||||
|
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
||||||
|
return trimmed.slice(1, -1);
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseWorkspace(workspacePath) {
|
||||||
|
const text = readText(workspacePath);
|
||||||
|
const lines = text.split(/\r?\n/);
|
||||||
|
const collections = [];
|
||||||
|
let inCollections = false;
|
||||||
|
let currentCollection = null;
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!inCollections && trimmed === 'collections:') {
|
||||||
|
inCollections = true;
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { collections };
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeVarName(value) {
|
||||||
|
return String(value)
|
||||||
|
.trim()
|
||||||
|
.replace(/[{}]/g, '')
|
||||||
|
.replace(/[^A-Za-z0-9_]/g, '_')
|
||||||
|
.replace(/^([0-9])/, '_$1') || 'value';
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectPlaceholders(value) {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const placeholders = [];
|
||||||
|
const regex = /\{\{([^{}]+)\}\}/g;
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(value)) !== null) {
|
||||||
|
placeholders.push(match[1]);
|
||||||
|
}
|
||||||
|
return placeholders;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findBlock(lines, keyName) {
|
||||||
|
for (let i = 0; i < lines.length; i += 1) {
|
||||||
|
const trimmed = lines[i].trim();
|
||||||
|
if (trimmed !== keyName && !trimmed.startsWith(`${keyName}:`)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineIndent = lines[i].match(/^\s*/)[0].length;
|
||||||
|
const block = [];
|
||||||
|
for (let j = i + 1; j < lines.length; j += 1) {
|
||||||
|
const currentLine = lines[j];
|
||||||
|
const currentTrimmed = currentLine.trim();
|
||||||
|
if (!currentTrimmed) {
|
||||||
|
block.push(currentLine);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const currentIndent = currentLine.match(/^\s*/)[0].length;
|
||||||
|
if (currentIndent <= lineIndent && !currentLine.startsWith(' ')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (currentIndent <= lineIndent && currentTrimmed.startsWith('#')) {
|
||||||
|
block.push(currentLine);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (currentIndent <= lineIndent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
block.push(currentLine);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRequestInfo(text) {
|
||||||
|
const lines = text.split(/\r?\n/);
|
||||||
|
const infoLines = findBlock(lines, 'info');
|
||||||
|
const nameMatch = infoLines.join('\n').match(/^\s*name:\s*(.+)$/m);
|
||||||
|
return nameMatch ? stripQuotes(nameMatch[1]) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
method: methodMatch ? stripQuotes(methodMatch[1]) : 'GET',
|
||||||
|
url: urlMatch ? stripQuotes(urlMatch[1]) : '',
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRequestContent(request, requestName) {
|
||||||
|
const lines = [];
|
||||||
|
const placeholders = new Set();
|
||||||
|
|
||||||
|
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 (type === 'header') {
|
||||||
|
headers.push({ name, value });
|
||||||
|
} else {
|
||||||
|
queryParams.push({ name, value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (placeholders.size > 0) {
|
||||||
|
lines.push(`# Variables for ${requestName}`);
|
||||||
|
for (const placeholder of [...placeholders].sort()) {
|
||||||
|
lines.push(`@${sanitizeVarName(placeholder)} = YOUR_VALUE_HERE`);
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function walkYamlFiles(rootDir) {
|
||||||
|
const results = [];
|
||||||
|
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const workspaceRoot = findWorkspaceRoot(__dirname);
|
||||||
|
const workspaceFile = path.join(workspaceRoot, 'workspace.yml');
|
||||||
|
const workspace = parseWorkspace(workspaceFile);
|
||||||
|
const collections = Array.isArray(workspace.collections) ? workspace.collections : [];
|
||||||
|
|
||||||
|
if (collections.length === 0) {
|
||||||
|
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);
|
||||||
|
if (!fs.existsSync(sourceDir)) {
|
||||||
|
console.warn(`Skipping missing collection path: ${collection.path}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
main();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user