@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
JavaScript
"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