UNPKG

@droppedcode/typedoc-plugin-copy-assets

Version:

This plugin for typedoc copies the assets found in comments to the output assets folder.

218 lines 9.53 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CopyAssets = void 0; const fs = require("fs"); const path = require("path"); const typedoc_1 = require("typedoc"); const copy_assets_options_1 = require("./copy-assets-options"); /** * Copies the assets found in comments to the output assets folder. */ class CopyAssets { constructor() { /** Maps a reflection to a source file it was created from. */ this._reflectionPaths = new Map(); /** Maps an image absolute path to a relative path in the output. */ this._referencePathMaps = new Map(); /** Path that we already used. */ this._usedOutputPaths = new Set(); /** * The pattern used to find references in markdown. */ this._referencePattern = /(\[.*?\]\()(.*?)(\))/g; this._imagePattern = /(!\[.*?\]\()(.*?)(\))/g; this._htmlImgTagPattern = /(<img[^<]*?src=")(.*?)("[^<]*?>)/g; this._options = copy_assets_options_1.defaultOptions; } /** * Create a new RelativeIncludesConverterComponent instance. * @param typedoc The application. */ initialize(typedoc) { this._typedoc = typedoc; typedoc.converter.on(typedoc_1.Converter.EVENT_CREATE_DECLARATION, (c, r, n) => { const filePath = this.getFolderPaths(n, r, c); if (!filePath) return; this._reflectionPaths.set(r, filePath); }); typedoc.renderer.on(typedoc_1.Renderer.EVENT_BEGIN, () => { this._outFolder = this._typedoc?.options.getValue('out'); this._options = this._typedoc?.options.getValue(copy_assets_options_1.optionsKey) ?? copy_assets_options_1.defaultOptions; this._includeList = this._options.include ? this._options.include.map((m) => new RegExp(m)) : undefined; this._excludeList = this._options.exclude ? this._options.exclude.map((m) => new RegExp(m)) : undefined; this._includePathList = this._options.includePath ? this._options.includePath.map((m) => new RegExp(m)) : undefined; this._excludePathList = this._options.excludePath ? this._options.excludePath.map((m) => new RegExp(m)) : undefined; if (!this._outFolder) return; this._outMediaFolder = path.join(this._outFolder, 'assets', 'media'); if (!fs.existsSync(this._outMediaFolder)) { fs.mkdirSync(this._outMediaFolder, { recursive: true }); } }); let currentReflection = undefined; let currentOutputFilePath = undefined; typedoc.renderer.on(typedoc_1.PageEvent.BEGIN, (event) => { currentOutputFilePath = event.url; currentReflection = event.model instanceof typedoc_1.Reflection ? event.model : undefined; }); typedoc.renderer.on(typedoc_1.MarkdownEvent.PARSE, (event) => { if (!currentOutputFilePath) return; if (!currentReflection) return; if (!this._outMediaFolder) return; const folderPaths = this._reflectionPaths.get(currentReflection); if (!folderPaths) return; event.parsedText = this.processText(event.parsedText, folderPaths, currentOutputFilePath); }, undefined, 1 // Do it before the default ); } /** * Get the folder path for the current item. * @param n Node. * @param r Reflection. * @param c Context. * @returns The folder path for the current context item or undefined. */ getFolderPaths(n, r, c) { if (r.parent) { const filePath = this.getNodeFilePath(n) ?? this.getReflectionFilePath(r); return filePath ? [path.dirname(filePath)] : undefined; } else { const result = []; const filePath = this.getReflectionFilePath(r); if (filePath) { result.push(path.dirname(filePath)); } result.push(c.program.getCurrentDirectory()); return result; } } /** * Get the first file the reflection was created from. * @param reflection The reflection. * @returns The file path. */ getReflectionFilePath(reflection) { if (reflection.variant !== 'signature' && reflection.variant !== 'declaration') return; const sourceVariant = reflection; if (!sourceVariant.sources || sourceVariant.sources.length === 0) return; return sourceVariant.sources[0].fileName; } /** * Get the file the node was created from. * @param node The node. * @returns The file path. */ getNodeFilePath(node) { if (!node) return undefined; if ('fileName' in node) return node['fileName']; return this.getNodeFilePath(node.parent); } /** * Process the text collecting the references from it and copying to the out folder. * @param text The text to process. * @param originalFolderPaths Possible paths of the parsed file. * @param outputFilePath Path of the documentation file. * @returns The modified comment. */ processText(text, originalFolderPaths, outputFilePath) { text = text.replace(this._options.onlyImages ? this._imagePattern : this._referencePattern, (_match, prefix, pathGroup, suffix) => this.processMatch(originalFolderPaths, outputFilePath, _match, prefix, pathGroup, suffix, false)); if (this._options.copyHtmlImgTag) { text = text.replace(this._htmlImgTagPattern, (_match, prefix, pathGroup, suffix) => this.processMatch(originalFolderPaths, outputFilePath, _match, prefix, pathGroup, suffix, true)); } return text; } /** * Process a match, replacing it with the new path. * @param originalFolderPaths Possible paths of the parsed file. * @param outputFilePath Path of the documentation file. * @param match Match to process. * @param prefix Prefix of the match. * @param pathGroup Path of of the resource. * @param suffix Suffix of the match. * @param allowNonRelativePaths Allow non relative paths to be processed. * @returns The replacement of the match. */ processMatch(originalFolderPaths, outputFilePath, match, prefix, pathGroup, suffix, allowNonRelativePaths) { if (typeof pathGroup === 'string' && // Check if we allow non relative paths, that it is not using protocol ((allowNonRelativePaths && !pathGroup.includes('://')) || pathGroup.startsWith('./') || pathGroup.startsWith('../')) && (!this._includeList || this._includeList.some((s) => s.test(match))) && (!this._excludeList || !this._excludeList.some((s) => s.test(match))) && (!this._includePathList || this._includePathList.some((s) => s.test(pathGroup))) && (!this._excludePathList || !this._excludePathList.some((s) => s.test(pathGroup)))) { let referencePath; for (const p of originalFolderPaths) { const possiblePath = path.join(p, pathGroup); if (fs.existsSync(possiblePath)) { referencePath = possiblePath; break; } } if (!referencePath) { console.warn(`Missing file on relative paths: ${pathGroup}, paths tried: [${originalFolderPaths.join(', ')}]`); return prefix + pathGroup + suffix; } let newPath = this._referencePathMaps.get(referencePath); if (!newPath) { const ext = path.extname(referencePath); const fileName = path.basename(referencePath, ext); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion newPath = path.join(this._outMediaFolder, fileName + ext); let index = 0; while (this._usedOutputPaths.has(newPath)) { newPath = path.join( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this._outMediaFolder, `${fileName}_${index++}${ext}`); } this._usedOutputPaths.add(newPath); this._referencePathMaps.set(referencePath, newPath); // console.log( // `Copy ${referencePath} => ${newPath}, exists: ${fs.existsSync( // referencePath // )}` // ); fs.copyFileSync(referencePath, newPath); } return (prefix + path .relative( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion path.dirname(path.join(this._outFolder, outputFilePath)), // eslint-disable-next-line @typescript-eslint/no-non-null-assertion newPath) .replace(/\\/g, '/') + suffix); } else { return match; } } } exports.CopyAssets = CopyAssets; //# sourceMappingURL=copy-assets.js.map