UNPKG

myst-cli

Version:
207 lines (206 loc) 8.55 kB
import path from 'node:path'; import { createStore } from 'redux'; import { chalkLogger, LogLevel } from 'myst-cli-utils'; import latestVersion from 'latest-version'; import boxen from 'boxen'; import chalk from 'chalk'; import { HttpsProxyAgent } from 'https-proxy-agent'; import pLimit from 'p-limit'; import { findCurrentProjectAndLoad, findCurrentSiteAndLoad, reloadAllConfigsForCurrentSite, } from '../config.js'; import { loadPlugins } from '../plugins.js'; import { selectors } from '../store/index.js'; import { rootReducer } from '../store/reducers.js'; import version from '../version.js'; import { isWhiteLabelled } from '../utils/whiteLabelling.js'; import { KernelManager, ServerConnection, SessionManager } from '@jupyterlab/services'; import { launchJupyterServer } from 'myst-execute'; import { default as nodeFetch, Headers, Request, Response } from 'node-fetch'; // fetch polyfill for node<18 if (!globalThis.fetch) { globalThis.fetch = nodeFetch; globalThis.Headers = Headers; globalThis.Request = Request; globalThis.Response = Response; } const CONFIG_FILES = ['myst.yml']; const API_URL = 'https://api.mystmd.org'; const NPM_COMMAND = 'npm i -g mystmd@latest'; const PIP_COMMAND = 'pip install -U mystmd'; const LOCALHOSTS = ['localhost', '127.0.0.1', '::1']; function socialLink({ twitter, bsky }) { if (bsky) { return `Follow ${chalk.yellowBright(`@${bsky}`)} for updates!\nhttps://bsky.app/profile/${bsky}`; } if (twitter) { return `Follow ${chalk.yellowBright(`@${twitter}`)} for updates!\nhttps://x.com/${twitter}`; } return ''; } export function logUpdateAvailable({ current, latest, upgradeCommand, twitter, bsky, }) { return boxen(`Update available! ${chalk.dim(`v${current}`)} ≫ ${chalk.green.bold(`v${latest}`)}\n\nRun \`${chalk.cyanBright.bold(upgradeCommand)}\` to update.\n\n${socialLink({ bsky, twitter })}`, { padding: 1, margin: 1, borderColor: 'green', borderStyle: 'round', textAlignment: 'center', }); } export class Session { get log() { return this.$logger; } constructor(opts = {}) { var _a, _b; this._shownUpgrade = false; this._clones = []; this.API_URL = API_URL; this.configFiles = CONFIG_FILES; this.$logger = (_a = opts.logger) !== null && _a !== void 0 ? _a : chalkLogger(LogLevel.info, process.cwd()); this.doiLimiter = (_b = opts.doiLimiter) !== null && _b !== void 0 ? _b : pLimit(3); const proxyUrl = process.env.HTTPS_PROXY; if (proxyUrl) this.proxyAgent = new HttpsProxyAgent(proxyUrl); this.store = createStore(rootReducer); // Allow the latest version to be loaded latestVersion('mystmd') .then((latest) => { this._latestVersion = latest; }) .catch(() => null); } showUpgradeNotice() { if (this._shownUpgrade || !this._latestVersion || version === this._latestVersion || isWhiteLabelled()) return; this.log.info(logUpdateAvailable({ current: version, latest: this._latestVersion, upgradeCommand: process.env.MYST_LANG === 'PYTHON' ? PIP_COMMAND : NPM_COMMAND, bsky: 'mystmd.org', })); this._shownUpgrade = true; } async reload() { await findCurrentProjectAndLoad(this, '.'); await findCurrentSiteAndLoad(this, '.'); if (selectors.selectCurrentSitePath(this.store.getState())) { await reloadAllConfigsForCurrentSite(this); } return this; } async fetch(url, init) { var _a; const urlOnly = new URL((_a = url.url) !== null && _a !== void 0 ? _a : url); this.log.debug(`Fetching: ${urlOnly}`); if (this.proxyAgent && !LOCALHOSTS.includes(urlOnly.hostname)) { if (!init) init = {}; init = { agent: this.proxyAgent, ...init }; this.log.debug(`Using HTTPS proxy: ${this.proxyAgent.proxy}`); } const logData = { url: urlOnly, done: false }; setTimeout(() => { if (!logData.done) this.log.info(`⏳ Waiting for response from ${url}`); }, 5000); const resp = await nodeFetch(url, init); logData.done = true; return resp; } async loadPlugins(plugins) { this.plugins = await loadPlugins(this, plugins); return this.plugins; } sourcePath() { var _a; const state = this.store.getState(); const sitePath = selectors.selectCurrentSitePath(state); const projectPath = selectors.selectCurrentProjectPath(state); const root = (_a = sitePath !== null && sitePath !== void 0 ? sitePath : projectPath) !== null && _a !== void 0 ? _a : '.'; return path.resolve(root); } buildPath() { return path.join(this.sourcePath(), '_build'); } sitePath() { return path.join(this.buildPath(), 'site'); } contentPath() { return path.join(this.sitePath(), 'content'); } publicPath() { return path.join(this.sitePath(), 'public'); } async clone() { const cloneSession = new Session({ logger: this.log, doiLimiter: this.doiLimiter }); await cloneSession.reload(); // TODO: clean this up through better state handling cloneSession._jupyterSessionManagerPromise = this._jupyterSessionManagerPromise; this._clones.push(cloneSession); return cloneSession; } getAllWarnings(ruleId) { const stringWarnings = []; const warnings = []; [this, ...this._clones].forEach((session) => { const sessionWarnings = selectors.selectFileWarningsByRule(session.store.getState(), ruleId); sessionWarnings.forEach((warning) => { const stringWarning = JSON.stringify(Object.entries(warning).sort()); if (!stringWarnings.includes(stringWarning)) { stringWarnings.push(stringWarning); warnings.push(warning); } }); }); return warnings; } jupyterSessionManager() { if (this._jupyterSessionManagerPromise === undefined) { this._jupyterSessionManagerPromise = this.createJupyterSessionManager(); } return this._jupyterSessionManagerPromise; } async createJupyterSessionManager() { try { let partialServerSettings; // Load from environment if (process.env.JUPYTER_BASE_URL !== undefined) { partialServerSettings = { baseUrl: process.env.JUPYTER_BASE_URL, token: process.env.JUPYTER_TOKEN, }; } else { // Note: To use an existing Jupyter server use `findExistingJupyterServer`, see #1716 this.log.debug(`Launching jupyter server on ${this.sourcePath()}`); // Create and load new server partialServerSettings = await launchJupyterServer(this.sourcePath(), this.log); } const serverSettings = ServerConnection.makeSettings(partialServerSettings); const kernelManager = new KernelManager({ serverSettings }); const manager = new SessionManager({ kernelManager, serverSettings }); // Tie the lifetime of the kernelManager and (potential) spawned server to the manager manager.disposed.connect(() => { var _a; kernelManager.dispose(); (_a = partialServerSettings === null || partialServerSettings === void 0 ? void 0 : partialServerSettings.dispose) === null || _a === void 0 ? void 0 : _a.call(partialServerSettings); }); return manager; } catch (err) { this.log.error('Unable to instantiate connection to Jupyter Server', err); return undefined; } } dispose() { this._clones.forEach((session) => { session.dispose(); }); if (this._jupyterSessionManagerPromise) { this._jupyterSessionManagerPromise.then((manager) => { var _a; return (_a = manager === null || manager === void 0 ? void 0 : manager.dispose) === null || _a === void 0 ? void 0 : _a.call(manager); }); this._jupyterSessionManagerPromise = undefined; } } }