UNPKG

@appnest/readme

Version:

Automatically generate a beautiful best-practice README file based on the contents of your repository

1,503 lines (1,314 loc) 37.4 kB
import { green, red, yellow } from 'colors'; import { readFileSync, outputFile, existsSync } from 'fs-extra'; import { resolve } from 'path'; import checkLinks from 'check-links'; import glob from 'glob'; import { analyzeText, transformAnalyzerResult } from 'web-component-analyzer'; import { version as version$1, command, help, on, parse } from 'commander'; /** * Generate npm badges. * @param npmId */ function npmBadges({ npmId }) { return [{ "alt": "Downloads per month", "url": `https://npmcharts.com/compare/${npmId}?minimal=true`, "img": `https://img.shields.io/npm/dm/${npmId}.svg` }, { "alt": "NPM Version", "url": `https://www.npmjs.com/package/${npmId}`, "img": `https://img.shields.io/npm/v/${npmId}.svg` }]; } /** * Generate github badges. * @param githubId */ function githubBadges({ githubId }) { return [{ "alt": "Dependencies", "url": `https://david-dm.org/${githubId}`, "img": `https://img.shields.io/david/${githubId}.svg` }, { "alt": "Contributors", "url": `https://github.com/${githubId}/graphs/contributors`, "img": `https://img.shields.io/github/contributors/${githubId}.svg` }]; } /** * Generates the webcomponents badges. * @param webcomponentsId */ function webcomponentsBadges({ webcomponentsId }) { return [{ "alt": "Published on webcomponents.org", "url": `https://www.webcomponents.org/element/${webcomponentsId}`, "img": `https://img.shields.io/badge/webcomponents.org-published-blue.svg` }]; } const URL_PATTERN = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.​\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[​6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1​,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00​a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u​00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/i; /** * Returns whether the URL is valid. * @param url */ function isValidURL(url) { return URL_PATTERN.test(url); } /** * Determines whether an object has the specified key. * @param obj * @param key */ function hasKey(obj, key) { return getValue(obj, key) != null; } /** * Returns the license url. * @param license */ function getLicenseUrl(license) { return `https://opensource.org/licenses/${license}`; } /** * Returns a key from from an object for a key path. * @param obj * @param keyPath */ function getValue(obj, keyPath) { let keys = keyPath.split("."); while (keys.length > 0 && obj != null) { keyPath = keys.shift(); obj = obj[keyPath]; } return obj; } /** * Sets a value for a key path (".") * @param obj * @param keyPath * @param value */ function setValue(obj, keyPath, value) { let keys = keyPath.split("."); while (keys.length > 0) { // Set value for the last key if (keys.length === 1) { obj[keys.shift()] = value; return; } const key = keys.shift(); if (obj[key] != null) { obj = obj[key]; } else { obj = obj[key] = {}; } } } /** * Validates the package. * @param obj * @param fileName */ function validateObject({ obj, requiredFields }) { for (const key of requiredFields) { if (!hasKey(obj, key)) { return false; } } return true; } /** * Returns whether the func is a function. * @param func */ function isFunction(func) { return typeof func === "function"; } /** * Returns whether the obj is an object. * @param obj */ function isObject(obj) { if (obj == null) { return false; } return typeof obj === "object" && !Array.isArray(obj); } /** * Extracts values from an object. * @param map * @param obj */ function extractValues({ map, obj }) { const newObj = {}; for (const [k, v] of Object.entries(map)) { newObj[k] = getValue(obj, v); } return newObj; } /** * Returns available badges. * @param pkg */ function getBadges({ config }) { const badges = []; const npmId = getValue(config, "ids.npm"); const githubId = getValue(config, "ids.github"); const webcomponentsId = getValue(config, "ids.webcomponents"); // Add NPM badges if (npmId != null) { badges.push(...npmBadges({ npmId })); } // Add Github badges if (githubId != null) { badges.push(...githubBadges({ githubId })); } // Add webcomponents badges if (webcomponentsId != null) { badges.push(...webcomponentsBadges({ webcomponentsId })); } // Add user badges badges.push(...(config.badges || [])); return badges; } /** * Reads a file. * @param name */ function readFile(name) { // Checks whether the file exists if (!fileExists(name)) { return null; } return readFileSync(resolve(process.cwd(), name)).toString("utf8"); } /** * Reads the contents of a json file. * @param name */ function readJSONFile(name) { const file = readFile(name); return file != null ? JSON.parse(file) : file; } /** * Escapes a regex. * @param text */ function escapeRegex(text) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } /** * Returns a placeholder regex. * @param text */ function placeholderRegexCallback(text) { return ({ config }) => { const { placeholder } = config; return new RegExp(`${escapeRegex(placeholder[0])}\\s*(${text})\\s*${escapeRegex(placeholder[1])}`, "gm"); }; } /** * Writes a file to a path. * @param target * @param content */ async function writeFile({ target, content }) { try { await outputFile(target, content); } catch (err) { console.error(err); } } /** * Returns the title for a level. * @param title * @param level * @param config */ function getTitle({ title, level, config }) { const prefix = config.headingPrefix[level] || ""; return `${prefix}${title}`; } /** * Cleans the title from weird symbols. * @param title */ function getCleanTitle(title) { return title.replace(/[^a-zA-Z0-9-_ ]/g, ""); } /** * Returns the title link. * @param title * @param index */ function getTitleLink(title, index = 0) { return `#${getCleanTitle(title).replace(/ /g, "-").toLowerCase()}${index > 0 ? `-${index}` : ""}`; } /** * Determines whether the file at the path exists. * @param absolutePath */ function fileExists(absolutePath) { if (absolutePath == null || absolutePath == "") return false; return existsSync(absolutePath); } /** * Splits an array into smaller arrays. * @param arr * @param count */ function splitArrayIntoArrays(arr, count) { arr = [...arr]; const arrs = []; while (arr.length) { arrs.push(arr.splice(0, count)); } return arrs; } /** * Replaces content in string between two indicies. * @param string * @param start * @param end * @param content */ function replaceInString(string, content, { start, end }) { return string.substring(0, start) + content + string.substring(end); } /** * Loads the package file. * @param pkgPath */ function loadPackage(pkgPath) { return readJSONFile(pkgPath) || null; } /** * Loads the config file. * @param configPath */ function loadConfig(configPath) { return readJSONFile(configPath) || null; } /** * Returns links from a text. * @param text */ function getLinks(text) { return Array.from(text.match(/(http|www)[A-Za-z\d-\._~:\/?#\[\]@!\$&\+;=]+/gm) || []); } /** * Checks all links in the text for aliveness. * @param text */ async function checkLinksAliveness(text) { const links = getLinks(text); console.log(green(`[readme] - Found "${links.length}" link${links.length === 1 ? "" : `s`}. Checking all of them now!`)); // Check all links const results = await checkLinks(links); // Go through the results and notify the user about broken links for (const [link, { status, statusCode }] of Object.entries(results)) { if (status === "dead") { console.log(red(`[readme] - The link "${link}" is dead. Responded with status code "${statusCode}".`)); } } } /** * Runs the check links command. * @param options */ async function checkLinksCommand(options) { const path = getValue(options, "input"); // Ensure that a path exists if (path == null) { console.log(red(`[readme] - Could not resolve '${path}'.`)); return; } const content = readFile(path); // Ensure that the file could be read if (content == null) { console.log(red(`[readme] - Could not read the file at path '${path}'.`)); return; } await checkLinksAliveness(content); } var LineColor; (function (LineColor) { LineColor["AQUA"] = "aqua"; LineColor["CLOUDY"] = "cloudy"; LineColor["COLORED"] = "colored"; LineColor["CUT"] = "cut"; LineColor["DARK"] = "dark"; LineColor["FIRE"] = "fire"; LineColor["GRASS"] = "grass"; LineColor["RAINBOW"] = "rainbow"; LineColor["SOLAR"] = "solar"; LineColor["VINTAGE"] = "vintage"; LineColor["WATER"] = "water"; LineColor["NONE"] = "none"; })(LineColor || (LineColor = {})); const fs = require("fs"); /** * Creates the template for the logo. * @param logo */ function logoTemplate({ logo }) { const { src, width = "auto", height = "auto", alt = "Logo" } = logo; return `<p align="center"> <img src="${src}" alt="${alt}" width="${width}" height="${height}" /> </p>`; } /** * Creates the template for the title. * @param name */ function mainTitleTemplate({ name }) { return `<h1 align="center">${name}</h1>`; } /** * Creates a line template. * @param config */ function lineTemplate({ config }) { let url = ""; const { line } = config; // If the line should not be there we just return an empty string. if (line === LineColor.NONE) { return ``; } // Construct the URL. if (isValidURL(line)) { url = line; } else { url = `https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/${line}.png`; } return `![-----------------------------------------------------](${url})`; } /** * Creates a template for the title. * @param title * @param level * @param config */ function titleTemplate({ title, level, config }) { const beforeTitleContent = level <= 2 ? `${config.lineBreak}[${lineTemplate({ config })}](${getTitleLink(title)})${config.lineBreak}${config.lineBreak}` : ""; return `${beforeTitleContent}${Array(level).fill("#").join("")} ${getTitle({ title, level, config })}`; } /** * Creates a template for the badges. * @param badges * @param config */ function badgesTemplate({ badges, config }) { return `<p align="center"> ${badges.map(badge => `<a href="${badge.url}"><img alt="${badge.alt}" src="${badge.img}" height="20"/></a>`).join(config.lineBreak)} </p> `; } /** * Creates a template for the license. * @param license * @returns {string} */ function licenseTemplate({ license }) { return `## License Licensed under [${license}](${getLicenseUrl(license)}).`; } /** * Creates a template for the demo link. * @param url */ function demoTemplate({ url }) { return `Go here to see a demo <a href="${url}">${url}</a>.`; } /** * Creates a description template. * @param description * @param text * @param demo */ function descriptionTemplate({ description, text, demo }) { return `<p align="center"> <b>${description}</b></br> <sub>${text != null ? text : ""}${demo != null ? ` ${demoTemplate({ url: demo })}` : ""}<sub> </p> <br /> `; } /** * Creates a bullets template. * @param bullets * @param pkg */ function bulletsTemplate({ bullets, config }) { return bullets.map(bullet => `* ${bullet}`).join(config.lineBreak); } /** * Creates a table template. * @param rows * @param config * @param centered */ function tableTemplate({ rows, config, centered }) { /** * Fills the width of the cell. * @param text * @param width * @param paddingStart */ function fillWidth(text, width, paddingStart) { return " ".repeat(paddingStart) + text + " ".repeat(Math.max(1, width - text.length - paddingStart)); } /** * Escape a text so it can be used in a markdown table * @param text */ function markdownEscapeTableCell(text) { return text.replace(/\n/g, "<br />").replace(/\|/g, "\\|"); } // Filter away the rows that have no content rows = rows.filter(row => row.map(r => r.trim()).join("").length > 0); // Count the amount of columns const columnCount = Math.max(...rows.map(r => r.length)); // Escape all cells in the markdown output rows = rows.map(r => r.map(markdownEscapeTableCell)); const MIN_WIDTH = 3; const MAX_WIDTH = 50; const PADDING = 1; const tableColPrefix = centered ? ":" : ""; const columnWidths = Array(columnCount).fill(0).map((c, i) => Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, ...rows.map(r => (r[i] || "").length)) + PADDING * 2)); return ` |${rows[0].map((r, i) => fillWidth(r, columnWidths[i], PADDING)).join("|")}| |${columnWidths.map(c => `${tableColPrefix}${"-".repeat(c)}${tableColPrefix}`).join("|")}| ${rows.slice(1).map(r => `|${r.map((r, i) => fillWidth(r, columnWidths[i], PADDING)).join("|")}|`).join(config.lineBreak)} `; } /** * Creates the table of contents. * @param titles * @param pkg */ function tocTemplate({ titles, config }) { // Create a clean titles array. // We are going to use it to figure out the index of each title (there might be more titles with the same name). const tempCleanTitles = titles.map(title => getCleanTitle(title)); // Create a map, mapping each clean title to the amount of times it occurs in the titles array const countForTitle = tempCleanTitles.reduce((acc, title) => { acc[title] = (acc[title] || 0) + 1; return acc; }, {}); // Map the titles to relevant info. const titlesInfo = titles.map(title => { const cleanTitle = getCleanTitle(title); const titlesWithSameName = tempCleanTitles.filter(t => t === cleanTitle); // Remove title from the temp array and compute the index tempCleanTitles.splice(tempCleanTitles.indexOf(cleanTitle), 1); // Compute the index (the first will be 0 and so on) const index = (countForTitle[cleanTitle] || 1) - titlesWithSameName.length; // Compute the level of the title const level = (title.match(/#/g) || []).length; // Remove the "# " from the titles so eg. "## Hello" becomes "Hello" title = title.replace(/^#*\s?/, ""); // Compute the title link const titleLink = getTitleLink(title, index); return { title, cleanTitle, index, titleLink, level }; }); // Find the lowest level of heading (# is lower than ##) const lowestLevel = titlesInfo.reduce((acc, { title, level }) => Math.min(acc, level), Infinity); // Format the table of contents title because it is applied after the title template return `${titleTemplate({ title: "Table of Contents", level: 2, config: config })} ${titlesInfo.map(({ level, titleLink, title }) => { // Subtract the lowest level from the level to ensure that the lowest level will have 0 tabs in front // We can't make any assumptions about what level of headings the readme uses. const tabs = Array(level - lowestLevel).fill(config.tab).join(""); return `${tabs}* [${title}](${titleLink})`; }).join(config.lineBreak)}`; } /** * Creates the authors template. * @param contributors * @param config */ function contributorsTemplate({ contributors, config }) { const { contributorsPerRow } = config; const imageSize = 100; // Split the contributors into multiple arrays (one for each row) const rows = splitArrayIntoArrays(contributors, contributorsPerRow); return `## Contributors ${rows.map(row => { // Compile the rows const imgs = row.map(({ img, url, name }) => img != null ? `[<img alt="${name}" src="${img}" width="${imageSize}">](${url})` : " "); const names = row.map(({ url, email, name }) => `[${name}](${url})`); const emails = row.map(({ url, email }) => email != null ? `[${email}](mailto:${email})` : ""); // Find the maximum amount of info lines for the row! const maxInfoLinesCount = row.reduce((acc, { info }) => info != null ? Math.max(acc, info.length) : acc, 0); // For each line we go through the row and find the correct info const infos = Array(maxInfoLinesCount).fill(0).map((_, i) => { return row.map(({ info }) => info != null && i < info.length ? info[i] : ""); }); const content = [imgs, names, emails, ...infos]; return tableTemplate({ rows: content, config, centered: true }); }).join(config.lineBreak)}`; } /** * Generates documentation for a glob. * @param glob * @param config */ async function documentationTemplate({ glob: globString, config }) { // Resolve all paths const paths = await new Promise((res, rej) => { glob(globString, (err, paths) => { err != null ? rej(err) : res(paths); }); }); // Read all files const files = paths.map(path => ({ fileName: path, text: fs.readFileSync(path, { encoding: "utf8" }) })); // Analyze the text const { results, program } = analyzeText(files); // Turn the result into markdown const format = "markdown"; return transformAnalyzerResult(format, results, program, config.documentationConfig); } /** * Creates a simple template. * @param name * @param template * @param params */ function simpleTemplateGenerator({ name, template, params }) { return { name, regex: placeholderRegexCallback(`template:${name}`), template: () => template, params }; } /** * Loads markdown. */ const generateLoad = { name: "load", regex: placeholderRegexCallback("load:(.+?\.md)"), template: ({ content, generateReadme, configPath, config }) => { // Recursively generate the readme for all the files that are being loaded, but only add the load generator // since all of the generators should only run once. return generateReadme({ config, blueprint: content, configPath, generators: [generateLoad] }); }, params: ({ config, match, generateReadme, configPath }) => { const absolutePath = resolve(match[2]); // Check if file exists if (!fileExists(absolutePath)) { return { error: `the file "${absolutePath}" doesn't exist.` }; } // Read the file const content = readFile(absolutePath) || ""; return { content, generateReadme, configPath, config }; } }; /** * Generates a logo. */ const generateLogo = { name: "logo", regex: placeholderRegexCallback("template:logo"), template: logoTemplate, params: { logo: "logo", src: "logo.src" } }; /** * Generates a title. */ const generateMainTitle = { name: "main-title", regex: placeholderRegexCallback("template:title"), template: mainTitleTemplate, params: { name: "pkg.name" } }; /** * Generates badges. */ const generateBadges = { name: "badges", regex: placeholderRegexCallback("template:badges"), template: badgesTemplate, params: ({ config }) => { const badges = getBadges({ config }); if (badges.length === 0) { return { error: "it could not generate any badges" }; } return { badges, config }; } }; /** * Generates a description. */ const generateDescription = { name: "description", regex: placeholderRegexCallback("template:description"), template: descriptionTemplate, params: { description: "pkg.description", optional: { demo: "demo", text: "text" } } }; /** * Generates a line. */ const generateLine = { name: "line", regex: placeholderRegexCallback("template:line"), template: lineTemplate }; /** * Generates contributors. */ const generateContributors = { name: "contributors", regex: placeholderRegexCallback("template:contributors"), template: contributorsTemplate, params: { contributors: "pkg.contributors" } }; /** * Generates license. */ const generateLicense = { name: "license", regex: placeholderRegexCallback("template:license"), template: licenseTemplate, params: { license: "pkg.license" } }; /** * Generates the titles. */ const generateTitle = { name: "title", regex: () => /^([#]{1,2}) (.*)$/gm, template: titleTemplate, params: ({ config, match }) => { const hashes = match[1]; const title = match[2]; return { title, level: hashes.length, config }; } }; /** * Generates the interpolation. */ const generateInterpolate = { name: "interpolate", regex: placeholderRegexCallback(`[^\\s:]*`), template: ({ config, text }) => { let value = getValue(config, text); if (value == null) return text; // Transform objects into array so they can be transformed into lists if (isObject(value)) { value = Object.entries(value).map(([k, v]) => `**${k}**: ${v}`); } // Transform arrays if (Array.isArray(value)) { // Turn 2D arrays into tables if (value.length > 0 && Array.isArray(value[0])) { value = tableTemplate({ rows: value, config: config }); } // Turn 1D arrays into bullets else { value = bulletsTemplate({ bullets: value, config: config }); } } return value || text; }, params: ({ config, match }) => { const text = match[1]; return { config, text: text.trim() }; } }; /** * Generates the toc. */ const generateToc = { name: "toc", regex: placeholderRegexCallback("template:toc"), template: tocTemplate, params: ({ config, blueprint }) => { const titles = blueprint.match(/^[#]{1,6} .*$/gm); if (titles == null) { return { error: "it could not find any titles" }; } return { titles, config }; } }; /** * Generates documentation. */ const generateDocumentation = { name: "documentation", regex: placeholderRegexCallback("doc:(.+?)"), template: documentationTemplate, params: ({ match, config }) => { const glob = match[2]; if (glob.length === 0) { return { error: "it could not find the glob" }; } return { glob, config }; } }; const defaultGenerators = [// Pre process generateLoad, // Process generateLogo, generateMainTitle, generateBadges, generateDescription, generateLine, generateContributors, generateLicense, generateDocumentation, // Post process generateTitle, generateInterpolate, generateToc]; const defaultDocumentationConfig = { visibility: "public", markdown: { titleLevel: 2 } }; /** * Default name of the blueprint configuration. */ const defaultConfigName = `blueprint.json`; /** * Default configuration. */ const defaultConfig = { lineBreak: "\r\n", tab: "\t", input: "blueprint.md", package: "package.json", output: "README.md", checkLinks: false, placeholder: ["{{", "}}"], dry: false, silent: false, help: false, line: LineColor.COLORED, templates: [], contributorsPerRow: 6, headingPrefix: { 1: "➤ ", 2: "➤ " }, pkg: {}, documentationConfig: defaultDocumentationConfig }; /** * Converts a value to a boolean. * @param v */ function booleanTransformer(v) { return v !== "false"; } /** * Transforms the value based on the type. * @param type * @param value */ function transformValue({ type, value }) { if (value == null) { return null; } if (type === Boolean) { return booleanTransformer(value); } return value; } /** * Constructs a config using the extend path if one is defined. * @param config */ function extendConfigWithExtendConfig({ config }) { // Recursively load the extend path. const extend = config.extend; if (extend != null) { const extendConfig = loadConfig(extend); // Make sure the config exists if (extendConfig == null) { throw new Error(`Could not load extend config at path "${extend}". Make sure the file exists.`); } // Merge the extend with the config. The config object takes precedence. config = { ...extendConfigWithExtendConfig({ config: extendConfig }), ...config }; } return config; } /** * Constructs a configuration object with defaults. * @param pkg * @param options * @param config */ function extendConfigWithDefaults({ options, config }) { config = { ...config }; for (let [key, value] of Object.entries(defaultConfig)) { value = getValue(options, key) || getValue(config, key) || value; setValue(config, key, value); } return config; } /** * Generates a readme. * @param pkg * @param blueprint * @param configPath * @param generators */ async function generateReadme({ config, blueprint, configPath, generators }) { const { silent } = config; // Go through all of the generators and replace with the template let defaultArgs = { config, configPath, generateReadme }; for (const generator of generators) { const regex = generator.regex({ ...defaultArgs, blueprint }); let match = null; do { match = regex.exec(blueprint); if (match != null) { let markdown = match[0]; let errorReason; let params = null; // If the params are required we extract them from the package. if (generator.params != null) { if (isFunction(generator.params)) { // Extract the params using the function params = generator.params({ ...defaultArgs, blueprint, match }); // Validate the params if (params == null || params.error) { errorReason = (params || {}).error || `the params couldn't not be generated`; } } else { // Get the required and optional parameters const optionalParams = generator.params["optional"] || []; const requiredParams = { ...generator.params }; delete requiredParams["optional"]; // Validate the params if (!validateObject({ obj: config, requiredFields: Object.values(requiredParams) })) { errorReason = `"${configPath}" is missing one or more of the keys "${Object.values(requiredParams).join(", ")}"`; } else { params = extractValues({ map: { ...optionalParams, ...requiredParams }, obj: config }); } } } // Use the template if no errors occurred if (errorReason == null) { markdown = await generator.template({ ...defaultArgs, blueprint, ...params }); } else { if (!silent) { console.log(yellow(`[readme] - The readme generator "${generator.name}" matched "${match[0]}" but was skipped because ${errorReason}.`)); } } // Replace the match with the new markdown const start = match.index; const end = start + match[0].length; blueprint = replaceInString(blueprint, markdown, { start, end }); // Change the regex pointer so we dont parse the newly added content again regex.lastIndex = start + markdown.length; } } while (match != null); } return blueprint; } /** * Generates the readme. */ async function generate({ config, configPath, generators }) { const { dry, silent, templates, output } = config; // Grab blueprint let blueprint = ""; if (Array.isArray(config.input)) { blueprint = config.input.join(config.lineBreak); } else { const blueprintPath = resolve(config.input); if (!fileExists(blueprintPath)) { console.log(red(`[readme] - Could not find the blueprint file "${blueprintPath}". Make sure to provide a valid path as either the user arguments --readme.input or in the "input" field in the "${configPath}" file.`)); return; } blueprint = readFile(blueprintPath) || ""; } // Grab templates if (templates != null) { const simpleTemplateGenerators = templates.map(simpleTemplateGenerator); // Append the simple generators after the loading generator generators.splice(1, 0, ...simpleTemplateGenerators); } // Generate the readme let readme = await generateReadme({ config, blueprint, configPath, generators }); // Add warning const warning = `<!-- ⚠️ This README has been generated from the file(s) "${Array.isArray(config.input) ? config.input.join(", ") : config.input}" ⚠️-->`; readme = `${warning}${readme}`; // Check broken links if (config.checkLinks) { await checkLinksAliveness(readme); } // Write the file if (!dry) { try { await writeFile({ target: output, content: readme }); // Print the success messsage if not silent if (!silent) { console.log(green(`[readme] - A readme file was successfully generated at "${output}".`)); } } catch (err) { console.log(red(`[readme] - Could not generate readme at "${output}"`), err); } } else { console.log(green(`[readme] - Created the following readme but did not write it to any files".`), green(readme)); } } /** * Runs the readme command. * @param options */ async function generateCommand(options) { const configPath = resolve(options["config"] || options["c"] || defaultConfigName); let config = loadConfig(configPath) || defaultConfig; config = extendConfigWithExtendConfig({ config }); config = extendConfigWithDefaults({ config, options }); // Extend the config with the package object config.pkg = { ...(loadPackage(config.package) || {}), ...config.pkg }; await generate({ config, configPath, generators: defaultGenerators }); } var name = "@appnest/readme"; var version = "1.2.7"; var description = "Automatically generate a beautiful best-practice README file based on the contents of your repository"; var license = "MIT"; var module = "index.esm.js"; var main = "index.cjs.js"; var types = "index.d.ts"; var author = "Appnest"; var scripts = { prebuild: "node prebuild.js", "b:lib": "rollup -c && rollup -c=rollup-cli.config.js", "b:lib:prebuild": "npm run prebuild && npm run b:lib", readme: "npm run b:lib && npm run generate:readme", "generate:readme": "node dist/cli.cjs.js generate", publish: "cd dist && npm publish --access=public && cd ..", "git:add:commit:push": "git add . && git commit --no-edit --amend --no-verify && git push", "bump:patch": "npm version patch && npm run git:add:commit:push", "bump:minor": "npm version minor && npm run git:add:commit:push", "bump:major": "npm version major && npm run git:add:commit:push", "publish:patch": "npm run bump:patch && npm run b:lib:prebuild && npm run publish", "publish:minor": "npm run bump:minor && npm run b:lib:prebuild && npm run publish", "publish:major": "npm run bump:major && npm run b:lib:prebuild && npm run publish", "push:readme": "npm run readme && git add . && git commit -m 'New readme' && git push", ncu: "ncu -u -a && npm update && npm install" }; var bugs = { url: "https://github.com/andreasbm/readme/issues" }; var homepage = "https://github.com/andreasbm/readme#readme"; var repository = { type: "git", url: "git+https://github.com/andreasbm/readme.git" }; var keywords = ["opensource", "project", "readme", "template", "boilerplate", "nodejs", "maintaining", "generator"]; var contributors = [{ name: "Andreas Mehlsen", url: "https://twitter.com/andreasmehlsen", img: "https://avatars1.githubusercontent.com/u/6267397?s=460&v=4", info: ["🔥"] }, { name: "You?", img: "https://joeschmoe.io/api/v1/random", url: "https://github.com/andreasbm/readme/blob/master/CONTRIBUTING.md" }]; var bin = { readme: "cli.cjs.js" }; var dependencies = { "@types/glob": "^7.1.1", "check-links": "^1.1.8", colors: "^1.4.0", commander: "^5.0.0", "fs-extra": "^9.0.0", glob: "^7.1.6", path: "^0.12.7", "web-component-analyzer": "1.0.3" }; var devDependencies = { "@types/fs-extra": "^8.1.0", "@wessberg/rollup-plugin-ts": "^1.2.21", rimraf: "^3.0.2", rollup: "^2.1.0", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-json": "^4.0.0", "rollup-plugin-node-resolve": "^5.2.0" }; var pkg = { name: name, version: version, description: description, license: license, module: module, main: main, types: types, author: author, scripts: scripts, bugs: bugs, homepage: homepage, repository: repository, keywords: keywords, contributors: contributors, bin: bin, dependencies: dependencies, devDependencies: devDependencies }; /** * Runs the cli. * @param argv */ async function run(argv) { version$1(pkg.version); command(`check-links`).description(`Checks all links for aliveness.`).option(`--i, --input <string>`, `Path of the file that needs to be checked. Defaults to '${defaultConfig.output}'`, defaultConfig.output).action(cmd => { checkLinksCommand(cmd.opts()).then(); }); command(`generate`).description(`Generates a README file.`).option(`-c --config <path>`, `Path of the configuration file. Defaults to '${defaultConfigName}'.`).option(`-p --package <path>`, `Path of the package file. Defaults to '${defaultConfig.package}'.`).option(`-o --output <path>`, `Path of the generated README file. Defaults to '${defaultConfig.output}'.`).option(`-i --input <path>`, `Path of the blueprint. Defaults to '${defaultConfig.input}'.`).option(`-e --extend <path>`, `Path to another configuration object that should be extended.`).option(`-d --dry`, `Whether the command should run as dry. If dry, the output file is not generated but outputted to the console instead.`).option(`--badges <list>`, `Badges. Used for the 'badges' template.`).option(`--text <string>`, `Text describing your project. Used for the 'description' template.`).option(`--demo <string>`, `Demo url for your project. Used for the 'description' template.`).option(`--lineBreak <string>`, `The linebreak used in the generation of the README file. Defaults to '\\r\\n'`).option(`--tab <string>`, `The tab used in the generation of the README file. Defaults to '\\t'`).option(`--placeholder <list>`, `The placeholder syntax used when looking for templates in the blueprint. Defaults to '\["\{\{", "\}\}"\]`).option(`--line <string>`, `The line style of the titles. Can also be an URL. Defaults to 'colored'.`).option(`--templates <list>`, `User created templates.`).option(`--silent`, `Whether the console output from the command should be silent.`).option(`--headingPrefix <object>`, `The prefix of the header tags. Defaults to '\{1: "", 2: ""\}'`).option(`--logo <object>`, `The logo information. Used for the 'logo' template.`).option(`--contributorsPerRow <integer>`, `The amount of contributors pr row when using the 'contributors' template. Defaults to '${defaultConfig.contributorsPerRow}'`).option(`--documentationConfig <object>`, `Configuration object for automatic documentation template.`).option(`--checkLinks`, `Checks all links for aliveness after the README file has been generated.`).option(`--pkg.name <string>`, `Contributors of the project. Used for the 'contributors' template.`).option(`--pkg.contributors <list>`, `Contributors of the project. Used for the 'contributors' template.`).option(`--pkg.license <license>`, `License kind. Used for the 'license' template.`).action(cmd => { generateCommand(cmd.opts()).then(); }); // Do some error handling const userArgs = argv.slice(2); if (userArgs.length === 0) { help(); } // Handle unknown commands on("command:*", () => { console.error(`Invalid command: ${userArgs.join(" ")}\nSee --help for a list of available commands.`); process.exit(1); }); // Parse the input parse(argv); } export { LineColor, URL_PATTERN, badgesTemplate, bulletsTemplate, checkLinksAliveness, checkLinksCommand, contributorsTemplate, defaultConfig, defaultConfigName, defaultDocumentationConfig, defaultGenerators, demoTemplate, descriptionTemplate, documentationTemplate, escapeRegex, extendConfigWithDefaults, extendConfigWithExtendConfig, extractValues, fileExists, generate, generateBadges, generateCommand, generateContributors, generateDescription, generateDocumentation, generateInterpolate, generateLicense, generateLine, generateLoad, generateLogo, generateMainTitle, generateReadme, generateTitle, generateToc, getBadges, getCleanTitle, getLicenseUrl, getLinks, getTitle, getTitleLink, getValue, githubBadges, hasKey, isFunction, isObject, isValidURL, licenseTemplate, lineTemplate, loadConfig, loadPackage, logoTemplate, mainTitleTemplate, npmBadges, placeholderRegexCallback, readFile, readJSONFile, replaceInString, run, setValue, simpleTemplateGenerator, splitArrayIntoArrays, tableTemplate, titleTemplate, tocTemplate, validateObject, webcomponentsBadges, writeFile }; //# sourceMappingURL=index.esm.js.map