@oml/cli 0.8.0 → 0.10.0
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.
- package/README.md +1 -1
- package/out/backend/backend-types.d.ts +2 -0
- package/out/backend/direct-backend.d.ts +3 -0
- package/out/backend/direct-backend.js +32 -0
- package/out/backend/direct-backend.js.map +1 -1
- package/out/cli-error.d.ts +5 -0
- package/out/cli-error.js +12 -0
- package/out/cli-error.js.map +1 -0
- package/out/cli.d.ts +1 -0
- package/out/cli.js +10 -1
- package/out/cli.js.map +1 -1
- package/out/commands/compile.js +2 -2
- package/out/commands/compile.js.map +1 -1
- package/out/commands/lint.js +2 -2
- package/out/commands/lint.js.map +1 -1
- package/out/commands/reason.js +4 -6
- package/out/commands/reason.js.map +1 -1
- package/out/commands/render.js +29 -30
- package/out/commands/render.js.map +1 -1
- package/out/commands/validate.js +3 -3
- package/out/commands/validate.js.map +1 -1
- package/out/main.js +4 -2
- package/out/main.js.map +1 -1
- package/out/platform.js +8 -1
- package/out/platform.js.map +1 -1
- package/out/util.js +6 -7
- package/out/util.js.map +1 -1
- package/package.json +7 -14
- package/src/backend/backend-types.ts +2 -0
- package/src/backend/direct-backend.ts +34 -0
- package/src/cli-error.ts +15 -0
- package/src/cli.ts +11 -1
- package/src/commands/compile.ts +2 -2
- package/src/commands/lint.ts +2 -2
- package/src/commands/reason.ts +4 -6
- package/src/commands/render.ts +33 -26
- package/src/commands/validate.ts +3 -3
- package/src/main.ts +4 -2
- package/src/platform.ts +10 -1
- package/src/util.ts +7 -8
package/src/commands/render.ts
CHANGED
|
@@ -15,15 +15,16 @@ import {
|
|
|
15
15
|
type MdBlockKind,
|
|
16
16
|
type MdExecutableBlock
|
|
17
17
|
} from '@oml/markdown';
|
|
18
|
+
import { runWithIriLabelSnapshot } from '@oml/markdown/renderers';
|
|
18
19
|
import { STATIC_MARKDOWN_RUNTIME_BUNDLE_FILE, STATIC_MARKDOWN_RUNTIME_CSS } from '@oml/markdown/static';
|
|
19
20
|
import { normalizeFormatExtension } from '../backend/reasoned-output.js';
|
|
20
21
|
import { createBackend } from '../backend/create-backend.js';
|
|
21
22
|
import type { CliBackend } from '../backend/backend-types.js';
|
|
23
|
+
import { failCli } from '../cli-error.js';
|
|
22
24
|
import { formatDuration } from '../util.js';
|
|
23
25
|
import type { ReasonOptions } from './reason.js';
|
|
24
26
|
import { reasonAction } from './reason.js';
|
|
25
27
|
import { resolveCompileOutputRoot, resolveCompileWorkspaceRoot } from './compile.js';
|
|
26
|
-
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
|
27
28
|
const markdownRuntime = new MarkdownPreviewRuntime(new MarkdownHandlerRegistry());
|
|
28
29
|
const SUPPORTED_MD_BLOCK_KINDS = new Set<MdBlockKind>([
|
|
29
30
|
'table',
|
|
@@ -84,18 +85,15 @@ export const renderAction = async (
|
|
|
84
85
|
const output = path.resolve(opts.web);
|
|
85
86
|
const workspaceStat = await fs.stat(workspaceRoot).catch(() => undefined);
|
|
86
87
|
if (!workspaceStat || !workspaceStat.isDirectory()) {
|
|
87
|
-
|
|
88
|
-
process.exit(1);
|
|
88
|
+
failCli(chalk.red(`Workspace folder does not exist: ${workspaceRoot}`));
|
|
89
89
|
}
|
|
90
90
|
const markdownStat = await fs.stat(markdownRoot).catch(() => undefined);
|
|
91
91
|
if (!markdownStat || !markdownStat.isDirectory()) {
|
|
92
|
-
|
|
93
|
-
process.exit(1);
|
|
92
|
+
failCli(chalk.red(`Markdown folder does not exist: ${markdownRoot}`));
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
if (isSameOrDescendant(markdownRoot, output)) {
|
|
97
|
-
|
|
98
|
-
process.exit(1);
|
|
96
|
+
failCli(chalk.red('Web output folder cannot be inside the markdown folder.'));
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
if (opts.clean) {
|
|
@@ -108,6 +106,7 @@ export const renderAction = async (
|
|
|
108
106
|
const templateCatalog = buildTemplateCatalog(templateFiles);
|
|
109
107
|
const explicitContextModelUri = resolveModelUriOption(opts.context, workspaceRoot);
|
|
110
108
|
const templateGenerationEnabled = explicitContextModelUri !== undefined;
|
|
109
|
+
const memberLabelSnapshot = await backend.getWorkspaceMemberLabelSnapshot(workspaceRoot);
|
|
111
110
|
const wikiPageIndex = buildWikiPageIndex(renderableMarkdownFiles, markdownRoot, output);
|
|
112
111
|
const generatedInstancePages = new Set<string>();
|
|
113
112
|
const instancePageByIri = new Map<string, string>();
|
|
@@ -129,11 +128,11 @@ export const renderAction = async (
|
|
|
129
128
|
staticAssets.stylesheetFile,
|
|
130
129
|
templateGenerationEnabled,
|
|
131
130
|
explicitContextModelUri,
|
|
131
|
+
memberLabelSnapshot,
|
|
132
132
|
wikiPageIndex,
|
|
133
133
|
templateCatalog,
|
|
134
134
|
generatedInstancePages,
|
|
135
|
-
instancePageByIri
|
|
136
|
-
,
|
|
135
|
+
instancePageByIri,
|
|
137
136
|
attemptedInstanceIris
|
|
138
137
|
);
|
|
139
138
|
const referencedWorkspaceAssets = rendered.workspaceAssets;
|
|
@@ -292,6 +291,7 @@ async function renderMarkdownFile(
|
|
|
292
291
|
stylesheetFile: string,
|
|
293
292
|
templateGenerationEnabled: boolean,
|
|
294
293
|
defaultContextModelUri: string | undefined,
|
|
294
|
+
memberLabelSnapshot: Record<string, string>,
|
|
295
295
|
wikiPageIndex: ReadonlyMap<string, string>,
|
|
296
296
|
templateCatalog: TemplateCatalog,
|
|
297
297
|
generatedInstancePages: Set<string>,
|
|
@@ -302,7 +302,9 @@ async function renderMarkdownFile(
|
|
|
302
302
|
forcedModelUri?: string
|
|
303
303
|
): Promise<{ workspaceAssets: Set<string>; blockArtifactFiles: number }> {
|
|
304
304
|
const markdown = explicitMarkdown ?? await fs.readFile(inputFile, 'utf-8');
|
|
305
|
-
const
|
|
305
|
+
const inlineContextUri = extractLeadingFrontMatter(markdown)?.contextUri;
|
|
306
|
+
const modelUri = forcedModelUri ?? resolveContextModelUri(inputFile, inlineContextUri, workspaceRoot) ?? defaultContextModelUri;
|
|
307
|
+
const prepared = runWithIriLabelSnapshot(memberLabelSnapshot, () => markdownRuntime.prepare(markdown));
|
|
306
308
|
const rewriteResult = rewriteRenderedLinks(prepared.renderedHtml, {
|
|
307
309
|
workspaceRoot,
|
|
308
310
|
inputRoot,
|
|
@@ -312,7 +314,6 @@ async function renderMarkdownFile(
|
|
|
312
314
|
});
|
|
313
315
|
const executableBlocks = toExecutableBlocks(prepared.codeBlocks);
|
|
314
316
|
const optionsByBlockId = new Map(prepared.codeBlocks.map((block) => [block.id, block.options] as const));
|
|
315
|
-
const modelUri = forcedModelUri ?? resolveContextModelUri(inputFile, prepared.contextUri, workspaceRoot) ?? defaultContextModelUri;
|
|
316
317
|
const blockResults = executableBlocks.length === 0
|
|
317
318
|
? []
|
|
318
319
|
: (await backend.executeMarkdownBlocks({
|
|
@@ -344,6 +345,7 @@ async function renderMarkdownFile(
|
|
|
344
345
|
stylesheetFile,
|
|
345
346
|
templateGenerationEnabled,
|
|
346
347
|
modelUri,
|
|
348
|
+
memberLabelSnapshot,
|
|
347
349
|
pageWikilinkIris,
|
|
348
350
|
renderedBlockResults,
|
|
349
351
|
wikiPageIndex,
|
|
@@ -368,7 +370,8 @@ async function renderMarkdownFile(
|
|
|
368
370
|
blockRewriteResult.results,
|
|
369
371
|
wikiLinkHrefByKey,
|
|
370
372
|
{ ...resolvedInstanceLinks, ...iriAliasHrefByIri },
|
|
371
|
-
templateGenerationEnabled && Boolean(modelUri)
|
|
373
|
+
templateGenerationEnabled && Boolean(modelUri),
|
|
374
|
+
memberLabelSnapshot
|
|
372
375
|
);
|
|
373
376
|
await fs.mkdir(path.dirname(outputFile), { recursive: true });
|
|
374
377
|
await fs.writeFile(outputFile, html, 'utf-8');
|
|
@@ -445,8 +448,8 @@ async function writeStaticAssets(outputRoot: string): Promise<{
|
|
|
445
448
|
const mergedStylesheet = `${sourceStylesheet}\n\n${STATIC_MARKDOWN_RUNTIME_CSS}\n`;
|
|
446
449
|
const runtimeVersion = createHash('sha1').update(runtimeBundle).digest('hex').slice(0, 12);
|
|
447
450
|
const stylesheetVersion = createHash('sha1').update(mergedStylesheet).digest('hex').slice(0, 12);
|
|
448
|
-
const runtimeFile = path.join(outputRoot, '
|
|
449
|
-
const stylesheetFile = path.join(outputRoot, '
|
|
451
|
+
const runtimeFile = path.join(outputRoot, 'assets', `markdown-static-${runtimeVersion}.js`);
|
|
452
|
+
const stylesheetFile = path.join(outputRoot, 'assets', `markdown-webview-${stylesheetVersion}.css`);
|
|
450
453
|
await fs.mkdir(path.dirname(runtimeFile), { recursive: true });
|
|
451
454
|
await fs.writeFile(runtimeFile, runtimeBundle, 'utf-8');
|
|
452
455
|
await fs.writeFile(stylesheetFile, mergedStylesheet, 'utf-8');
|
|
@@ -468,18 +471,17 @@ async function loadStaticRuntimeBundle(): Promise<string> {
|
|
|
468
471
|
}
|
|
469
472
|
|
|
470
473
|
async function loadCodeBlockStylesheet(): Promise<string> {
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
474
|
+
const require = createRequire(import.meta.url);
|
|
475
|
+
const staticEntry = require.resolve('@oml/markdown/static');
|
|
476
|
+
const stylesheetPath = path.resolve(path.dirname(staticEntry), '..', '..', 'src', 'static', 'markdown-webview.css');
|
|
477
|
+
try {
|
|
478
|
+
return await fs.readFile(stylesheetPath, 'utf-8');
|
|
479
|
+
} catch {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`Unable to load markdown webview stylesheet at '${stylesheetPath}'. `
|
|
482
|
+
+ 'The installed @oml/markdown package is missing a required static asset.'
|
|
483
|
+
);
|
|
481
484
|
}
|
|
482
|
-
return '';
|
|
483
485
|
}
|
|
484
486
|
|
|
485
487
|
async function writeBlockArtifacts(outputFile: string, results: ReadonlyArray<RenderedBlockResult>): Promise<{
|
|
@@ -880,6 +882,7 @@ async function generateInstanceTemplatePages(
|
|
|
880
882
|
stylesheetFile: string,
|
|
881
883
|
templateGenerationEnabled: boolean,
|
|
882
884
|
queryContextModelUri: string,
|
|
885
|
+
memberLabelSnapshot: Record<string, string>,
|
|
883
886
|
pageWikilinkIris: ReadonlySet<string>,
|
|
884
887
|
results: ReadonlyArray<RenderedBlockResult>,
|
|
885
888
|
wikiPageIndex: ReadonlyMap<string, string>,
|
|
@@ -932,6 +935,7 @@ async function generateInstanceTemplatePages(
|
|
|
932
935
|
stylesheetFile,
|
|
933
936
|
templateGenerationEnabled,
|
|
934
937
|
queryContextModelUri,
|
|
938
|
+
memberLabelSnapshot,
|
|
935
939
|
wikiPageIndex,
|
|
936
940
|
templateCatalog,
|
|
937
941
|
generatedInstancePages,
|
|
@@ -960,7 +964,8 @@ function wrapHtml(
|
|
|
960
964
|
blockResults: ReadonlyArray<RenderedBlockResult>,
|
|
961
965
|
wikiLinkHrefByKey: Record<string, string>,
|
|
962
966
|
iriAliasHrefByIri: Record<string, string>,
|
|
963
|
-
linkingEnabled: boolean
|
|
967
|
+
linkingEnabled: boolean,
|
|
968
|
+
memberLabelSnapshot?: Record<string, string>
|
|
964
969
|
): string {
|
|
965
970
|
const escapedManifest = escapeJsonForScript(JSON.stringify(blockManifest));
|
|
966
971
|
const inlineResults = Object.fromEntries(blockResults.map((result) => [result.blockId, result]));
|
|
@@ -968,6 +973,7 @@ function wrapHtml(
|
|
|
968
973
|
const escapedWikiIndex = escapeJsonForScript(JSON.stringify(wikiLinkHrefByKey));
|
|
969
974
|
const escapedIriAliasIndex = escapeJsonForScript(JSON.stringify(iriAliasHrefByIri));
|
|
970
975
|
const escapedLinkingConfig = escapeJsonForScript(JSON.stringify({ linkingEnabled }));
|
|
976
|
+
const escapedMemberLabels = escapeJsonForScript(JSON.stringify(memberLabelSnapshot ?? {}));
|
|
971
977
|
return `<!doctype html>
|
|
972
978
|
<html lang="en">
|
|
973
979
|
<head>
|
|
@@ -983,6 +989,7 @@ ${content}
|
|
|
983
989
|
<script id="oml-md-wikilink-index" type="application/json">${escapedWikiIndex}</script>
|
|
984
990
|
<script id="oml-md-wikilink-iri-aliases" type="application/json">${escapedIriAliasIndex}</script>
|
|
985
991
|
<script id="oml-md-wikilink-config" type="application/json">${escapedLinkingConfig}</script>
|
|
992
|
+
<script id="oml-md-member-labels" type="application/json">${escapedMemberLabels}</script>
|
|
986
993
|
<script src="${escapeAttribute(runtimeScriptPath)}"></script>
|
|
987
994
|
</body>
|
|
988
995
|
</html>
|
package/src/commands/validate.ts
CHANGED
|
@@ -10,6 +10,7 @@ import * as fs from 'node:fs/promises';
|
|
|
10
10
|
import * as path from 'node:path';
|
|
11
11
|
import * as url from 'node:url';
|
|
12
12
|
import { loadPreparedDatasetFromOutput, normalizeFormatExtension } from '../backend/reasoned-output.js';
|
|
13
|
+
import { failCli } from '../cli-error.js';
|
|
13
14
|
import { formatDuration } from '../util.js';
|
|
14
15
|
import type { ReasonOptions } from './reason.js';
|
|
15
16
|
import { reasonAction } from './reason.js';
|
|
@@ -40,8 +41,7 @@ export const validateAction = async (opts: ValidateOptions): Promise<void> => {
|
|
|
40
41
|
|
|
41
42
|
const markdownStat = await fs.stat(markdownRoot).catch(() => undefined);
|
|
42
43
|
if (!markdownStat?.isDirectory()) {
|
|
43
|
-
|
|
44
|
-
process.exit(1);
|
|
44
|
+
failCli(chalk.red(`Markdown folder does not exist: ${markdownRoot}`));
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
if (!opts.only) {
|
|
@@ -98,7 +98,7 @@ export const validateAction = async (opts: ValidateOptions): Promise<void> => {
|
|
|
98
98
|
|
|
99
99
|
reportValidationResults(results, workspaceRoot, markdownRoot, validationStartedAt);
|
|
100
100
|
if (results.some((result) => result.error || result.issues.length > 0 || !result.conforms)) {
|
|
101
|
-
|
|
101
|
+
failCli('');
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
104
|
|
package/src/main.ts
CHANGED
package/src/platform.ts
CHANGED
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
* commands to execute. No whitelist or other fallback is used.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { OmlClient, FileStorageAdapter } from '@oml/platform';
|
|
11
|
+
import { OmlClient, FileStorageAdapter, installNodeShutdownHandlers } from '@oml/platform';
|
|
12
12
|
import type { OmlClientConfig } from '@oml/platform';
|
|
13
|
+
import type { NodeShutdownHandle } from '@oml/platform';
|
|
13
14
|
import chalk from 'chalk';
|
|
14
15
|
import { DEFAULT_API_BASE_URL } from './platform-constants.js';
|
|
15
16
|
import { OmlCliAuthService } from './auth.js';
|
|
16
17
|
const API_BASE_URL_ENV = 'OML_PLATFORM_API_URL';
|
|
17
18
|
|
|
18
19
|
let client: OmlClient | null = null;
|
|
20
|
+
let shutdownHandle: NodeShutdownHandle | null = null;
|
|
19
21
|
|
|
20
22
|
export type CommandInvocationTracker = ReturnType<OmlClient['trackInvocation']>;
|
|
21
23
|
|
|
@@ -30,6 +32,8 @@ export async function initializePlatform(
|
|
|
30
32
|
authService: OmlCliAuthService,
|
|
31
33
|
apiBaseUrl = DEFAULT_API_BASE_URL
|
|
32
34
|
): Promise<void> {
|
|
35
|
+
await disposePlatform();
|
|
36
|
+
|
|
33
37
|
const key = process.env.OML_PLATFORM_API_KEY;
|
|
34
38
|
const resolvedApiBaseUrl = process.env[API_BASE_URL_ENV]?.trim() || apiBaseUrl;
|
|
35
39
|
|
|
@@ -66,6 +70,7 @@ export async function initializePlatform(
|
|
|
66
70
|
throw new Error(`OML CLI could not connect to the authorization service. ${toGenericPlatformErrorMessage(error)}`);
|
|
67
71
|
}
|
|
68
72
|
client = platformClient;
|
|
73
|
+
shutdownHandle = installNodeShutdownHandlers(platformClient);
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
/**
|
|
@@ -73,6 +78,10 @@ export async function initializePlatform(
|
|
|
73
78
|
* and ends the session. Call at the end of CLI execution.
|
|
74
79
|
*/
|
|
75
80
|
export async function disposePlatform(): Promise<void> {
|
|
81
|
+
if (shutdownHandle) {
|
|
82
|
+
shutdownHandle.dispose();
|
|
83
|
+
shutdownHandle = null;
|
|
84
|
+
}
|
|
76
85
|
if (client) {
|
|
77
86
|
await client.dispose();
|
|
78
87
|
client = null;
|
package/src/util.ts
CHANGED
|
@@ -5,17 +5,16 @@ import chalk from 'chalk';
|
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import * as fs from 'node:fs';
|
|
7
7
|
import { URI } from 'langium';
|
|
8
|
+
import { failCli } from './cli-error.js';
|
|
8
9
|
|
|
9
10
|
export async function extractDocument(fileName: string, services: LangiumCoreServices, workspaceRoot?: string): Promise<LangiumDocument> {
|
|
10
11
|
const extensions = services.LanguageMetaData.fileExtensions;
|
|
11
12
|
if (!extensions.includes(path.extname(fileName))) {
|
|
12
|
-
|
|
13
|
-
process.exit(1);
|
|
13
|
+
failCli(chalk.yellow(`Please choose a file with one of these extensions: ${extensions}.`));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
if (!fs.existsSync(fileName)) {
|
|
17
|
-
|
|
18
|
-
process.exit(1);
|
|
17
|
+
failCli(chalk.red(`File ${fileName} does not exist.`));
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
if (workspaceRoot) {
|
|
@@ -31,13 +30,13 @@ export async function extractDocument(fileName: string, services: LangiumCoreSer
|
|
|
31
30
|
const diagnostics = document.diagnostics ?? [];
|
|
32
31
|
const validationErrors = diagnostics.filter(e => e.severity === 1);
|
|
33
32
|
if (validationErrors.length > 0) {
|
|
34
|
-
|
|
33
|
+
let errorMessage = chalk.red(`There are validation errors in ${path.resolve(fileName)}:`);
|
|
35
34
|
for (const validationError of validationErrors) {
|
|
36
|
-
|
|
35
|
+
errorMessage += `\n${chalk.red(
|
|
37
36
|
`line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]`
|
|
38
|
-
)
|
|
37
|
+
)}`;
|
|
39
38
|
}
|
|
40
|
-
|
|
39
|
+
failCli(errorMessage);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
const validationWarnings = diagnostics.filter(e => e.severity === 2);
|