myst-cli
Version:
Command line tools for MyST
102 lines (101 loc) • 4.66 kB
JavaScript
import { access, readdir } from 'node:fs/promises';
import { RuleId } from 'myst-common';
import { computeHash, hashAndCopyStaticFile, isUrl } from 'myst-cli-utils';
import { selectAll } from 'unist-util-select';
import path from 'node:path';
import { addWarningForFile } from '../utils/addWarningForFile.js';
import { fetchRemoteAsset } from '../utils/fetchRemoteAsset.js';
import { getSourceFolder } from './links.js';
import { resolveOutputPath } from './images.js';
/**
* Transform to resolve and stash static assets for anywidgets.
* This is temporary — the problem of pulling out assets is generaliseable.
*
* Note — the `resourceFolder` is what we include in the AST. If we write to `/public/file.png`,
* and the content server serves `/public` under `/`, then the `writeFolder` is `/public` and
* the `resourceFolder` is `/`
*
* @param session session object
* @param tree document AST
* @param filePath path of source document
* @param writeFolder path of folder to write to
* @param resourceFolder alternative representation of writeFolder that is written to AST
*/
export async function transformWidgetStaticAssetsToDisk(session, tree, filePath, writeFolder, resourceFolder) {
for (const widgetNode of selectAll('anywidget', tree)) {
for (const [attr, ext] of [
['esm', 'mjs'],
['css', 'css'],
]) {
const attrPath = widgetNode[attr];
if (attrPath === undefined) {
continue;
}
const attrSourceFolder = getSourceFolder(attrPath, filePath, session.sourcePath());
const attrLocalPath = path.join(attrSourceFolder, attrPath);
// File name of the form `name.ext`, a single path component
let fileName;
if (isUrl(attrPath)) {
const stem = computeHash(attrPath);
// Check whether file with stem exists (but unknown extension)
let existingName;
try {
const entries = await readdir(writeFolder);
existingName = entries.find((f) => path.parse(f).name === stem);
}
catch (err) {
const code = err.code;
if (code !== 'ENOENT')
throw err;
}
if (existingName !== undefined) {
session.log.debug(`Cached asset found for '${attr}' (${attrPath})...`);
fileName = existingName;
}
else {
try {
const { name } = await fetchRemoteAsset(session, attrPath, writeFolder, stem, {
extension: ext,
});
fileName = name;
session.log.debug(`Fetching asset for '${attr}' (${attrPath})...\n -> saving to: ${fileName}`);
}
catch (error) {
session.log.debug(`\n\n${error.stack}\n\n`);
addWarningForFile(session, fileName, `Error saving asset for '${attr}' "${attrPath}": ${error.message}`, 'error');
continue;
}
}
}
else {
try {
await access(attrLocalPath);
}
catch {
const message = `Cannot find asset for '${attr}' "${attrPath}" in ${attrSourceFolder}`;
addWarningForFile(session, filePath, message, 'error', {
position: widgetNode.position,
// TODO: add "asset exists" rule?
});
continue;
}
// non-url local image paths relative to the config.section.path
if (path.resolve(path.dirname(attrLocalPath)) === path.resolve(writeFolder)) {
// If file is already in write folder, don't hash/copy
fileName = path.basename(attrLocalPath);
}
else {
fileName = hashAndCopyStaticFile(session, attrLocalPath, writeFolder, (m) => {
addWarningForFile(session, filePath, m, 'error', { ruleId: RuleId.imageCopied });
});
}
if (!fileName)
continue;
}
// Update mdast with new file name
if (fileName !== undefined) {
widgetNode[attr] = resolveOutputPath(fileName, writeFolder, resourceFolder);
}
}
}
}