@oml/server 0.16.1 → 0.16.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,6 +22,8 @@ import { createOpenApiSpec, dispatchRestOperation, resolveRestOperationId } from
22
22
  import { buildTemplateCatalog, expandTemplateComposeBlocks, findFilesByExtension, frontMatterString, isTemplateMarkdownFile, normalizeContextOntologyIri, } from './template.js';
23
23
  import { lintWorkspace, validateWorkspace } from './validation.js';
24
24
  import { reindexWorkspaceShacl } from './shacl-index.js';
25
+ import { generateShellIndex } from './shell-index.js';
26
+ import { readWorkspaceSettings } from '../workspace-settings.js';
25
27
  import { OmlAccessError, requiredFeatureForRestOperation, } from '../auth/feature-policy.js';
26
28
  const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
27
29
  const HTML_CONTENT_TYPE = 'text/html; charset=utf-8';
@@ -1671,6 +1673,38 @@ function toMdBlockKind(language) {
1671
1673
  return SUPPORTED_MD_BLOCK_KINDS.has(language) ? language : undefined;
1672
1674
  }
1673
1675
  const SCRIPT_LANGUAGES = new Set(['javascript', 'js', 'python', 'r']);
1676
+ function extractScriptIncludePaths(code) {
1677
+ const re = /\binclude\s*\(\s*(?:"((?:[^"\\]|\\[\s\S])*)"|'((?:[^'\\]|\\[\s\S])*)'|`((?:[^`\\]|\\[\s\S])*)`)\s*\)/g;
1678
+ const seen = new Set();
1679
+ let m;
1680
+ while ((m = re.exec(code)) !== null) {
1681
+ seen.add(m[1] ?? m[2] ?? m[3]);
1682
+ }
1683
+ return [...seen];
1684
+ }
1685
+ async function buildScriptIncludeCache(codeBlocks, workspaceRoot) {
1686
+ const allPaths = new Set();
1687
+ for (const block of codeBlocks) {
1688
+ if (!SCRIPT_LANGUAGES.has(block.language)) {
1689
+ continue;
1690
+ }
1691
+ for (const p of extractScriptIncludePaths(block.content)) {
1692
+ allPaths.add(p);
1693
+ }
1694
+ }
1695
+ const cache = {};
1696
+ await Promise.all([...allPaths].map(async (p) => {
1697
+ try {
1698
+ const abs = path.resolve(workspaceRoot, p);
1699
+ const rel = path.relative(workspaceRoot, abs);
1700
+ if (!rel.startsWith('..') && !path.isAbsolute(rel)) {
1701
+ cache[p] = await fs.readFile(abs, 'utf-8');
1702
+ }
1703
+ }
1704
+ catch { /* silently omit missing/unreadable files */ }
1705
+ }));
1706
+ return cache;
1707
+ }
1674
1708
  function extractScriptQueryStrings(code) {
1675
1709
  const seen = new Set();
1676
1710
  const patterns = [
@@ -1770,6 +1804,9 @@ function toRelativeWebPath(fromDir, toFile) {
1770
1804
  }
1771
1805
  return relative.startsWith('.') ? relative : `./${relative}`;
1772
1806
  }
1807
+ function contentRelPathForShell(shellRelPath) {
1808
+ return path.posix.join('assets', 'pages', shellRelPath);
1809
+ }
1773
1810
  function isImagePath(filePath) {
1774
1811
  return /\.(png|jpe?g|gif|svg|webp|bmp|ico|avif)$/i.test(filePath);
1775
1812
  }
@@ -2117,7 +2154,7 @@ function buildIriAliasMapForPage(aliasesByIri, outputFile) {
2117
2154
  }
2118
2155
  return aliases;
2119
2156
  }
2120
- function wrapHtml(content, runtimeScriptPath, stylesheetPath, blockManifest, blockResults, wikiLinkHrefByKey, iriAliasByIri, scriptSparqlCache) {
2157
+ function wrapHtml(content, runtimeScriptPath, stylesheetPath, blockManifest, blockResults, wikiLinkHrefByKey, iriAliasByIri, scriptSparqlCache, scriptIncludeCache) {
2121
2158
  const escapedManifest = escapeJsonForScript(JSON.stringify(blockManifest));
2122
2159
  const inlineResults = Object.fromEntries(blockResults.map((result) => [result.blockId, result]));
2123
2160
  const escapedInlineResults = escapeJsonForScript(JSON.stringify(inlineResults));
@@ -2125,10 +2162,12 @@ function wrapHtml(content, runtimeScriptPath, stylesheetPath, blockManifest, blo
2125
2162
  const escapedIriAliasIndex = escapeJsonForScript(JSON.stringify(iriAliasByIri));
2126
2163
  const escapedLinkingConfig = escapeJsonForScript(JSON.stringify({ linkingEnabled: true }));
2127
2164
  const escapedScriptCache = escapeJsonForScript(JSON.stringify(scriptSparqlCache));
2165
+ const escapedIncludeCache = escapeJsonForScript(JSON.stringify(scriptIncludeCache));
2128
2166
  return `<!doctype html>
2129
2167
  <html lang="en">
2130
2168
  <head>
2131
2169
  <meta charset="UTF-8">
2170
+ <base href="__OML_SHELL_BASE_HREF__" target="_top">
2132
2171
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
2133
2172
  <title>OML Markdown</title>
2134
2173
  <script>
@@ -2150,6 +2189,7 @@ function wrapHtml(content, runtimeScriptPath, stylesheetPath, blockManifest, blo
2150
2189
  <script id="oml-md-wikilink-config" type="application/json">${escapedLinkingConfig}</script>
2151
2190
  <script id="oml-md-member-labels" type="application/json">{}</script>
2152
2191
  <script id="oml-md-script-sparql-cache" type="application/json">${escapedScriptCache}</script>
2192
+ <script id="oml-md-script-include-cache" type="application/json">${escapedIncludeCache}</script>
2153
2193
  <script src="${escapeAttribute(runtimeScriptPath)}"></script>
2154
2194
  </body>
2155
2195
  </html>
@@ -2763,6 +2803,14 @@ class InMemoryJsonRpcLspClient {
2763
2803
  }
2764
2804
  async renderWorkspace(params) {
2765
2805
  await this.ensureInitialized();
2806
+ const reasoningService = this.runtime.Oml.reasoning.ReasoningService;
2807
+ const render = () => this.renderWorkspaceWithInitializedRuntime(params);
2808
+ if (typeof reasoningService?.suppressSemanticChangeNotifications === 'function') {
2809
+ return await reasoningService.suppressSemanticChangeNotifications(render);
2810
+ }
2811
+ return await render();
2812
+ }
2813
+ async renderWorkspaceWithInitializedRuntime(params) {
2766
2814
  await this.ensureWorkspaceSettled(false);
2767
2815
  const lint = await this.lintWorkspace({});
2768
2816
  if (lint.errors > 0 || lint.warnings > 0) {
@@ -2819,6 +2867,7 @@ class InMemoryJsonRpcLspClient {
2819
2867
  const workspaceAssetFiles = new Set();
2820
2868
  const aliasesByIri = new Map();
2821
2869
  const renderJobs = [];
2870
+ const mdHtmlPaths = [];
2822
2871
  const contextModelUris = new Set();
2823
2872
  let filesRendered = 0;
2824
2873
  let blockArtifactFiles = 0;
@@ -2851,13 +2900,13 @@ class InMemoryJsonRpcLspClient {
2851
2900
  });
2852
2901
  const prepared = runtime.prepare(expandedMarkdown);
2853
2902
  const relative = path.relative(inputDir, markdownPath).replace(/\\/g, '/');
2854
- const htmlPath = path.join(outputDir, relative.replace(/\.md$/i, '.html'));
2903
+ const shellPath = path.join(outputDir, relative.replace(/\.md$/i, '.html'));
2855
2904
  const rewriteResult = rewriteRenderedLinks(prepared.renderedHtml, {
2856
2905
  workspaceRoot,
2857
2906
  inputRoot: inputDir,
2858
2907
  inputFile: markdownPath,
2859
2908
  outputRoot: outputDir,
2860
- outputFile: htmlPath,
2909
+ outputFile: shellPath,
2861
2910
  });
2862
2911
  for (const asset of rewriteResult.workspaceAssets) {
2863
2912
  workspaceAssetFiles.add(asset);
@@ -2879,22 +2928,25 @@ class InMemoryJsonRpcLspClient {
2879
2928
  inputRoot: inputDir,
2880
2929
  sourceFile: markdownPath,
2881
2930
  outputRoot: outputDir,
2882
- outputFile: htmlPath,
2931
+ outputFile: shellPath,
2883
2932
  }, workspaceAssetFiles));
2884
- const blockArtifacts = await writeBlockArtifacts(htmlPath, rewrittenBlockResults);
2933
+ const blockArtifacts = await writeBlockArtifacts(shellPath, rewrittenBlockResults);
2885
2934
  blockArtifactFiles += blockArtifacts.count;
2886
- const wikiLinkHrefByKey = buildWikiLinkHrefMapForPage(wikiPageIndex, htmlPath);
2935
+ const wikiLinkHrefByKey = buildWikiLinkHrefMapForPage(wikiPageIndex, shellPath);
2887
2936
  const scriptSparqlCache = contextModelUri
2888
2937
  ? await buildScriptSparqlCache(prepared.codeBlocks, (sparql) => reasoningService.getSparqlService().query(contextModelUri, sparql))
2889
2938
  : {};
2939
+ const scriptIncludeCache = await buildScriptIncludeCache(prepared.codeBlocks, workspaceRoot);
2890
2940
  renderJobs.push({
2891
- htmlPath,
2941
+ shellPath,
2892
2942
  renderedHtml: rewriteResult.html,
2893
2943
  blockManifest: blockArtifacts.manifest,
2894
2944
  blockResults: rewrittenBlockResults,
2895
2945
  wikiLinkHrefByKey,
2896
2946
  scriptSparqlCache,
2947
+ scriptIncludeCache,
2897
2948
  });
2949
+ mdHtmlPaths.push(shellPath);
2898
2950
  filesRendered += 1;
2899
2951
  }
2900
2952
  const renderedNavigationPages = new Set();
@@ -2907,15 +2959,15 @@ class InMemoryJsonRpcLspClient {
2907
2959
  if (!resolution.template) {
2908
2960
  continue;
2909
2961
  }
2910
- const targetFile = memberIriToOutputFile(outputDir, memberIri);
2911
- if (!targetFile) {
2962
+ const targetShellFile = memberIriToOutputFile(outputDir, memberIri);
2963
+ if (!targetShellFile) {
2912
2964
  continue;
2913
2965
  }
2914
- aliasesByIri.set(memberIri, targetFile);
2915
- if (renderedNavigationPages.has(targetFile)) {
2966
+ aliasesByIri.set(memberIri, targetShellFile);
2967
+ if (renderedNavigationPages.has(targetShellFile)) {
2916
2968
  continue;
2917
2969
  }
2918
- renderedNavigationPages.add(targetFile);
2970
+ renderedNavigationPages.add(targetShellFile);
2919
2971
  const contextOntologyIri = inferOntologyIriFromMemberIri(memberIri);
2920
2972
  const sourceDocumentUri = resolution.template.sourceUri?.trim() || pathToFileURL(path.join(workspaceRoot, 'index.md')).toString();
2921
2973
  const invocation = {
@@ -2940,8 +2992,11 @@ class InMemoryJsonRpcLspClient {
2940
2992
  },
2941
2993
  args: {},
2942
2994
  };
2943
- const rendered = renderTemplate(resolution.template, invocation);
2944
- if (rendered.missingRequired.length > 0) {
2995
+ let rendered;
2996
+ try {
2997
+ rendered = renderTemplate(resolution.template, invocation);
2998
+ }
2999
+ catch {
2945
3000
  continue;
2946
3001
  }
2947
3002
  const syntheticSourcePath = resolution.template.sourceUri?.startsWith('file:')
@@ -2961,7 +3016,7 @@ class InMemoryJsonRpcLspClient {
2961
3016
  inputRoot: inputDir,
2962
3017
  inputFile: syntheticSourcePath,
2963
3018
  outputRoot: outputDir,
2964
- outputFile: targetFile,
3019
+ outputFile: targetShellFile,
2965
3020
  });
2966
3021
  for (const asset of rewriteResult.workspaceAssets) {
2967
3022
  workspaceAssetFiles.add(asset);
@@ -2983,18 +3038,20 @@ class InMemoryJsonRpcLspClient {
2983
3038
  inputRoot: inputDir,
2984
3039
  sourceFile: syntheticSourcePath,
2985
3040
  outputRoot: outputDir,
2986
- outputFile: targetFile,
3041
+ outputFile: targetShellFile,
2987
3042
  }, workspaceAssetFiles));
2988
- const blockArtifacts = await writeBlockArtifacts(targetFile, rewrittenBlockResults);
3043
+ const blockArtifacts = await writeBlockArtifacts(targetShellFile, rewrittenBlockResults);
2989
3044
  blockArtifactFiles += blockArtifacts.count;
2990
3045
  const scriptSparqlCache = await buildScriptSparqlCache(prepared.codeBlocks, (sparql) => reasoningService.getSparqlService().query(modelUri, sparql));
3046
+ const scriptIncludeCache = await buildScriptIncludeCache(prepared.codeBlocks, workspaceRoot);
2991
3047
  renderJobs.push({
2992
- htmlPath: targetFile,
3048
+ shellPath: targetShellFile,
2993
3049
  renderedHtml: rewriteResult.html,
2994
3050
  blockManifest: blockArtifacts.manifest,
2995
3051
  blockResults: rewrittenBlockResults,
2996
- wikiLinkHrefByKey: buildWikiLinkHrefMapForPage(wikiPageIndex, targetFile),
3052
+ wikiLinkHrefByKey: buildWikiLinkHrefMapForPage(wikiPageIndex, targetShellFile),
2997
3053
  scriptSparqlCache,
3054
+ scriptIncludeCache,
2998
3055
  });
2999
3056
  filesRendered += 1;
3000
3057
  });
@@ -3003,12 +3060,12 @@ class InMemoryJsonRpcLspClient {
3003
3060
  for (const task of navTasks) {
3004
3061
  await task();
3005
3062
  }
3063
+ const shellContentByRelPath = new Map();
3006
3064
  for (const job of renderJobs) {
3007
- const runtimeScriptRelative = toRelativeWebPath(path.dirname(job.htmlPath), staticAssets.runtimeScriptFile);
3008
- const stylesheetRelative = toRelativeWebPath(path.dirname(job.htmlPath), staticAssets.stylesheetFile);
3009
- const html = wrapHtml(job.renderedHtml, runtimeScriptRelative, stylesheetRelative, job.blockManifest, job.blockResults, job.wikiLinkHrefByKey, buildIriAliasMapForPage(aliasesByIri, job.htmlPath), job.scriptSparqlCache);
3010
- await fs.mkdir(path.dirname(job.htmlPath), { recursive: true });
3011
- await fs.writeFile(job.htmlPath, html, 'utf-8');
3065
+ const runtimeScriptRelative = toRelativeWebPath(path.dirname(job.shellPath), staticAssets.runtimeScriptFile);
3066
+ const stylesheetRelative = toRelativeWebPath(path.dirname(job.shellPath), staticAssets.stylesheetFile);
3067
+ const html = wrapHtml(job.renderedHtml, runtimeScriptRelative, stylesheetRelative, job.blockManifest, job.blockResults, job.wikiLinkHrefByKey, buildIriAliasMapForPage(aliasesByIri, job.shellPath), job.scriptSparqlCache, job.scriptIncludeCache);
3068
+ shellContentByRelPath.set(path.relative(outputDir, job.shellPath).replace(/\\/g, '/'), html);
3012
3069
  }
3013
3070
  for (const file of workspaceAssetFiles) {
3014
3071
  const workspaceRelative = path.relative(workspaceRoot, file);
@@ -3034,6 +3091,33 @@ class InMemoryJsonRpcLspClient {
3034
3091
  }
3035
3092
  }
3036
3093
  }
3094
+ const workspaceSettings = await readWorkspaceSettings(workspaceRoot);
3095
+ const projectTitle = workspaceSettings.project?.title?.trim() || undefined;
3096
+ const indexEntry = mdHtmlPaths.find(p => path.relative(outputDir, p).replace(/\\/g, '/').toLowerCase() === 'index.html');
3097
+ const homePath = indexEntry ? path.relative(outputDir, indexEntry).replace(/\\/g, '/') : undefined;
3098
+ const shellPaths = renderJobs.map(j => j.shellPath);
3099
+ const shellPathSet = new Set(shellPaths.map(p => path.relative(outputDir, p).replace(/\\/g, '/')));
3100
+ const contentPathByShellRelPath = new Map();
3101
+ for (const [shellRelPath, contentHtml] of shellContentByRelPath) {
3102
+ const shellFile = path.join(outputDir, shellRelPath);
3103
+ const contentRelPath = contentRelPathForShell(shellRelPath);
3104
+ const contentFile = path.join(outputDir, contentRelPath);
3105
+ const shellHrefFromContent = toRelativeWebPath(path.dirname(contentFile), shellFile);
3106
+ const html = contentHtml.replace(/__OML_SHELL_BASE_HREF__/g, escapeAttribute(shellHrefFromContent));
3107
+ await fs.mkdir(path.dirname(contentFile), { recursive: true });
3108
+ await fs.writeFile(contentFile, html, 'utf-8');
3109
+ contentPathByShellRelPath.set(shellRelPath, contentRelPath);
3110
+ }
3111
+ for (const shellRelPath of shellPathSet) {
3112
+ const shellHtml = generateShellIndex(outputDir, mdHtmlPaths, projectTitle, homePath, shellPaths, shellRelPath, contentPathByShellRelPath.get(shellRelPath));
3113
+ const shellFile = path.join(outputDir, shellRelPath);
3114
+ await fs.mkdir(path.dirname(shellFile), { recursive: true });
3115
+ await fs.writeFile(shellFile, shellHtml, 'utf-8');
3116
+ }
3117
+ if (!shellPathSet.has('index.html')) {
3118
+ const indexHtml = generateShellIndex(outputDir, mdHtmlPaths, projectTitle, undefined, shellPaths);
3119
+ await fs.writeFile(path.join(outputDir, 'index.html'), indexHtml, 'utf-8');
3120
+ }
3037
3121
  return { success: true, filesRendered, outputDir, blockArtifactFiles };
3038
3122
  }
3039
3123
  async ontologiesWorkspace() {
@@ -3204,7 +3288,16 @@ class InMemoryJsonRpcLspClient {
3204
3288
  .map((uri) => URI.parse(uri));
3205
3289
  await Promise.all(changedUris.map((uri) => documents.getOrCreateDocument(uri)));
3206
3290
  const builder = this.runtime.shared.workspace.DocumentBuilder;
3207
- await builder.update(changedUris, deletedUris);
3291
+ const reasoningService = this.runtime.Oml.reasoning.ReasoningService;
3292
+ const runUpdate = async () => {
3293
+ await builder.update(changedUris, deletedUris);
3294
+ };
3295
+ if (typeof reasoningService?.suppressSemanticChangeNotifications === 'function') {
3296
+ await reasoningService.suppressSemanticChangeNotifications(runUpdate);
3297
+ }
3298
+ else {
3299
+ await runUpdate();
3300
+ }
3208
3301
  await this.waitForValidatedWithTrace();
3209
3302
  }
3210
3303
  async bootstrapWorkspaceOmlDocuments() {