@oml/cli 0.7.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 +75 -0
- package/bin/cli.js +6 -0
- package/out/auth.d.ts +25 -0
- package/out/auth.js +253 -0
- package/out/auth.js.map +1 -0
- package/out/backend/backend-types.d.ts +19 -0
- package/out/backend/backend-types.js +3 -0
- package/out/backend/backend-types.js.map +1 -0
- package/out/backend/create-backend.d.ts +2 -0
- package/out/backend/create-backend.js +6 -0
- package/out/backend/create-backend.js.map +1 -0
- package/out/backend/direct-backend.d.ts +17 -0
- package/out/backend/direct-backend.js +97 -0
- package/out/backend/direct-backend.js.map +1 -0
- package/out/backend/reasoned-output.d.ts +38 -0
- package/out/backend/reasoned-output.js +568 -0
- package/out/backend/reasoned-output.js.map +1 -0
- package/out/cli.d.ts +1 -0
- package/out/cli.js +132 -0
- package/out/cli.js.map +1 -0
- package/out/commands/closure.d.ts +33 -0
- package/out/commands/closure.js +537 -0
- package/out/commands/closure.js.map +1 -0
- package/out/commands/compile.d.ts +11 -0
- package/out/commands/compile.js +63 -0
- package/out/commands/compile.js.map +1 -0
- package/out/commands/lint.d.ts +5 -0
- package/out/commands/lint.js +31 -0
- package/out/commands/lint.js.map +1 -0
- package/out/commands/reason.d.ts +13 -0
- package/out/commands/reason.js +62 -0
- package/out/commands/reason.js.map +1 -0
- package/out/commands/render.d.ts +15 -0
- package/out/commands/render.js +753 -0
- package/out/commands/render.js.map +1 -0
- package/out/commands/validate.d.ts +5 -0
- package/out/commands/validate.js +186 -0
- package/out/commands/validate.js.map +1 -0
- package/out/main.d.ts +1 -0
- package/out/main.js +4 -0
- package/out/main.js.map +1 -0
- package/out/update.d.ts +1 -0
- package/out/update.js +79 -0
- package/out/update.js.map +1 -0
- package/out/util.d.ts +10 -0
- package/out/util.js +63 -0
- package/out/util.js.map +1 -0
- package/package.json +36 -0
- package/src/auth.ts +315 -0
- package/src/backend/backend-types.ts +25 -0
- package/src/backend/create-backend.ts +8 -0
- package/src/backend/direct-backend.ts +114 -0
- package/src/backend/reasoned-output.ts +697 -0
- package/src/cli.ts +147 -0
- package/src/commands/closure.ts +624 -0
- package/src/commands/compile.ts +88 -0
- package/src/commands/lint.ts +35 -0
- package/src/commands/reason.ts +79 -0
- package/src/commands/render.ts +1021 -0
- package/src/commands/validate.ts +226 -0
- package/src/main.ts +5 -0
- package/src/update.ts +103 -0
- package/src/util.ts +83 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { normalizeNamespace } from '@oml/language';
|
|
4
|
+
import { createDefaultShaclService, createOwlServices, type ShaclValidationIssue } from '@oml/owl';
|
|
5
|
+
import { MarkdownPreviewRuntime, MarkdownHandlerRegistry } from '@oml/markdown';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { URI } from 'langium';
|
|
8
|
+
import { NodeFileSystem } from 'langium/node';
|
|
9
|
+
import * as fs from 'node:fs/promises';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import * as url from 'node:url';
|
|
12
|
+
import { loadPreparedDatasetFromOutput, normalizeFormatExtension } from '../backend/reasoned-output.js';
|
|
13
|
+
import { formatDuration } from '../util.js';
|
|
14
|
+
import type { ReasonOptions } from './reason.js';
|
|
15
|
+
import { reasonAction } from './reason.js';
|
|
16
|
+
import { resolveCompileOutputRoot, resolveCompileWorkspaceRoot } from './compile.js';
|
|
17
|
+
|
|
18
|
+
const markdownRuntime = new MarkdownPreviewRuntime(new MarkdownHandlerRegistry());
|
|
19
|
+
|
|
20
|
+
export type ValidateOptions = ReasonOptions & {
|
|
21
|
+
md: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type MarkdownValidationResult = {
|
|
25
|
+
markdownFile: string;
|
|
26
|
+
blockId: string;
|
|
27
|
+
lineStart: number;
|
|
28
|
+
lineEnd: number;
|
|
29
|
+
contextModelUri?: string;
|
|
30
|
+
conforms: boolean;
|
|
31
|
+
issues: ShaclValidationIssue[];
|
|
32
|
+
error?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const validateAction = async (opts: ValidateOptions): Promise<void> => {
|
|
36
|
+
const workspaceRoot = resolveCompileWorkspaceRoot(opts.workspace);
|
|
37
|
+
const owlOutputRoot = resolveCompileOutputRoot(workspaceRoot, opts.owl);
|
|
38
|
+
const rdfFormat = normalizeFormatExtension(opts.format);
|
|
39
|
+
const markdownRoot = path.resolve(workspaceRoot, opts.md);
|
|
40
|
+
|
|
41
|
+
const markdownStat = await fs.stat(markdownRoot).catch(() => undefined);
|
|
42
|
+
if (!markdownStat?.isDirectory()) {
|
|
43
|
+
console.error(chalk.red(`Markdown folder does not exist: ${markdownRoot}`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!opts.only) {
|
|
48
|
+
await reasonAction({
|
|
49
|
+
workspace: workspaceRoot,
|
|
50
|
+
owl: owlOutputRoot,
|
|
51
|
+
format: rdfFormat,
|
|
52
|
+
clean: opts.clean,
|
|
53
|
+
pretty: opts.pretty,
|
|
54
|
+
only: false,
|
|
55
|
+
checkOnly: false,
|
|
56
|
+
uniqueNamesAssumption: opts.uniqueNamesAssumption,
|
|
57
|
+
explanations: opts.explanations,
|
|
58
|
+
profile: opts.profile,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const servicesBundle = createOwlServices(NodeFileSystem);
|
|
63
|
+
await servicesBundle.Oml.shared.workspace.WorkspaceManager.initializeWorkspace([
|
|
64
|
+
{ uri: URI.file(workspaceRoot).toString(), name: path.basename(workspaceRoot) }
|
|
65
|
+
]);
|
|
66
|
+
const preparedDataset = await loadPreparedDatasetFromOutput(workspaceRoot, owlOutputRoot, rdfFormat);
|
|
67
|
+
servicesBundle.Oml.reasoning.ReasoningService.loadPreparedDataset(preparedDataset);
|
|
68
|
+
const shaclService = createDefaultShaclService(
|
|
69
|
+
servicesBundle.Oml.reasoning.ReasoningService.getSparqlService(),
|
|
70
|
+
(modelUri) => servicesBundle.Oml.reasoning.ReasoningService.ensureQueryContext(modelUri),
|
|
71
|
+
(modelUri) => servicesBundle.Oml.reasoning.ReasoningService.getContextIri(modelUri),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const validationStartedAt = Date.now();
|
|
75
|
+
const markdownFiles = await discoverMarkdownFiles(markdownRoot);
|
|
76
|
+
const results: MarkdownValidationResult[] = [];
|
|
77
|
+
for (const markdownFile of markdownFiles) {
|
|
78
|
+
const markdown = await fs.readFile(markdownFile, 'utf-8');
|
|
79
|
+
const prepared = markdownRuntime.prepare(markdown);
|
|
80
|
+
const contextModelUri = resolveContextModelUri(markdownFile, prepared.contextUri, workspaceRoot);
|
|
81
|
+
for (const block of prepared.codeBlocks) {
|
|
82
|
+
if (block.language !== 'table-editor') {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const validation = await shaclService.validateShacl(contextModelUri, block.content);
|
|
86
|
+
results.push({
|
|
87
|
+
markdownFile,
|
|
88
|
+
blockId: block.id,
|
|
89
|
+
lineStart: block.lineStart,
|
|
90
|
+
lineEnd: block.lineEnd,
|
|
91
|
+
contextModelUri,
|
|
92
|
+
conforms: validation.conforms,
|
|
93
|
+
issues: validation.issues,
|
|
94
|
+
error: validation.error,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
reportValidationResults(results, workspaceRoot, markdownRoot, validationStartedAt);
|
|
100
|
+
if (results.some((result) => result.error || result.issues.length > 0 || !result.conforms)) {
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
async function discoverMarkdownFiles(root: string): Promise<string[]> {
|
|
106
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
107
|
+
const markdownFiles: string[] = [];
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const fullPath = path.join(root, entry.name);
|
|
110
|
+
if (entry.isDirectory()) {
|
|
111
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
markdownFiles.push(...await discoverMarkdownFiles(fullPath));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) {
|
|
118
|
+
markdownFiles.push(fullPath);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return markdownFiles.sort();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveContextModelUri(documentFile: string, contextUri: string | undefined, workspaceRoot: string): string | undefined {
|
|
125
|
+
if (!contextUri) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
const trimmed = contextUri.trim();
|
|
129
|
+
if (!trimmed) {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
if (trimmed.startsWith('workspace:/')) {
|
|
133
|
+
const resolved = resolveWorkspacePath(workspaceRoot, trimmed);
|
|
134
|
+
return resolved ? url.pathToFileURL(resolved).toString() : undefined;
|
|
135
|
+
}
|
|
136
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed)) {
|
|
137
|
+
return trimmed;
|
|
138
|
+
}
|
|
139
|
+
if (trimmed.startsWith('/')) {
|
|
140
|
+
return url.pathToFileURL(path.resolve(workspaceRoot, trimmed.replace(/^\/+/, ''))).toString();
|
|
141
|
+
}
|
|
142
|
+
return url.pathToFileURL(path.resolve(path.dirname(documentFile), trimmed)).toString();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function resolveWorkspacePath(workspaceRoot: string, href: string): string | undefined {
|
|
146
|
+
const trimmed = href.trim();
|
|
147
|
+
if (!trimmed.startsWith('workspace:/')) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
return path.resolve(workspaceRoot, trimmed.slice('workspace:/'.length));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function reportValidationResults(
|
|
154
|
+
results: ReadonlyArray<MarkdownValidationResult>,
|
|
155
|
+
workspaceRoot: string,
|
|
156
|
+
markdownRoot: string,
|
|
157
|
+
startedAt: number
|
|
158
|
+
): void {
|
|
159
|
+
if (results.length === 0) {
|
|
160
|
+
console.log(chalk.yellow(`No table-editor blocks found under ${path.relative(workspaceRoot, markdownRoot) || markdownRoot}.`));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let issueCount = 0;
|
|
165
|
+
let errorCount = 0;
|
|
166
|
+
let failingBlockCount = 0;
|
|
167
|
+
for (const result of results) {
|
|
168
|
+
const relativeFile = path.relative(markdownRoot, result.markdownFile) || path.basename(result.markdownFile);
|
|
169
|
+
const location = relativeFile;
|
|
170
|
+
if (result.error) {
|
|
171
|
+
failingBlockCount += 1;
|
|
172
|
+
errorCount += 1;
|
|
173
|
+
console.log(chalk.red(`Violation: ${location}: ${result.error}`));
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const hasIssues = result.issues.length > 0 || !result.conforms;
|
|
177
|
+
if (!hasIssues) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
failingBlockCount += 1;
|
|
181
|
+
for (const issue of result.issues) {
|
|
182
|
+
issueCount += 1;
|
|
183
|
+
const elementLabel = issue.focusNode ? shortIri(issue.focusNode) : '-';
|
|
184
|
+
const propertyLabel = issue.propertyName ?? (issue.path ? shortIri(issue.path) : '-');
|
|
185
|
+
const severityLabel = shortIri(issue.severity || 'Violation');
|
|
186
|
+
console.log(chalk.red(`${severityLabel}: ${location} element=${elementLabel} property=${propertyLabel}: ${describeIssue(issue)}`));
|
|
187
|
+
}
|
|
188
|
+
if (!result.conforms && result.issues.length === 0) {
|
|
189
|
+
issueCount += 1;
|
|
190
|
+
console.log(chalk.red(`Violation: ${location}: SHACL validation failed without detailed issues.`));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const duration = formatDuration(Date.now() - startedAt);
|
|
195
|
+
const summary = `${results.length} table-editor block(s) scanned [${duration}]`;
|
|
196
|
+
if (issueCount === 0 && errorCount === 0) {
|
|
197
|
+
console.log(chalk.green(`validate: ${summary}`));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
console.log(chalk.red(`validate: ${summary}; ${failingBlockCount} failing block(s), ${issueCount} issue(s), ${errorCount} error(s)`));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function shortIri(value: string): string {
|
|
204
|
+
const normalized = normalizeNamespace(value);
|
|
205
|
+
const hashIndex = normalized.lastIndexOf('#');
|
|
206
|
+
if (hashIndex >= 0 && hashIndex < normalized.length - 1) {
|
|
207
|
+
return normalized.slice(hashIndex + 1);
|
|
208
|
+
}
|
|
209
|
+
const slashIndex = normalized.lastIndexOf('/');
|
|
210
|
+
if (slashIndex >= 0 && slashIndex < normalized.length - 1) {
|
|
211
|
+
return normalized.slice(slashIndex + 1);
|
|
212
|
+
}
|
|
213
|
+
return normalized;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function describeIssue(issue: ShaclValidationIssue): string {
|
|
217
|
+
const message = issue.message.trim();
|
|
218
|
+
if (message && message !== 'Validation issue.') {
|
|
219
|
+
return message;
|
|
220
|
+
}
|
|
221
|
+
const source = issue.source ? shortIri(issue.source) : undefined;
|
|
222
|
+
if (source) {
|
|
223
|
+
return source;
|
|
224
|
+
}
|
|
225
|
+
return 'Validation issue.';
|
|
226
|
+
}
|
package/src/main.ts
ADDED
package/src/update.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
const CLI_PACKAGE_NAME = '@oml/cli';
|
|
6
|
+
const CLI_PACKAGE_URL = 'https://registry.npmjs.org/@oml%2fcli';
|
|
7
|
+
const UPDATE_CHECK_TIMEOUT_MS = 1_200;
|
|
8
|
+
|
|
9
|
+
interface NpmPackageResponse {
|
|
10
|
+
'dist-tags'?: {
|
|
11
|
+
latest?: unknown
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ParsedVersion {
|
|
16
|
+
major: number
|
|
17
|
+
minor: number
|
|
18
|
+
patch: number
|
|
19
|
+
prerelease: string | undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function notifyIfCliUpdateAvailable(currentVersion: string): Promise<void> {
|
|
23
|
+
if (!shouldCheckForUpdates()) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const latestVersion = await fetchLatestCliVersion();
|
|
29
|
+
if (latestVersion === undefined || compareVersions(latestVersion, currentVersion) <= 0) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const updateCommand = `npm install -g ${CLI_PACKAGE_NAME}@latest`;
|
|
34
|
+
console.error(chalk.yellow(
|
|
35
|
+
`A newer ${CLI_PACKAGE_NAME} release is available (${currentVersion} -> ${latestVersion}).`
|
|
36
|
+
));
|
|
37
|
+
console.error(chalk.yellow(`Update with: ${updateCommand}`));
|
|
38
|
+
} catch {
|
|
39
|
+
// Ignore update check failures so the CLI command remains unaffected.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function shouldCheckForUpdates(): boolean {
|
|
44
|
+
if (process.env.CI !== undefined) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (process.env.OML_NO_UPDATE_NOTIFIER === '1') {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return process.stderr.isTTY;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function fetchLatestCliVersion(): Promise<string | undefined> {
|
|
54
|
+
const response = await fetch(CLI_PACKAGE_URL, {
|
|
55
|
+
headers: { accept: 'application/json' },
|
|
56
|
+
signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS)
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const body = await response.json() as NpmPackageResponse;
|
|
63
|
+
const latest = body['dist-tags']?.latest;
|
|
64
|
+
return typeof latest === 'string' ? latest : undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function compareVersions(leftVersion: string, rightVersion: string): number {
|
|
68
|
+
const left = parseVersion(leftVersion);
|
|
69
|
+
const right = parseVersion(rightVersion);
|
|
70
|
+
|
|
71
|
+
if (left.major !== right.major) {
|
|
72
|
+
return left.major - right.major;
|
|
73
|
+
}
|
|
74
|
+
if (left.minor !== right.minor) {
|
|
75
|
+
return left.minor - right.minor;
|
|
76
|
+
}
|
|
77
|
+
if (left.patch !== right.patch) {
|
|
78
|
+
return left.patch - right.patch;
|
|
79
|
+
}
|
|
80
|
+
if (left.prerelease === right.prerelease) {
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
if (left.prerelease === undefined) {
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
if (right.prerelease === undefined) {
|
|
87
|
+
return -1;
|
|
88
|
+
}
|
|
89
|
+
return left.prerelease.localeCompare(right.prerelease);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseVersion(version: string): ParsedVersion {
|
|
93
|
+
const match = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<prerelease>[0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(version);
|
|
94
|
+
if (match?.groups === undefined) {
|
|
95
|
+
throw new Error(`Invalid CLI version '${version}'.`);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
major: Number.parseInt(match.groups.major, 10),
|
|
99
|
+
minor: Number.parseInt(match.groups.minor, 10),
|
|
100
|
+
patch: Number.parseInt(match.groups.patch, 10),
|
|
101
|
+
prerelease: match.groups.prerelease
|
|
102
|
+
};
|
|
103
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import type { AstNode, LangiumCoreServices, LangiumDocument } from 'langium';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import { URI } from 'langium';
|
|
8
|
+
|
|
9
|
+
export async function extractDocument(fileName: string, services: LangiumCoreServices, workspaceRoot?: string): Promise<LangiumDocument> {
|
|
10
|
+
const extensions = services.LanguageMetaData.fileExtensions;
|
|
11
|
+
if (!extensions.includes(path.extname(fileName))) {
|
|
12
|
+
console.error(chalk.yellow(`Please choose a file with one of these extensions: ${extensions}.`));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(fileName)) {
|
|
17
|
+
console.error(chalk.red(`File ${fileName} does not exist.`));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (workspaceRoot) {
|
|
22
|
+
const resolvedRoot = path.resolve(workspaceRoot);
|
|
23
|
+
await services.shared.workspace.WorkspaceManager.initializeWorkspace([
|
|
24
|
+
{ uri: URI.file(resolvedRoot).toString(), name: path.basename(resolvedRoot) }
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const document = await services.shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName)));
|
|
29
|
+
await services.shared.workspace.DocumentBuilder.build([document], { validation: true });
|
|
30
|
+
|
|
31
|
+
const diagnostics = document.diagnostics ?? [];
|
|
32
|
+
const validationErrors = diagnostics.filter(e => e.severity === 1);
|
|
33
|
+
if (validationErrors.length > 0) {
|
|
34
|
+
console.error(chalk.red(`There are validation errors in ${path.resolve(fileName)}:`));
|
|
35
|
+
for (const validationError of validationErrors) {
|
|
36
|
+
console.error(chalk.red(
|
|
37
|
+
`line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]`
|
|
38
|
+
));
|
|
39
|
+
}
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const validationWarnings = diagnostics.filter(e => e.severity === 2);
|
|
44
|
+
if (validationWarnings.length > 0) {
|
|
45
|
+
console.warn(chalk.yellow(`Validation warnings in ${path.resolve(fileName)}:`));
|
|
46
|
+
for (const validationWarning of validationWarnings) {
|
|
47
|
+
console.warn(chalk.yellow(
|
|
48
|
+
`line ${validationWarning.range.start.line + 1}: ${validationWarning.message} [${document.textDocument.getText(validationWarning.range)}]`
|
|
49
|
+
));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return document;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function extractAstNode<T extends AstNode>(fileName: string, services: LangiumCoreServices, workspaceRoot?: string): Promise<T> {
|
|
57
|
+
return (await extractDocument(fileName, services, workspaceRoot)).parseResult?.value as T;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface FilePathData {
|
|
61
|
+
destination: string,
|
|
62
|
+
name: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function extractDestinationAndName(filePath: string, destination: string | undefined): FilePathData {
|
|
66
|
+
filePath = path.basename(filePath, path.extname(filePath)).replace(/[.-]/g, '');
|
|
67
|
+
return {
|
|
68
|
+
destination: destination ?? path.join(path.dirname(filePath), 'generated'),
|
|
69
|
+
name: path.basename(filePath)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function formatDuration(elapsedMs: number): string {
|
|
74
|
+
if (elapsedMs < 1_000) {
|
|
75
|
+
return `${elapsedMs}ms`;
|
|
76
|
+
}
|
|
77
|
+
if (elapsedMs < 60_000) {
|
|
78
|
+
return `${(elapsedMs / 1_000).toFixed(1)}s`;
|
|
79
|
+
}
|
|
80
|
+
const minutes = Math.floor(elapsedMs / 60_000);
|
|
81
|
+
const seconds = ((elapsedMs % 60_000) / 1_000).toFixed(1);
|
|
82
|
+
return `${minutes}m ${seconds}s`;
|
|
83
|
+
}
|