@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
package/src/auth.ts
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import * as fs from 'node:fs/promises';
|
|
5
|
+
import * as os from 'node:os';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_WHITELIST_URL = 'https://www.modelware.io/oml-code/auth/permissions.json';
|
|
9
|
+
const CACHE_DURATION_MS = 5 * 60 * 1000;
|
|
10
|
+
const GITHUB_DEVICE_CODE_URL = 'https://github.com/login/device/code';
|
|
11
|
+
const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token';
|
|
12
|
+
const GITHUB_USER_URL = 'https://api.github.com/user';
|
|
13
|
+
const DEFAULT_GITHUB_CLIENT_ID = 'Ov23liQkHYczdOAvHp5P';
|
|
14
|
+
|
|
15
|
+
type Provider = 'github';
|
|
16
|
+
|
|
17
|
+
type StoredSession = {
|
|
18
|
+
provider: Provider;
|
|
19
|
+
userId: string;
|
|
20
|
+
userLabel?: string;
|
|
21
|
+
signedInAt: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type LoginOptions = {
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type AuthStatus = {
|
|
28
|
+
session: StoredSession;
|
|
29
|
+
authorized: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export class OmlCliAuthService {
|
|
33
|
+
private whitelistCache: Set<string> | null = null;
|
|
34
|
+
private whitelistCacheTime = 0;
|
|
35
|
+
|
|
36
|
+
async login(options: LoginOptions): Promise<void> {
|
|
37
|
+
const session = await this.authenticate();
|
|
38
|
+
|
|
39
|
+
const authorized = await this.isAuthorized(session);
|
|
40
|
+
if (!authorized) {
|
|
41
|
+
throw new Error('Sign in succeeded, but this account is not authorized.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await writeSession(session);
|
|
45
|
+
const summary = session.userLabel ? `${session.userLabel} (${session.userId})` : session.userId;
|
|
46
|
+
console.error(chalk.green(`Signed in as ${summary} via ${session.provider}.`));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async logout(): Promise<void> {
|
|
50
|
+
await deleteSession();
|
|
51
|
+
this.clearWhitelistCache();
|
|
52
|
+
console.error(chalk.green('Signed out.'));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async whoami(): Promise<void> {
|
|
56
|
+
const session = await readSession();
|
|
57
|
+
if (!session) {
|
|
58
|
+
console.error(chalk.yellow('Not signed in.'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const authorized = await this.isAuthorized(session);
|
|
62
|
+
console.error(`Provider: ${session.provider}`);
|
|
63
|
+
console.error(`User ID: ${session.userId}`);
|
|
64
|
+
console.error(`User label: ${session.userLabel ?? '(not set)'}`);
|
|
65
|
+
console.error(`Signed in at: ${session.signedInAt}`);
|
|
66
|
+
console.error(`Authorized: ${authorized ? 'yes' : 'no'}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async ensureAuthenticated(operationName: string): Promise<AuthStatus> {
|
|
70
|
+
const session = await readSession();
|
|
71
|
+
if (!session) {
|
|
72
|
+
throw new Error(`${operationName} requires authentication. Run 'oml login' first.`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const authorized = await this.isAuthorized(session);
|
|
76
|
+
if (!authorized) {
|
|
77
|
+
throw new Error(`${operationName} requires authorization. Your account is not authorized.`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { session, authorized };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private async isAuthorized(session: StoredSession): Promise<boolean> {
|
|
84
|
+
const whitelist = await this.fetchWhitelist();
|
|
85
|
+
if (!whitelist) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
const userId = session.userId.toLowerCase();
|
|
89
|
+
return whitelist.has(userId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async fetchWhitelist(): Promise<Set<string> | null> {
|
|
93
|
+
const whitelistUrl = process.env.OML_AUTH_WHITELIST_URL ?? DEFAULT_WHITELIST_URL;
|
|
94
|
+
if (!whitelistUrl) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
if (this.whitelistCache && (now - this.whitelistCacheTime) < CACHE_DURATION_MS) {
|
|
100
|
+
return this.whitelistCache;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(whitelistUrl);
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
107
|
+
}
|
|
108
|
+
const data = await response.json();
|
|
109
|
+
const userList = extractWhitelistEntries(data);
|
|
110
|
+
this.whitelistCache = new Set(userList.map((id) => String(id).toLowerCase()));
|
|
111
|
+
this.whitelistCacheTime = now;
|
|
112
|
+
return this.whitelistCache;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error(chalk.yellow(
|
|
115
|
+
`OML: Could not verify authorization. ${error instanceof Error ? error.message : String(error)}`
|
|
116
|
+
));
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private clearWhitelistCache(): void {
|
|
122
|
+
this.whitelistCache = null;
|
|
123
|
+
this.whitelistCacheTime = 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async authenticate(): Promise<StoredSession> {
|
|
127
|
+
return authenticateWithGitHub();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractWhitelistEntries(data: unknown): string[] {
|
|
132
|
+
if (Array.isArray(data)) {
|
|
133
|
+
return data.map((value) => String(value));
|
|
134
|
+
}
|
|
135
|
+
if (typeof data === 'object' && data !== null) {
|
|
136
|
+
const record = data as { allowedUsers?: unknown; users?: unknown };
|
|
137
|
+
if (Array.isArray(record.allowedUsers)) {
|
|
138
|
+
return record.allowedUsers.map((value) => String(value));
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(record.users)) {
|
|
141
|
+
return record.users.map((value) => String(value));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
throw new Error('Invalid whitelist format. Expected array or object with "allowedUsers" or "users" property.');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function readSession(): Promise<StoredSession | undefined> {
|
|
148
|
+
try {
|
|
149
|
+
const content = await fs.readFile(getSessionPath(), 'utf-8');
|
|
150
|
+
const data = JSON.parse(content) as Partial<StoredSession>;
|
|
151
|
+
if (!data.userId || !data.provider || !data.signedInAt) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
if (data.provider !== 'github' && data.provider !== 'microsoft') {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
provider: data.provider,
|
|
159
|
+
userId: data.userId,
|
|
160
|
+
userLabel: data.userLabel,
|
|
161
|
+
signedInAt: data.signedInAt
|
|
162
|
+
};
|
|
163
|
+
} catch {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function writeSession(session: StoredSession): Promise<void> {
|
|
169
|
+
const sessionPath = getSessionPath();
|
|
170
|
+
await fs.mkdir(path.dirname(sessionPath), { recursive: true });
|
|
171
|
+
await fs.writeFile(sessionPath, `${JSON.stringify(session, null, 2)}\n`, 'utf-8');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function deleteSession(): Promise<void> {
|
|
175
|
+
try {
|
|
176
|
+
await fs.unlink(getSessionPath());
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getSessionPath(): string {
|
|
185
|
+
return path.join(os.homedir(), '.oml', 'auth.json');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function authenticateWithGitHub(): Promise<StoredSession> {
|
|
189
|
+
const clientId = resolveClientId();
|
|
190
|
+
const params = new URLSearchParams({
|
|
191
|
+
client_id: clientId,
|
|
192
|
+
scope: 'read:user'
|
|
193
|
+
});
|
|
194
|
+
const response = await fetch(GITHUB_DEVICE_CODE_URL, {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: {
|
|
197
|
+
accept: 'application/json',
|
|
198
|
+
'content-type': 'application/x-www-form-urlencoded'
|
|
199
|
+
},
|
|
200
|
+
body: params
|
|
201
|
+
});
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new Error(`GitHub device authorization failed: HTTP ${response.status} ${response.statusText}`);
|
|
204
|
+
}
|
|
205
|
+
const device = await response.json() as GitHubDeviceCodeResponse;
|
|
206
|
+
if (!device.device_code || !device.user_code || !device.verification_uri) {
|
|
207
|
+
throw new Error('GitHub device authorization response was incomplete.');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
printDeviceFlowInstructions('GitHub', device.verification_uri, device.user_code);
|
|
211
|
+
const token = await pollForGitHubAccessToken(clientId, device);
|
|
212
|
+
const userResponse = await fetch(GITHUB_USER_URL, {
|
|
213
|
+
headers: {
|
|
214
|
+
accept: 'application/vnd.github+json',
|
|
215
|
+
authorization: `Bearer ${token}`
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
if (!userResponse.ok) {
|
|
219
|
+
throw new Error(`GitHub user lookup failed: HTTP ${userResponse.status} ${userResponse.statusText}`);
|
|
220
|
+
}
|
|
221
|
+
const user = await userResponse.json() as GitHubUserResponse;
|
|
222
|
+
if (!user.login) {
|
|
223
|
+
throw new Error('GitHub user lookup did not return a login name.');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
provider: 'github',
|
|
228
|
+
userId: user.login,
|
|
229
|
+
userLabel: user.login,
|
|
230
|
+
signedInAt: new Date().toISOString()
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function printDeviceFlowInstructions(providerName: string, verificationUri: string, userCode: string): void {
|
|
235
|
+
console.error(chalk.cyan(`${providerName} sign-in required.`));
|
|
236
|
+
console.error(`Open: ${verificationUri}`);
|
|
237
|
+
console.error(`Code: ${userCode}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function pollForGitHubAccessToken(clientId: string, device: GitHubDeviceCodeResponse): Promise<string> {
|
|
241
|
+
const deviceCode = device.device_code;
|
|
242
|
+
if (!deviceCode) {
|
|
243
|
+
throw new Error('GitHub device authorization response was incomplete.');
|
|
244
|
+
}
|
|
245
|
+
let intervalSeconds = Math.max(1, device.interval ?? 5);
|
|
246
|
+
while (true) {
|
|
247
|
+
await delay(intervalSeconds * 1_000);
|
|
248
|
+
const params = new URLSearchParams({
|
|
249
|
+
client_id: clientId,
|
|
250
|
+
device_code: deviceCode,
|
|
251
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
|
|
252
|
+
});
|
|
253
|
+
const response = await fetch(GITHUB_TOKEN_URL, {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: {
|
|
256
|
+
accept: 'application/json',
|
|
257
|
+
'content-type': 'application/x-www-form-urlencoded'
|
|
258
|
+
},
|
|
259
|
+
body: params
|
|
260
|
+
});
|
|
261
|
+
if (!response.ok) {
|
|
262
|
+
throw new Error(`GitHub token exchange failed: HTTP ${response.status} ${response.statusText}`);
|
|
263
|
+
}
|
|
264
|
+
const token = await response.json() as GitHubTokenResponse;
|
|
265
|
+
if (token.access_token) {
|
|
266
|
+
return token.access_token;
|
|
267
|
+
}
|
|
268
|
+
if (token.error === 'authorization_pending') {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (token.error === 'slow_down') {
|
|
272
|
+
intervalSeconds += 5;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (token.error === 'expired_token') {
|
|
276
|
+
throw new Error('GitHub device code expired before authorization completed.');
|
|
277
|
+
}
|
|
278
|
+
if (token.error === 'access_denied') {
|
|
279
|
+
throw new Error('GitHub sign-in was denied.');
|
|
280
|
+
}
|
|
281
|
+
throw new Error(token.error_description ?? token.error ?? 'GitHub sign-in failed.');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveClientId(): string {
|
|
286
|
+
const configured = process.env.OML_AUTH_GITHUB_CLIENT_ID?.trim() || DEFAULT_GITHUB_CLIENT_ID;
|
|
287
|
+
if (configured) {
|
|
288
|
+
return configured;
|
|
289
|
+
}
|
|
290
|
+
throw new Error(
|
|
291
|
+
'GitHub login is not configured. Set OML_AUTH_GITHUB_CLIENT_ID or embed DEFAULT_GITHUB_CLIENT_ID in packages/cli/src/auth.ts.'
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function delay(ms: number): Promise<void> {
|
|
296
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
type GitHubDeviceCodeResponse = {
|
|
300
|
+
device_code?: string;
|
|
301
|
+
user_code?: string;
|
|
302
|
+
verification_uri?: string;
|
|
303
|
+
interval?: number;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
type GitHubTokenResponse = {
|
|
307
|
+
access_token?: string;
|
|
308
|
+
error?: string;
|
|
309
|
+
error_description?: string;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
type GitHubUserResponse = {
|
|
313
|
+
id?: number;
|
|
314
|
+
login?: string;
|
|
315
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import type { MdExecuteBlocksParams, MdExecuteBlocksResult } from '@oml/markdown';
|
|
4
|
+
|
|
5
|
+
export interface ValidateSummary {
|
|
6
|
+
filesChecked: number;
|
|
7
|
+
warnings: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface MarkdownRenderResult {
|
|
11
|
+
renderedHtml: string;
|
|
12
|
+
contextUri?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CliBackendOptions {
|
|
16
|
+
owlOutputRoot?: string;
|
|
17
|
+
rdfFormat?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CliBackend {
|
|
21
|
+
validate(fileName: string | undefined, workspaceRoot: string): Promise<ValidateSummary>;
|
|
22
|
+
renderMarkdown(markdown: string): Promise<MarkdownRenderResult>;
|
|
23
|
+
executeMarkdownBlocks(params: MdExecuteBlocksParams, workspaceRoot: string): Promise<MdExecuteBlocksResult>;
|
|
24
|
+
dispose(): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { DirectBackend } from './direct-backend.js';
|
|
4
|
+
import type { CliBackend, CliBackendOptions } from './backend-types.js';
|
|
5
|
+
|
|
6
|
+
export function createBackend(options: CliBackendOptions = {}): CliBackend {
|
|
7
|
+
return new DirectBackend(options);
|
|
8
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { createOwlServices } from '@oml/owl';
|
|
4
|
+
import { MarkdownHandlerRegistry, MarkdownPreviewRuntime } from '@oml/markdown';
|
|
5
|
+
import { MarkdownExecutor, type MdExecuteBlocksParams, type MdExecuteBlocksResult } from '@oml/markdown';
|
|
6
|
+
import { NodeFileSystem } from 'langium/node';
|
|
7
|
+
import { URI } from 'langium';
|
|
8
|
+
import * as fs from 'node:fs/promises';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { extractDocument } from '../util.js';
|
|
11
|
+
import type { CliBackend, CliBackendOptions, MarkdownRenderResult, ValidateSummary } from './backend-types.js';
|
|
12
|
+
import { loadPreparedDatasetFromOutput, normalizeFormatExtension } from './reasoned-output.js';
|
|
13
|
+
|
|
14
|
+
export class DirectBackend implements CliBackend {
|
|
15
|
+
private readonly markdownRuntime = new MarkdownPreviewRuntime(new MarkdownHandlerRegistry());
|
|
16
|
+
private readonly owlOutputRoot?: string;
|
|
17
|
+
private readonly rdfFormat: string;
|
|
18
|
+
private servicesBundle?: ReturnType<typeof createOwlServices>;
|
|
19
|
+
private initializedWorkspaceRoot?: string;
|
|
20
|
+
private loadedDatasetKey?: string;
|
|
21
|
+
|
|
22
|
+
constructor(options: CliBackendOptions = {}) {
|
|
23
|
+
this.owlOutputRoot = options.owlOutputRoot ? path.resolve(options.owlOutputRoot) : undefined;
|
|
24
|
+
this.rdfFormat = normalizeFormatExtension(options.rdfFormat);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async validate(fileName: string | undefined, workspaceRoot: string): Promise<ValidateSummary> {
|
|
28
|
+
const services = await this.ensureServices(workspaceRoot);
|
|
29
|
+
|
|
30
|
+
if (fileName) {
|
|
31
|
+
const document = await extractDocument(fileName, services, undefined);
|
|
32
|
+
const warnings = (document.diagnostics ?? []).filter((d) => d.severity === 2).length;
|
|
33
|
+
return { filesChecked: 1, warnings };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const files = await findOmlFiles(workspaceRoot);
|
|
37
|
+
let warningCount = 0;
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const document = await extractDocument(file, services, undefined);
|
|
40
|
+
warningCount += (document.diagnostics ?? []).filter((d) => d.severity === 2).length;
|
|
41
|
+
}
|
|
42
|
+
return { filesChecked: files.length, warnings: warningCount };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async renderMarkdown(markdown: string): Promise<MarkdownRenderResult> {
|
|
46
|
+
const rendered = this.markdownRuntime.prepare(markdown);
|
|
47
|
+
return {
|
|
48
|
+
renderedHtml: rendered.renderedHtml,
|
|
49
|
+
contextUri: rendered.contextUri
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async executeMarkdownBlocks(params: MdExecuteBlocksParams, workspaceRoot: string): Promise<MdExecuteBlocksResult> {
|
|
54
|
+
const services = await this.ensureServices(workspaceRoot);
|
|
55
|
+
await this.ensurePreparedDataset(workspaceRoot, services);
|
|
56
|
+
const reasoningService = services.reasoning.ReasoningService as any;
|
|
57
|
+
const executor = new MarkdownExecutor({
|
|
58
|
+
ensureContext: (modelUri) => reasoningService.ensureQueryContext(modelUri),
|
|
59
|
+
resolveContextIri: (modelUri) => reasoningService.getContextIri(modelUri),
|
|
60
|
+
query: (modelUri, sparql) => reasoningService.getSparqlService().query(modelUri, sparql),
|
|
61
|
+
construct: (modelUri, sparql) => reasoningService.getSparqlService().construct(modelUri, sparql),
|
|
62
|
+
});
|
|
63
|
+
return executor.executeBlocks(params);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async dispose(): Promise<void> {
|
|
67
|
+
this.servicesBundle = undefined;
|
|
68
|
+
this.initializedWorkspaceRoot = undefined;
|
|
69
|
+
this.loadedDatasetKey = undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private async ensureServices(workspaceRoot: string): Promise<ReturnType<typeof createOwlServices>['Oml']> {
|
|
73
|
+
const resolvedRoot = path.resolve(workspaceRoot);
|
|
74
|
+
if (!this.servicesBundle || this.initializedWorkspaceRoot !== resolvedRoot) {
|
|
75
|
+
this.servicesBundle = createOwlServices(NodeFileSystem);
|
|
76
|
+
this.initializedWorkspaceRoot = resolvedRoot;
|
|
77
|
+
await this.servicesBundle.Oml.shared.workspace.WorkspaceManager.initializeWorkspace([
|
|
78
|
+
{ uri: URI.file(resolvedRoot).toString(), name: path.basename(resolvedRoot) }
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
return this.servicesBundle.Oml;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async ensurePreparedDataset(workspaceRoot: string, services: ReturnType<typeof createOwlServices>['Oml']): Promise<void> {
|
|
85
|
+
if (!this.owlOutputRoot) {
|
|
86
|
+
throw new Error('CLI markdown execution requires a prepared RDF output folder.');
|
|
87
|
+
}
|
|
88
|
+
const datasetKey = `${this.initializedWorkspaceRoot ?? ''}|${this.owlOutputRoot}|${this.rdfFormat}`;
|
|
89
|
+
if (this.loadedDatasetKey === datasetKey) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const prepared = await loadPreparedDatasetFromOutput(path.resolve(workspaceRoot), this.owlOutputRoot, this.rdfFormat);
|
|
93
|
+
services.reasoning.ReasoningService.loadPreparedDataset(prepared);
|
|
94
|
+
this.loadedDatasetKey = datasetKey;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function findOmlFiles(root: string): Promise<string[]> {
|
|
99
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
100
|
+
const results: string[] = [];
|
|
101
|
+
|
|
102
|
+
for (const entry of entries) {
|
|
103
|
+
const fullPath = path.join(root, entry.name);
|
|
104
|
+
if (entry.isDirectory()) {
|
|
105
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
results.push(...await findOmlFiles(fullPath));
|
|
109
|
+
} else if (entry.isFile() && path.extname(entry.name) === '.oml') {
|
|
110
|
+
results.push(fullPath);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}
|