@oml/cli 0.14.16 → 0.15.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/out/auth/auth.d.ts +10 -0
- package/out/auth/auth.js +225 -72
- package/out/auth/auth.js.map +1 -1
- package/out/cli.js +15 -18
- package/out/cli.js.map +1 -1
- package/out/commands/export.d.ts +0 -1
- package/out/commands/export.js +0 -1
- package/out/commands/export.js.map +1 -1
- package/out/commands/lint.js +2 -1
- package/out/commands/lint.js.map +1 -1
- package/out/commands/reason.d.ts +0 -1
- package/out/commands/reason.js +20 -7
- package/out/commands/reason.js.map +1 -1
- package/out/commands/render.d.ts +0 -1
- package/out/commands/render.js.map +1 -1
- package/out/commands/server/actions.d.ts +8 -0
- package/out/commands/server/actions.js +41 -12
- package/out/commands/server/actions.js.map +1 -1
- package/out/commands/validate.js +9 -6
- package/out/commands/validate.js.map +1 -1
- package/package.json +6 -4
- package/src/auth/auth.ts +238 -88
- package/src/cli.ts +22 -27
- package/src/commands/export.ts +0 -2
- package/src/commands/lint.ts +2 -1
- package/src/commands/reason.ts +21 -10
- package/src/commands/render.ts +0 -1
- package/src/commands/server/actions.ts +55 -15
- package/src/commands/validate.ts +8 -6
|
@@ -17,12 +17,19 @@ const SHUTDOWN_TIMEOUT_MS = 3000;
|
|
|
17
17
|
const POLL_INTERVAL_MS = 100;
|
|
18
18
|
const execFileAsync = promisify(execFile);
|
|
19
19
|
|
|
20
|
+
type EntitlementCache = {
|
|
21
|
+
expiry: number;
|
|
22
|
+
featureIds: string[];
|
|
23
|
+
token?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
20
26
|
type StartServerOptions = {
|
|
21
27
|
port?: number | string;
|
|
22
28
|
workspace?: string;
|
|
23
29
|
auth?: {
|
|
24
30
|
accessToken: string;
|
|
25
31
|
};
|
|
32
|
+
entitlementCache?: EntitlementCache;
|
|
26
33
|
};
|
|
27
34
|
|
|
28
35
|
type ServerStatePaths = {
|
|
@@ -34,7 +41,7 @@ type ServerStatePaths = {
|
|
|
34
41
|
type ServerLockState = {
|
|
35
42
|
pid: number;
|
|
36
43
|
port: number;
|
|
37
|
-
owner?: '
|
|
44
|
+
owner?: 'cli' | 'extension';
|
|
38
45
|
workspaceRoot?: string;
|
|
39
46
|
};
|
|
40
47
|
|
|
@@ -63,6 +70,7 @@ function getServerStatePaths(workspace?: string): ServerStatePaths {
|
|
|
63
70
|
|
|
64
71
|
async function cleanupStateFile(paths: ServerStatePaths): Promise<void> {
|
|
65
72
|
await fs.rm(paths.lockFile, { force: true });
|
|
73
|
+
await fs.rm(paths.dir, { recursive: true, force: true });
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
function parseServerLock(raw: string): ServerLockState | undefined {
|
|
@@ -78,7 +86,7 @@ function parseServerLock(raw: string): ServerLockState | undefined {
|
|
|
78
86
|
if (pidInt <= 0 || portInt <= 0 || portInt > 65535) {
|
|
79
87
|
return undefined;
|
|
80
88
|
}
|
|
81
|
-
const owner = parsed.owner === '
|
|
89
|
+
const owner = parsed.owner === 'cli' || parsed.owner === 'extension'
|
|
82
90
|
? parsed.owner
|
|
83
91
|
: undefined;
|
|
84
92
|
const workspaceRoot = typeof parsed.workspaceRoot === 'string' && parsed.workspaceRoot.trim().length > 0
|
|
@@ -105,7 +113,7 @@ async function readServerLock(lockFile: string): Promise<ServerLockState | undef
|
|
|
105
113
|
type RunningServer = {
|
|
106
114
|
pid: number;
|
|
107
115
|
port: number;
|
|
108
|
-
owner: '
|
|
116
|
+
owner: 'cli' | 'extension' | 'unknown';
|
|
109
117
|
workspaceRoot?: string;
|
|
110
118
|
lockFile: string;
|
|
111
119
|
};
|
|
@@ -128,6 +136,7 @@ async function listRunningServers(): Promise<RunningServer[]> {
|
|
|
128
136
|
}
|
|
129
137
|
if (!isProcessAlive(state.pid)) {
|
|
130
138
|
await fs.rm(lockFile, { force: true });
|
|
139
|
+
await fs.rm(path.dirname(lockFile), { recursive: true, force: true });
|
|
131
140
|
continue;
|
|
132
141
|
}
|
|
133
142
|
servers.push({
|
|
@@ -257,7 +266,7 @@ function normalizeStartPort(value: number | string | undefined): number {
|
|
|
257
266
|
return intPort;
|
|
258
267
|
}
|
|
259
268
|
|
|
260
|
-
async function inspectListeningProcess(port: number): Promise<string> {
|
|
269
|
+
async function inspectListeningProcess(port: number): Promise<string | undefined> {
|
|
261
270
|
const args = ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN'];
|
|
262
271
|
try {
|
|
263
272
|
const { stdout } = await execFileAsync('lsof', args);
|
|
@@ -265,7 +274,7 @@ async function inspectListeningProcess(port: number): Promise<string> {
|
|
|
265
274
|
} catch (error) {
|
|
266
275
|
const failed = error as NodeJS.ErrnoException & { stdout?: string };
|
|
267
276
|
if (failed?.code === 'ENOENT') {
|
|
268
|
-
|
|
277
|
+
return undefined;
|
|
269
278
|
}
|
|
270
279
|
return typeof failed?.stdout === 'string' ? failed.stdout.trim() : '';
|
|
271
280
|
}
|
|
@@ -279,6 +288,7 @@ function spawnServerProcess(
|
|
|
279
288
|
options: {
|
|
280
289
|
workspace?: string;
|
|
281
290
|
auth?: { accessToken: string };
|
|
291
|
+
entitlementCache?: EntitlementCache;
|
|
282
292
|
},
|
|
283
293
|
): ChildProcess {
|
|
284
294
|
const args = [serverMainScript, `--port=${port}`];
|
|
@@ -289,6 +299,13 @@ function spawnServerProcess(
|
|
|
289
299
|
if (options.auth?.accessToken) {
|
|
290
300
|
args.push(`--token=${options.auth.accessToken}`);
|
|
291
301
|
}
|
|
302
|
+
if (options.entitlementCache) {
|
|
303
|
+
args.push(`--entitlement-expiry=${options.entitlementCache.expiry}`);
|
|
304
|
+
args.push(`--entitlement-features=${JSON.stringify(options.entitlementCache.featureIds)}`);
|
|
305
|
+
if (options.entitlementCache.token) {
|
|
306
|
+
args.push(`--entitlement-token=${options.entitlementCache.token}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
292
309
|
|
|
293
310
|
return spawn(process.execPath, args, {
|
|
294
311
|
detached,
|
|
@@ -352,6 +369,7 @@ async function serverStartDetached(
|
|
|
352
369
|
const child = spawnServerProcess(serverMainScript, port, true, 'ignore', {
|
|
353
370
|
workspace: options.workspace,
|
|
354
371
|
auth: options.auth,
|
|
372
|
+
entitlementCache: options.entitlementCache,
|
|
355
373
|
});
|
|
356
374
|
if (!child.pid) {
|
|
357
375
|
throw new Error('Failed to launch server process.');
|
|
@@ -392,7 +410,7 @@ export async function serverStopAction(): Promise<void> {
|
|
|
392
410
|
return;
|
|
393
411
|
}
|
|
394
412
|
const owner = state.owner ?? 'unknown';
|
|
395
|
-
if (owner !== '
|
|
413
|
+
if (owner !== 'cli') {
|
|
396
414
|
process.stdout.write(`Server is running (pid ${state.pid}, port ${state.port}) and managed by ${owner}; not stopping.\n`);
|
|
397
415
|
return;
|
|
398
416
|
}
|
|
@@ -417,6 +435,11 @@ export async function serverStatusAction(): Promise<void> {
|
|
|
417
435
|
return;
|
|
418
436
|
}
|
|
419
437
|
const output = await inspectListeningProcess(state.port);
|
|
438
|
+
if (output === undefined) {
|
|
439
|
+
const owner = state.owner ?? 'unknown';
|
|
440
|
+
process.stdout.write(`Server is running on http://${DEFAULT_HOST}:${state.port} (pid ${state.pid}, owner ${owner}).\n`);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
420
443
|
if (!output) {
|
|
421
444
|
process.stdout.write(`No listening process found on port ${state.port}.\n`);
|
|
422
445
|
return;
|
|
@@ -444,13 +467,10 @@ export type RunServerOptions = {
|
|
|
444
467
|
auth: {
|
|
445
468
|
accessToken: string;
|
|
446
469
|
};
|
|
470
|
+
entitlementCache?: EntitlementCache;
|
|
471
|
+
deviceId?: string;
|
|
447
472
|
};
|
|
448
473
|
|
|
449
|
-
function sendLspNotification(childStdin: NodeJS.WritableStream, method: string, params: unknown): void {
|
|
450
|
-
const message = JSON.stringify({ jsonrpc: '2.0', method, params });
|
|
451
|
-
const header = `Content-Length: ${Buffer.byteLength(message, 'utf-8')}\r\n\r\n`;
|
|
452
|
-
childStdin.write(header + message, 'utf-8');
|
|
453
|
-
}
|
|
454
474
|
|
|
455
475
|
export async function serverRunAction(portArg: number | string | undefined, options: RunServerOptions): Promise<void> {
|
|
456
476
|
if (process.env.OML_PLATFORM_API_KEY?.trim()) {
|
|
@@ -481,13 +501,23 @@ export async function serverRunAction(portArg: number | string | undefined, opti
|
|
|
481
501
|
args.push(`--workspace=${path.resolve(options.workspace)}`);
|
|
482
502
|
}
|
|
483
503
|
args.push(`--token=${options.auth.accessToken}`);
|
|
504
|
+
if (options.entitlementCache) {
|
|
505
|
+
args.push(`--entitlement-expiry=${options.entitlementCache.expiry}`);
|
|
506
|
+
args.push(`--entitlement-features=${JSON.stringify(options.entitlementCache.featureIds)}`);
|
|
507
|
+
if (options.entitlementCache.token) {
|
|
508
|
+
args.push(`--entitlement-token=${options.entitlementCache.token}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (options.deviceId) {
|
|
512
|
+
args.push(`--device-id=${options.deviceId}`);
|
|
513
|
+
}
|
|
484
514
|
|
|
485
515
|
const child = spawn(process.execPath, args, {
|
|
486
516
|
detached: false,
|
|
487
|
-
stdio: ['
|
|
517
|
+
stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
|
|
488
518
|
});
|
|
489
519
|
|
|
490
|
-
if (!child.pid
|
|
520
|
+
if (!child.pid) {
|
|
491
521
|
throw new Error('Failed to launch server process.');
|
|
492
522
|
}
|
|
493
523
|
|
|
@@ -505,7 +535,17 @@ export async function serverRunAction(portArg: number | string | undefined, opti
|
|
|
505
535
|
process.stdout.write(`OML server running on http://${DEFAULT_HOST}:${state.port} (pid ${state.pid})\n`);
|
|
506
536
|
process.stdout.write('Press Ctrl-C to stop.\n');
|
|
507
537
|
|
|
508
|
-
|
|
538
|
+
child.on('message', (msg: unknown) => {
|
|
539
|
+
if (msg && typeof msg === 'object' && (msg as Record<string, unknown>).type === 'entitlementsCached') {
|
|
540
|
+
const { expiry, featureIds, entitlementsToken } = msg as { expiry?: unknown; featureIds?: unknown; entitlementsToken?: unknown };
|
|
541
|
+
const expiryNum = Number(expiry);
|
|
542
|
+
const token = typeof entitlementsToken === 'string' ? entitlementsToken : undefined;
|
|
543
|
+
if (Number.isFinite(expiryNum) && Array.isArray(featureIds) && featureIds.every((x) => typeof x === 'string')) {
|
|
544
|
+
void options.authService.saveEntitlementCache(expiryNum, featureIds as string[], token);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
509
549
|
const REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
510
550
|
const REFRESH_RETRY_BASE_MS = 15_000;
|
|
511
551
|
const REFRESH_RETRY_MAX_MS = 5 * 60 * 1000;
|
|
@@ -533,7 +573,7 @@ export async function serverRunAction(portArg: number | string | undefined, opti
|
|
|
533
573
|
const snapshot = await options.authService.getServerAuthSnapshot();
|
|
534
574
|
if (snapshot.accessToken !== currentAccessToken) {
|
|
535
575
|
currentAccessToken = snapshot.accessToken;
|
|
536
|
-
|
|
576
|
+
child.send({ type: 'tokenRefreshed', accessToken: snapshot.accessToken });
|
|
537
577
|
}
|
|
538
578
|
refreshFailureCount = 0;
|
|
539
579
|
scheduleRefresh(REFRESH_INTERVAL_MS);
|
package/src/commands/validate.ts
CHANGED
|
@@ -31,7 +31,7 @@ export const validateAction = async (opts: ValidateOptions): Promise<void> => {
|
|
|
31
31
|
}>('/v0/validate', {}, opts.authToken);
|
|
32
32
|
|
|
33
33
|
if (result.filesChecked === 0) {
|
|
34
|
-
console.log(chalk.yellow('No
|
|
34
|
+
console.log(chalk.yellow('No SHACL validation targets found in server workspace.'));
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
printLintDiagnostics({
|
|
@@ -77,16 +77,18 @@ export const validateAction = async (opts: ValidateOptions): Promise<void> => {
|
|
|
77
77
|
if (result.errors > 0) {
|
|
78
78
|
const failingItems = Number(result.failingItems ?? 0);
|
|
79
79
|
if (failingItems > 0) {
|
|
80
|
-
failCli(chalk.red(`validate: ${result.filesChecked}
|
|
80
|
+
failCli(chalk.red(`validate: ${result.filesChecked} SHACL target(s) scanned [${formatDuration(validateElapsedMs)}]; ${failingItems} failing block(s), ${result.errors} error(s), ${result.warnings} warning(s)`));
|
|
81
81
|
}
|
|
82
|
-
failCli(chalk.red(`validate: ${result.filesChecked}
|
|
82
|
+
failCli(chalk.red(`validate: ${result.filesChecked} SHACL target(s) checked with ${result.errors} error(s) and ${result.warnings} warning(s). [${formatDuration(validateElapsedMs)}]`));
|
|
83
83
|
}
|
|
84
84
|
if (result.warnings > 0) {
|
|
85
85
|
const failingItems = Number(result.failingItems ?? 0);
|
|
86
86
|
if (failingItems > 0) {
|
|
87
|
-
|
|
87
|
+
console.warn(chalk.yellow(`validate: ${result.filesChecked} SHACL target(s) scanned [${formatDuration(validateElapsedMs)}]; ${failingItems} failing block(s), ${result.errors} error(s), ${result.warnings} warning(s)`));
|
|
88
|
+
} else {
|
|
89
|
+
console.warn(chalk.yellow(`validate: ${result.filesChecked} SHACL target(s) checked with ${result.warnings} warning(s). [${formatDuration(validateElapsedMs)}]`));
|
|
88
90
|
}
|
|
89
|
-
|
|
91
|
+
return;
|
|
90
92
|
}
|
|
91
|
-
console.log(chalk.green(`validate: ${result.filesChecked}
|
|
93
|
+
console.log(chalk.green(`validate: ${result.filesChecked} SHACL target(s) scanned [${formatDuration(validateElapsedMs)}]`));
|
|
92
94
|
};
|