@curvenote/cli
Version:
CLI Client library for Curvenote
132 lines (131 loc) • 6.21 kB
JavaScript
import path from 'node:path';
import YAML from 'js-yaml';
import { prepareToWrite } from 'myst-cli';
import { writeFileToFolder } from 'myst-cli-utils';
import { KINDS, oxaLink } from '@curvenote/blocks';
import { fillPageFrontmatter } from 'myst-frontmatter';
import { createId, toMyst } from '@curvenote/schema';
import { pageFrontmatterFromDTOAndThumbnail, projectFrontmatterFromDTO, saveAffiliations, } from '../../frontmatter.js';
import { Block, Project, Version } from '../../models.js';
import { resolvePath } from '../../utils/index.js';
import { remoteExportWrapper } from './utils/remoteExportWrapper.js';
import { getChildren } from './utils/getChildren.js';
import { localizationOptions } from './utils/localizationOptions.js';
import { walkArticle } from './utils/walkArticle.js';
import { writeBibtex } from './utils/writeBibtex.js';
import { writeImagesToFiles } from './utils/writeImagesToFiles.js';
/**
* Pull content from a version of kind Output, cache in mdast snippets, and return {mdast} directive
*
* Note: the output data cached here is directly from the API; it still needs to be minified in
* the transformOutputs transform.
*
*/
async function createOutputSnippet(session, version, name, mdastSnippets) {
const response = await session.fetch(version.data.links.download);
if (!response.ok)
return '';
const outputData = (await response.json());
const snippetId = `${name}#${createId()}`;
mdastSnippets[snippetId] = {
type: 'output',
data: outputData,
};
return `\`\`\`{mdast} ${snippetId}\n\`\`\``;
}
export async function articleToMarkdown(session, versionId, opts) {
var _a, _b, _c;
const [block, version] = await Promise.all([
new Block(session, versionId).get(),
new Version(session, versionId).get(),
getChildren(session, versionId),
]);
const { data } = version;
if (data.kind !== KINDS.Article)
throw new Error('Not an article');
const article = await walkArticle(session, data);
const imageFilenames = await writeImagesToFiles(session, article.images, {
buildPath: opts === null || opts === void 0 ? void 0 : opts.path,
basePath: (_a = opts === null || opts === void 0 ? void 0 : opts.images) !== null && _a !== void 0 ? _a : 'images',
simple: true,
});
const localization = localizationOptions(session, imageFilenames, article.references);
const mdastName = `${opts.filename.replace(/\.md$/, '')}.mdast.json`;
const articleMdastSnippets = {};
const content = await Promise.all(article.children.map(async (child) => {
var _a, _b;
if (!child.version)
return '';
const blockData = {
oxa: oxaLink('', child.version.id),
tags: ((_a = child.block) === null || _a === void 0 ? void 0 : _a.data.tags) || [],
part: ((_b = child.block) === null || _b === void 0 ? void 0 : _b.data.part) || undefined,
};
let md = '';
let mdastSnippets = {};
if (opts.keepOutputs && child.version.data.kind === KINDS.Output) {
// Reprocess output here, ignoring Output state from walkArticle
md = await createOutputSnippet(session, child.version, mdastName, mdastSnippets);
}
else if (child.state) {
const myst = toMyst(child.state.doc, {
...localization,
renderers: { iframe: 'myst' },
createMdastImportId() {
return `${mdastName}#${createId()}`;
},
});
md = myst.content;
mdastSnippets = myst.mdastSnippets;
}
if (Object.keys(mdastSnippets).length) {
Object.assign(articleMdastSnippets, mdastSnippets);
}
return `+++ ${JSON.stringify(blockData)}\n\n${md}`;
}));
const project = await new Project(session, block.id.project).get();
saveAffiliations(session, project.data);
let frontmatter = await pageFrontmatterFromDTOAndThumbnail(session, resolvePath(opts.path, opts.filename), block.data, version.data.date);
const validationOpts = {
property: 'frontmatter',
file: opts.filename,
messages: {},
errorLogFn: (message) => {
session.log.error(`Validation error: ${message}`);
},
warningLogFn: (message) => {
session.log.warn(`Validation: ${message}`);
},
};
if (!opts.ignoreProjectFrontmatter) {
const projectFrontmatter = projectFrontmatterFromDTO(session, project.data);
frontmatter = fillPageFrontmatter(frontmatter, projectFrontmatter, validationOpts);
}
const metadata = YAML.dump(prepareToWrite(frontmatter));
let titleString = `---\n${metadata}---\n\n`;
if (!opts.titleOnlyInFrontmatter) {
// TODO: Remove the title when Jupyter Book allows title to be defined in the yaml.
// https://github.com/executablebooks/MyST-Parser/pull/492
titleString += `# ${block.data.title}\n\n`;
}
let file = titleString + content.join('\n\n');
if (opts.renderReferences && Object.keys(article.references).length > 0) {
file += '\n\n### References\n\n```{bibliography}\n:filter: docname in docnames\n```';
}
file += '\n\n';
writeFileToFolder(resolvePath(opts.path, opts.filename), file);
if (Object.keys(articleMdastSnippets).length) {
const normalizedSnippets = Object.fromEntries(Object.entries(articleMdastSnippets).map(([k, v]) => [k.split('#')[1], v]));
writeFileToFolder(resolvePath(opts.path, mdastName), JSON.stringify(normalizedSnippets, null, 2));
}
if ((_b = opts.writeBibtex) !== null && _b !== void 0 ? _b : true) {
session.log.debug('Writing bib file...');
// Write out the references
await writeBibtex(session, article.references, (_c = opts === null || opts === void 0 ? void 0 : opts.bibtex) !== null && _c !== void 0 ? _c : 'main.bib', {
path: path.join(opts.path || '', path.dirname(opts.filename)),
alwaysWriteFile: false,
});
}
return article;
}
export const oxaLinkToMarkdown = remoteExportWrapper(articleToMarkdown);