UNPKG

mfdoc

Version:

Auto generate JS SDK and HTTP API documentation

237 lines 9.54 kB
import assert from 'assert'; import fse from 'fs-extra'; import { compact, identity, isArray, isUndefined } from 'lodash-es'; import path from 'path'; import { indexArray, pathSplit } from 'softkave-js-utils'; import { isMfdocFieldArray, isMfdocFieldBase, isMfdocFieldObjectField, isMfdocFieldOrCombination, } from './mfdoc.js'; export function filterEndpointsByTags(endpoints, tags) { if (tags.length === 0) { return endpoints; } const tagsMap = indexArray(tags, { indexer: identity }); return endpoints.filter(endpoint => { return endpoint.tags?.some(tag => tagsMap[tag]); }); } function matchesString(value, pattern) { return value === pattern; } function matchesAnyString(value, patterns) { return patterns.some(pattern => matchesString(value, pattern)); } function matchesRegexp(value, pattern) { return pattern.test(value); } function matchesAnyRegexp(value, patterns) { return patterns.some(pattern => matchesRegexp(value, pattern)); } function matchesAny(value, stringPatterns, regexpPatterns) { if (stringPatterns.length > 0 && matchesAnyString(value, stringPatterns)) { return true; } if (regexpPatterns.length > 0 && matchesAnyRegexp(value, regexpPatterns)) { return true; } return false; } /** * Parses a regexp string in the format "/pattern/flags" into a RegExp object. * Returns null if the string is not a valid regexp format. */ export function parseRegExpString(input) { const regexMatch = input.match(/^\/(.+)\/([gimuy]*)$/); if (regexMatch) { try { return new RegExp(regexMatch[1], regexMatch[2]); } catch { return null; } } return null; } export function filterEndpoints(endpoints, options = {}) { const { includeTags = [], ignoreTags = [], includeTagsRegExp = [], ignoreTagsRegExp = [], includePaths = [], ignorePaths = [], includePathsRegExp = [], ignorePathsRegExp = [], includeNames = [], ignoreNames = [], includeNamesRegExp = [], ignoreNamesRegExp = [], } = options; return endpoints.filter(endpoint => { // Ignore tags take precedence - if endpoint has any ignore tag, filter it out if ((ignoreTags.length > 0 || ignoreTagsRegExp.length > 0) && endpoint.tags) { const hasIgnoreTag = endpoint.tags.some(tag => matchesAny(tag, ignoreTags, ignoreTagsRegExp)); if (hasIgnoreTag) { return false; } } // Include tags - endpoint must have at least one include tag (if includeTags is specified) if (includeTags.length > 0 || includeTagsRegExp.length > 0) { if (!endpoint.tags || endpoint.tags.length === 0) { return false; } const hasIncludeTag = endpoint.tags.some(tag => matchesAny(tag, includeTags, includeTagsRegExp)); if (!hasIncludeTag) { return false; } } // Ignore paths - filter out endpoints with matching paths if (ignorePaths.length > 0 || ignorePathsRegExp.length > 0) { if (matchesAny(endpoint.path, ignorePaths, ignorePathsRegExp)) { return false; } } // Include paths - endpoint must match at least one include path (if includePaths is specified) if (includePaths.length > 0 || includePathsRegExp.length > 0) { if (!matchesAny(endpoint.path, includePaths, includePathsRegExp)) { return false; } } // Ignore names - filter out endpoints with matching names if (ignoreNames.length > 0 || ignoreNamesRegExp.length > 0) { const fullName = endpoint.name ? endpoint.name : getEndpointNames(endpoint).join('.'); if (matchesAny(fullName, ignoreNames, ignoreNamesRegExp)) { return false; } } // Include names - endpoint must match at least one include name (if includeNames is specified) if (includeNames.length > 0 || includeNamesRegExp.length > 0) { const fullName = endpoint.name ? endpoint.name : getEndpointNames(endpoint).join('.'); if (!matchesAny(fullName, includeNames, includeNamesRegExp)) { return false; } } return true; }); } export async function hasPackageJson(params) { const { outputPath } = params; const pkgJsonPath = path.join(outputPath, 'package.json'); return ((await fse.exists(pkgJsonPath)) && (await fse.stat(pkgJsonPath)).isFile()); } export const kInstallScripts = { npm: (pkgName) => `npm install ${pkgName}`, yarn: (pkgName) => `yarn add ${pkgName}`, pnpm: (pkgName) => `pnpm add ${pkgName}`, bun: (pkgName) => `bun add ${pkgName}`, deno: (pkgName) => `deno add ${pkgName}`, }; export async function hasPkgInstalled(params) { const { outputPath, pkgName } = params; return ((await fse.exists(path.join(outputPath, 'node_modules', pkgName))) && (await fse.stat(path.join(outputPath, 'node_modules', pkgName))).isDirectory()); } export function getEndpointNames(endpoint) { const endpointName = endpoint.name ? pathSplit({ input: endpoint.name }).filter(p => p.length > 0) : undefined; const pathname = endpoint.path .split('/') .filter(p => !p.startsWith(':') && p.length > 0); const names = endpointName || pathname; assert.ok(names.length > 0, 'names is required'); return names; } export async function addScriptToPackageJson(params) { const { outputPath, scriptName, scriptValue } = params; const pkgJsonPath = path.join(outputPath, 'package.json'); await fse.ensureFile(pkgJsonPath); const pkgJson = await fse.readJson(pkgJsonPath); pkgJson.scripts = pkgJson.scripts || {}; pkgJson.scripts[scriptName] = scriptValue; await fse.writeJson(pkgJsonPath, pkgJson, { spaces: 2 }); } export function gatherDescriptionAndExample(item) { const descriptions = []; const examples = []; if (isMfdocFieldArray(item)) { const { descriptions: typeDescriptions, examples: typeExamples } = gatherDescriptionAndExample(item.type); if (item.description) { descriptions.push(item.description); } else { descriptions.push(...typeDescriptions.map(description => description ? `[Array] ${description}` : undefined)); } if (!isUndefined(item.example)) { examples.push(item.example); } else { examples.push(...typeExamples.map(example => (example ? [example] : example))); } } else if (isMfdocFieldOrCombination(item)) { if (item.description) { descriptions.push(item.description); } if (!isUndefined(item.example)) { examples.push(item.example); } item.types.forEach((type) => { const { descriptions: typeDescriptions, examples: typeExamples } = gatherDescriptionAndExample(type); if (!descriptions.length) { descriptions.push(...typeDescriptions); } if (!examples.length) { examples.push(...typeExamples); } }); } else if (isMfdocFieldObjectField(item)) { const { descriptions: dataDescriptions, examples: dataExamples } = gatherDescriptionAndExample(item.data); if (item.description) { descriptions.push(item.description); } else { descriptions.push(...dataDescriptions); } if (!isUndefined(item.example)) { examples.push(item.example); } else { examples.push(...dataExamples); } } else if (isMfdocFieldBase(item)) { descriptions.push(item.description); examples.push(item.example); } return { descriptions, examples }; } export function getDescriptionForEndpointInfo(inputDescriptions, inputExamples) { const descriptions = compact(Array.isArray(inputDescriptions) ? inputDescriptions : [inputDescriptions]); const examples = compact(Array.isArray(inputExamples) ? inputExamples : [inputExamples]); let comment = descriptions.join('\n'); if (examples) { comment += `\n\nExamples:\n${examples.join('\n')}`; } return comment; } export function getDescriptionForJsDoc(inputDescriptions, inputExamples) { const descriptions = compact(Array.isArray(inputDescriptions) ? inputDescriptions : [inputDescriptions]); const examples = compact(Array.isArray(inputExamples) ? inputExamples : [inputExamples]); let comment = ''; const descriptionText = descriptions.join('  · \n'); const examplesText = examples ?.map(example => isArray(example) || typeof example === 'object' ? `\`\`\`json\n${JSON.stringify(example, null, 2)}\n\`\`\`` : example ? `\`\`\`\n${example.toString()}\n\`\`\`` : '') .join('\n\n'); if (descriptionText) { comment += descriptionText; } if (examplesText) { comment += `\n\n@example\n${examplesText}`; } return comment; } export function stringToJsDoc(input) { if (!input) { return ''; } const lines = input.split('\n').filter(line => line.trim() !== ''); const comment = lines.map(line => ` * ${line}`).join('\n'); return `/**\n${comment}\n */`; } //# sourceMappingURL=utils.js.map