myst-cli
Version:
Command line tools for MyST
118 lines (117 loc) • 5.23 kB
JavaScript
import { selectAll } from 'unist-util-select';
import { RuleId, fileWarn, plural, selectMdastNodes } from 'myst-common';
import { computeHash, tic } from 'myst-cli-utils';
import { addChildrenFromTargetNode } from 'myst-transforms';
import { loadFromCache, writeToCache } from '../session/cache.js';
export const XREF_MAX_AGE = 1; // in days
function mystDataFilename(dataUrl) {
return `myst-${computeHash(dataUrl)}.json`;
}
async function fetchMystData(session, dataUrl, urlSource, vfile) {
const onError = (note) => {
var _a;
fileWarn(vfile, `Unable to resolve link text from external MyST reference: ${(_a = urlSource !== null && urlSource !== void 0 ? urlSource : dataUrl) !== null && _a !== void 0 ? _a : ''}`, {
ruleId: RuleId.mystLinkValid,
note,
});
};
if (!dataUrl) {
return onError('Data source URL unavailable');
}
const filename = mystDataFilename(dataUrl);
const cacheData = loadFromCache(session, filename, { maxAge: XREF_MAX_AGE });
if (cacheData) {
return JSON.parse(cacheData);
}
let data;
try {
const resp = await session.fetch(dataUrl);
if (!resp.ok) {
return onError('Could not fetch data from URL');
}
data = (await resp.json());
}
catch {
return onError('Could not load fetched data');
// data is unset
}
writeToCache(session, filename, JSON.stringify(data));
return data;
}
export async function fetchMystLinkData(session, node, vfile) {
return fetchMystData(session, node.dataUrl, node.urlSource, vfile);
}
export async function fetchMystXRefData(session, node, vfile) {
let dataUrl;
if (node.remoteBaseUrl && node.dataUrl) {
dataUrl = `${node.remoteBaseUrl}${node.dataUrl}`;
}
return fetchMystData(session, dataUrl, node.urlSource, vfile);
}
export function nodesFromMystXRefData(data, identifier, vfile, opts) {
var _a, _b;
let targetNodes;
[data, ...Object.values((_b = (_a = data.frontmatter) === null || _a === void 0 ? void 0 : _a.parts) !== null && _b !== void 0 ? _b : {})].forEach(({ mdast }) => {
if (!mdast || (targetNodes === null || targetNodes === void 0 ? void 0 : targetNodes.length))
return;
targetNodes = selectMdastNodes(mdast, identifier, opts === null || opts === void 0 ? void 0 : opts.maxNodes).nodes;
});
if (!(targetNodes === null || targetNodes === void 0 ? void 0 : targetNodes.length)) {
fileWarn(vfile, `Unable to resolve content from external MyST reference: ${opts === null || opts === void 0 ? void 0 : opts.urlSource}`, {
ruleId: RuleId.mystLinkValid,
note: `Could not locate identifier ${identifier} in page content`,
});
return;
}
return targetNodes;
}
/**
* Load external MyST project data to update link text for MyST xrefs
*/
export async function transformMystXRefs(session, vfile, mdast, frontmatter) {
const toc = tic();
const nodes = selectAll('link,crossReference', mdast)
.filter((node) => {
// Only handle MyST xrefs
return node.protocol === 'xref:myst' && node.dataUrl;
})
.filter((node) => {
var _a;
// If no link text, load the target to compute children
if (!((_a = node.children) === null || _a === void 0 ? void 0 : _a.length))
return true;
// If `identifier` is present, we may need to update existing children (e.g. enumerator)
return !!node.identifier;
});
if (nodes.length === 0)
return;
session.log.debug(`Updating link text for ${plural('%s external MyST reference(s)', nodes)}`);
let number = 0;
await Promise.all([
...nodes.map(async (node) => {
var _a, _b, _c, _d, _e;
if (!node.identifier) {
// Page references without specific node identifier
const data = await fetchMystLinkData(session, node, vfile);
if (!data)
return;
node.children = [{ type: 'text', value: (_c = (_b = (_a = data.frontmatter) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : data.slug) !== null && _c !== void 0 ? _c : '' }];
}
else {
const data = await fetchMystXRefData(session, node, vfile);
if (!data)
return;
const targets = nodesFromMystXRefData(data, node.identifier, vfile, {
urlSource: node.urlSource,
maxNodes: 1,
});
if (targets === null || targets === void 0 ? void 0 : targets.length) {
addChildrenFromTargetNode(node, targets[0], frontmatter.numbering, vfile, (_e = (_d = frontmatter.numbering) === null || _d === void 0 ? void 0 : _d.title) === null || _e === void 0 ? void 0 : _e.offset);
}
}
number += 1;
}),
]);
const denominator = number === nodes.length ? '' : `/${nodes.length}`;
session.log.info(toc(`🪄 Updated link text for ${plural(`%s${denominator} external MyST reference(s)`, number)} in %s seconds`));
}