@curvenote/cli
Version:
CLI Client library for Curvenote
126 lines (125 loc) • 5.67 kB
JavaScript
import fs from 'node:fs';
import pLimit from 'p-limit';
import { join, dirname, basename, extname } from 'node:path';
import { LogLevel, tic, isDirectory } from 'myst-cli-utils';
import { config, getRawFrontmatterFromFile, loadConfig, selectors, writeConfigs } from 'myst-cli';
import { projectFrontmatterFromDTO, saveAffiliations } from '../../frontmatter.js';
import { Project } from '../../models.js';
import { confirmOrExit } from '../../utils/index.js';
import { processOption, projectLogString } from '../utils.js';
import { projectToJupyterBook } from './jupyter-book/project.js';
import { oxaLinkToMarkdown } from './markdown.js';
import { oxaLinkToNotebook } from './notebook.js';
function logWithLevel(session, msg, level) {
if (level === LogLevel.info) {
session.log.info(msg);
}
else {
session.log.debug(msg);
}
}
/**
* Pull content for a project on a path
*
* Errors if project config does not exist or no remote project url is specified.
*/
export async function pullProject(session, path, opts) {
const state = session.store.getState();
const projectConfig = selectors.selectLocalProjectConfig(state, path);
if (!projectConfig)
throw Error(`Cannot pull project from ${path}: no project config`);
if (!projectConfig.remote)
throw Error(`Cannot pull project from ${path}: no remote project url`);
const project = await new Project(session, projectConfig.remote).get();
saveAffiliations(session, project.data);
const newFrontmatter = projectFrontmatterFromDTO(session, project.data);
session.store.dispatch(config.actions.receiveProjectConfig({ path, ...projectConfig, ...newFrontmatter }));
await writeConfigs(session, path);
const toc = tic();
logWithLevel(session, `📥 Pulling ${projectLogString(project)} into ${path}`, opts === null || opts === void 0 ? void 0 : opts.level);
await projectToJupyterBook(session, project.id, {
ci: opts === null || opts === void 0 ? void 0 : opts.ci,
path,
writeConfig: false,
createNotebookFrontmatter: true,
titleOnlyInFrontmatter: true,
keepOutputs: true,
// Project frontmatter is kept sepatare in project config, above
ignoreProjectFrontmatter: true,
});
if (fs.existsSync(join(path, '_toc.yml'))) {
logWithLevel(session, toc(`🚀 Pulled ${path} in %s`), opts === null || opts === void 0 ? void 0 : opts.level);
}
}
/**
* Pull content for all projects in the site config
*
* Errors if no site config is loaded in the state.
*/
export async function pullProjects(session, opts) {
const state = session.store.getState();
const siteConfig = selectors.selectCurrentSiteConfig(state);
if (!siteConfig)
throw Error('Cannot pull projects: no site config');
const limit = pLimit(1);
if (siteConfig.projects) {
const projectsToPull = siteConfig.projects.filter((proj) => Boolean(proj.path));
await Promise.all(projectsToPull === null || projectsToPull === void 0 ? void 0 : projectsToPull.map(async (proj) => {
return limit(async () => pullProject(session, proj.path, opts));
}));
}
}
export async function pullDocument(session, file) {
const frontmatter = await getRawFrontmatterFromFile(session, file);
if (!(frontmatter === null || frontmatter === void 0 ? void 0 : frontmatter.oxa)) {
throw new Error(`File ${file} does not have a "oxa" in the frontmatter.`);
}
switch (extname(file)) {
case '.md':
await oxaLinkToMarkdown(session, frontmatter.oxa, basename(file), { path: dirname(file) });
break;
case '.ipynb':
await oxaLinkToNotebook(session, frontmatter.oxa, basename(file), { path: dirname(file) });
break;
default:
throw new Error('Unrecognized extension to pull document.');
}
}
/**
* Pull new project content from curvenote.com
*
* If this is called from a folder with an existing site configuration and
* no other path is specified, all projects included in the site are pulled.
* Otherwise, a single project on the specified path is pulled.
*
* Errors if site config has no projects or if project does not exist
* on specified path.
*/
export async function pull(session, path, opts) {
var _a;
path = path || '.';
const processedOpts = processOption(opts);
if (!fs.existsSync(path)) {
throw new Error(`Invalid path: "${path}", it must be a folder or file accessible from the local directory`);
}
if (!isDirectory(path)) {
await confirmOrExit(`Pulling will overwrite the file "${path}". Are you sure?`, processedOpts);
await pullDocument(session, path);
return;
}
// Site config is loaded on session init
const siteConfig = selectors.selectCurrentSiteConfig(session.store.getState());
if (path === '.' && siteConfig) {
const numProjects = (_a = siteConfig.projects) === null || _a === void 0 ? void 0 : _a.length;
if (!numProjects)
throw new Error('Your site configuration has no projects');
const plural = numProjects > 1 ? 's' : '';
await confirmOrExit(`Pulling will overwrite all content in ${numProjects} project${plural}. Are you sure?`, processedOpts);
await pullProjects(session, { level: LogLevel.info, ...opts });
}
else {
await loadConfig(session, path);
await confirmOrExit(`Pulling will overwrite all content in ${path === '.' ? 'current directory' : path}. Are you sure?`, processedOpts);
await pullProject(session, path, { level: LogLevel.info, ...opts });
}
}