Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⏬ Add mechanism for downgrading MyST AST #1734

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions packages/myst-cli/src/process/loadReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ import {
} from '../session/cache.js';
import type { ISession } from '../session/types.js';
import { selectors } from '../store/index.js';
import { XREF_MAX_AGE } from '../transforms/crossReferences.js';
import { XREF_MAX_AGE, mystXRefsCacheFilename } from '../transforms/crossReferences.js';
import { dirname } from 'node:path';

function inventoryCacheFilename(refKind: 'intersphinx' | 'myst', id: string, path: string) {
const hashcontent = `${id}${path}`;
const ext = refKind === 'intersphinx' ? 'inv' : 'json';
return `xrefs-${refKind}-${computeHash(hashcontent)}.${ext}`;
function sphinxInventoryCacheFilename(path: string) {
return `xrefs-intersphinx-${computeHash(path)}.inv`;
}

async function preloadReference(session: ISession, key: string, reference: ExternalReference) {
Expand All @@ -31,11 +29,11 @@ async function preloadReference(session: ISession, key: string, reference: Exter
kind: reference.kind,
};
const toc = tic();
const mystXRefFilename = inventoryCacheFilename('myst', key, reference.url);
const mystXRefFilename = mystXRefsCacheFilename(reference.url);
const mystXRefData = loadFromCache(session, mystXRefFilename, {
maxAge: XREF_MAX_AGE,
});
const intersphinxFilename = inventoryCacheFilename('intersphinx', key, reference.url);
const intersphinxFilename = sphinxInventoryCacheFilename(reference.url);
if ((!ref.kind || ref.kind === 'myst') && !!mystXRefData) {
session.log.debug(`Loading cached inventory file for ${reference.url}: ${mystXRefFilename}`);
const xrefs = JSON.parse(mystXRefData);
Expand Down Expand Up @@ -93,11 +91,7 @@ async function loadReference(
reference.kind = 'myst';
const mystXRefs = (await mystXRefsResp?.json()) as MystXRefs;
session.log.debug(`Saving remote myst xref file to cache: ${reference.url}`);
writeToCache(
session,
inventoryCacheFilename('myst', reference.key, reference.url),
JSON.stringify(mystXRefs),
);
writeToCache(session, mystXRefsCacheFilename(reference.url), JSON.stringify(mystXRefs));
reference.value = mystXRefs;
session.log.info(
toc(`🏫 Read ${mystXRefs.references.length} myst references for "${reference.key}" in %s.`),
Expand Down Expand Up @@ -132,10 +126,7 @@ async function loadReference(
reference.kind = 'intersphinx';
reference.value = inventory;
if (inventory.id && inventory.path && isUrl(inventory.path)) {
const intersphinxPath = cachePath(
session,
inventoryCacheFilename('intersphinx', inventory.id, inventory.path),
);
const intersphinxPath = cachePath(session, sphinxInventoryCacheFilename(inventory.path));
if (!fs.existsSync(intersphinxPath)) {
session.log.debug(`Saving remote inventory file to cache: ${inventory.path}`);
fs.mkdirSync(dirname(intersphinxPath), { recursive: true });
Expand Down
90 changes: 88 additions & 2 deletions packages/myst-cli/src/transforms/crossReferences.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from 'node:fs';
import type { VFile } from 'vfile';
import { selectAll } from 'unist-util-select';
import type { FrontmatterParts, GenericNode, GenericParent, References } from 'myst-common';
Expand All @@ -7,7 +8,7 @@
import type { PageFrontmatter } from 'myst-frontmatter';
import type { CrossReference, Dependency, Link, SourceFileKind } from 'myst-spec-ext';
import type { ISession } from '../session/types.js';
import { loadFromCache, writeToCache } from '../session/cache.js';
import { loadFromCache, writeToCache, checkCache, cachePath } from '../session/cache.js';
import type { SiteAction, SiteExport } from 'myst-config';

export const XREF_MAX_AGE = 1; // in days
Expand All @@ -16,6 +17,10 @@
return `myst-${computeHash(dataUrl)}.json`;
}

export function mystXRefsCacheFilename(url: string) {
return `xrefs-myst-${computeHash(url)}.json`;
}

export type MystData = {
kind?: SourceFileKind;
sha256?: string;
Expand Down Expand Up @@ -74,12 +79,93 @@
return fetchMystData(session, node.dataUrl, node.urlSource, vfile);
}

const MYST_SPEC_VERSION = '0';

function upgradeMystData(version: string, data: any): MystData {
return data;
}

async function stepwiseDowngrade(
session: ISession,
fromVersion: string,
toVersion: string,
vfile: VFile,
data: any,
): Promise<any> {
const downgradeCachePath = `myst-downgrade-${fromVersion}-${toVersion}.mjs`;
if (
!checkCache(session, downgradeCachePath, {
maxAge: 7, // days
})
) {
try {
const response = await fetch(`http://localhost:9000/${downgradeCachePath}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on just treating this as a normal npm package that we install in the _build directory and check for updates (in the same way we do for mystmd now)?

Then there would be a version check against npm for that package, an npm install (already doing that for the themes).

That would mean we don't have to host these, we have versions, publishing, deprecations, etc. AND other tools can use this in a normal way - it is just a package.

const body = await response.text();
fs.writeFileSync(cachePath(session, downgradeCachePath), body);
} catch (err) {
fileWarn(
vfile,
`Unable to load utility for downgrading XRef from ${fromVersion} to ${toVersion}`,
);
return data;
}
}

const module = await import(cachePath(session, downgradeCachePath));
return module.default(data);
}

async function downgradeMystData(
session: ISession,
version: string,
vfile: VFile,
data: any,
): Promise<MystData> {
const fromVersion = parseInt(version);
const toVersion = parseInt(MYST_SPEC_VERSION);
for (let stepVersion = fromVersion; stepVersion !== toVersion; stepVersion--) {
data = await stepwiseDowngrade(session, `${version}`, `${stepVersion - 1}`, vfile, data);
}
return data;
}

export async function fetchMystXRefData(session: ISession, node: CrossReference, vfile: VFile) {
let dataUrl: string | undefined;
if (node.remoteBaseUrl && node.dataUrl) {
dataUrl = `${node.remoteBaseUrl}${node.dataUrl}`;
}
return fetchMystData(session, dataUrl, node.urlSource, vfile);
const rawData = await fetchMystData(session, dataUrl, node.urlSource, vfile);
let data: MystData | undefined;
if (node.remoteBaseUrl && !!rawData) {
// Retrieve the external xref information to determine the spec version
const cachePath = mystXRefsCacheFilename(node.remoteBaseUrl);

Check failure on line 141 in packages/myst-cli/src/transforms/crossReferences.ts

View workflow job for this annotation

GitHub Actions / lint

'cachePath' is already declared in the upper scope on line 11 column 51
const mystXRefData = loadFromCache(session, cachePath, {
maxAge: XREF_MAX_AGE,
});
if (!mystXRefData) {
fileWarn(vfile, `Unable to load external MyST reference data: ${node.remoteBaseUrl}`);
}
// Bring potentially incompatible schema into-alignment
else {
const { version } = JSON.parse(mystXRefData) as { version: string };
if (version === MYST_SPEC_VERSION) {
data = rawData;
} else if (parseInt(version) < parseInt(MYST_SPEC_VERSION)) {
data = upgradeMystData(version, rawData);
} else {
console.log(`Upgrading xref ${node.urlSource} with version ${version}`);
data = await downgradeMystData(session, version, vfile, rawData);
}
}
data = rawData;
} else {
fileWarn(
vfile,
`Unable to determine XRef AST version for external MyST reference: ${node.urlSource}`,
);
data = rawData;
}
return data;
}

export function nodesFromMystXRefData(
Expand Down
Loading