UNPKG

myst-cli

Version:
102 lines (101 loc) 4.66 kB
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); } } } }