@oml/cli 0.14.2 → 0.14.4
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 +3 -3
- package/out/auth/auth.js +254 -97
- package/out/auth/auth.js.map +1 -1
- package/out/auth/platform.d.ts +1 -1
- package/out/auth/platform.js +4 -26
- package/out/auth/platform.js.map +1 -1
- package/out/cli.js +59 -81
- package/out/cli.js.map +1 -1
- package/out/commands/server/actions.d.ts +3 -3
- package/out/commands/server/actions.js +88 -25
- package/out/commands/server/actions.js.map +1 -1
- package/out/commands/server/require.js +1 -1
- package/out/commands/server/require.js.map +1 -1
- package/out/commands/server/rest.js +103 -28
- package/out/commands/server/rest.js.map +1 -1
- package/package.json +4 -3
- package/src/auth/auth.ts +292 -115
- package/src/auth/platform.ts +4 -30
- package/src/cli.ts +62 -87
- package/src/commands/server/actions.ts +108 -29
- package/src/commands/server/require.ts +1 -1
- package/src/commands/server/rest.ts +119 -29
package/src/cli.ts
CHANGED
|
@@ -6,17 +6,15 @@ import * as fs from 'node:fs/promises';
|
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import * as url from 'node:url';
|
|
8
8
|
import { OmlCliAuthService } from './auth/auth.js';
|
|
9
|
-
import { exchangeApiToken } from '@oml/platform';
|
|
10
|
-
import { DEFAULT_API_BASE_URL } from './auth/constants.js';
|
|
11
9
|
import { exportAction } from './commands/export.js';
|
|
12
10
|
import { lintAction } from './commands/lint.js';
|
|
13
11
|
import { renderAction } from './commands/render.js';
|
|
14
|
-
import { serverStartAction, serverRunAction, serverStatusAction, serverStopAction } from './commands/server/actions.js';
|
|
12
|
+
import { serverStartAction, serverRunAction, serverStatusAction, serverStopAction, serverListAction } from './commands/server/actions.js';
|
|
15
13
|
import { assertServerRunning } from './commands/server/require.js';
|
|
16
14
|
import { notifyIfCliUpdateAvailable } from './update.js';
|
|
17
15
|
import { validateAction } from './commands/validate.js';
|
|
18
16
|
import { CliExitError } from './cli-error.js';
|
|
19
|
-
import {
|
|
17
|
+
import { trackCommand } from './auth/platform.js';
|
|
20
18
|
|
|
21
19
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
|
22
20
|
let debugEnabled = false;
|
|
@@ -71,32 +69,70 @@ export async function runCli(argv: string[] = process.argv): Promise<void> {
|
|
|
71
69
|
|
|
72
70
|
program
|
|
73
71
|
.command('login')
|
|
74
|
-
.description('sign
|
|
72
|
+
.description('creates a sign-in session with OAuth for CLI')
|
|
75
73
|
.action(async () => {
|
|
76
74
|
await authService.login({});
|
|
77
75
|
});
|
|
78
76
|
|
|
79
77
|
program
|
|
80
78
|
.command('logout')
|
|
81
|
-
.description('remove the
|
|
79
|
+
.description('remove the CLI OAuth sign-in session')
|
|
82
80
|
.action(async () => {
|
|
83
81
|
await authService.logout();
|
|
84
82
|
});
|
|
85
83
|
|
|
86
84
|
program
|
|
87
85
|
.command('whoami')
|
|
88
|
-
.description('print the current sign-in session')
|
|
86
|
+
.description('print the current CLI OAuth sign-in session (or api key account)')
|
|
89
87
|
.action(async () => {
|
|
90
88
|
await authService.whoami();
|
|
91
89
|
});
|
|
92
90
|
|
|
91
|
+
program
|
|
92
|
+
.command('start [port]')
|
|
93
|
+
.option('-p, --port <port>', 'bind port (default: auto-select free port)')
|
|
94
|
+
.option('-w, --workspace <workspace>', 'workspace root used by REST facade initialize (default: cwd)')
|
|
95
|
+
.description('start an OML server')
|
|
96
|
+
.action(async (port: string | undefined, options: { port?: string; workspace?: string }) => {
|
|
97
|
+
if (process.env.OML_PLATFORM_API_KEY?.trim()) {
|
|
98
|
+
await serverStartAction(port, { ...options, auth: await resolveServerStartAuth() });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await serverRunAction(port, {
|
|
102
|
+
...options,
|
|
103
|
+
authService,
|
|
104
|
+
auth: await resolveServerRunAuth(authService),
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
program
|
|
109
|
+
.command('stop')
|
|
110
|
+
.description('stop the OML server')
|
|
111
|
+
.action(async () => {
|
|
112
|
+
await serverStopAction();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
program
|
|
116
|
+
.command('status')
|
|
117
|
+
.description('print OML server status')
|
|
118
|
+
.action(async () => {
|
|
119
|
+
await serverStatusAction();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
program
|
|
123
|
+
.command('list')
|
|
124
|
+
.description('show all actively running servers')
|
|
125
|
+
.action(async () => {
|
|
126
|
+
await serverListAction();
|
|
127
|
+
});
|
|
128
|
+
|
|
93
129
|
program
|
|
94
130
|
.command('lint')
|
|
95
131
|
.description('lints OML files and prints any syntax or validation errors')
|
|
96
132
|
.action(async (...args: unknown[]) => {
|
|
97
133
|
const done = trackCommand('oml-lint');
|
|
98
134
|
try {
|
|
99
|
-
const authToken =
|
|
135
|
+
const authToken = resolveServerRequestToken();
|
|
100
136
|
await lintAction({
|
|
101
137
|
...(args[0] as Record<string, unknown> | undefined ?? {}),
|
|
102
138
|
authToken,
|
|
@@ -117,7 +153,7 @@ export async function runCli(argv: string[] = process.argv): Promise<void> {
|
|
|
117
153
|
.action(async (...args: unknown[]) => {
|
|
118
154
|
const done = trackCommand('oml-render');
|
|
119
155
|
try {
|
|
120
|
-
const authToken =
|
|
156
|
+
const authToken = resolveServerRequestToken();
|
|
121
157
|
await renderAction({
|
|
122
158
|
...(args[0] as Record<string, unknown> | undefined ?? {}),
|
|
123
159
|
authToken,
|
|
@@ -139,7 +175,7 @@ export async function runCli(argv: string[] = process.argv): Promise<void> {
|
|
|
139
175
|
.action(async (...args: unknown[]) => {
|
|
140
176
|
const done = trackCommand('oml-export');
|
|
141
177
|
try {
|
|
142
|
-
const authToken =
|
|
178
|
+
const authToken = resolveServerRequestToken();
|
|
143
179
|
await exportAction({
|
|
144
180
|
...(args[0] as Record<string, unknown> | undefined ?? {}),
|
|
145
181
|
authToken,
|
|
@@ -160,7 +196,7 @@ export async function runCli(argv: string[] = process.argv): Promise<void> {
|
|
|
160
196
|
const done = trackCommand('oml-reason');
|
|
161
197
|
try {
|
|
162
198
|
const { reasonAction } = await import('./commands/reason.js');
|
|
163
|
-
const authToken =
|
|
199
|
+
const authToken = resolveServerRequestToken();
|
|
164
200
|
await reasonAction({ ...(opts as Record<string, unknown>), authToken } as Parameters<typeof reasonAction>[0]);
|
|
165
201
|
done();
|
|
166
202
|
} catch (err) {
|
|
@@ -176,7 +212,7 @@ export async function runCli(argv: string[] = process.argv): Promise<void> {
|
|
|
176
212
|
.action(async (...args: unknown[]) => {
|
|
177
213
|
const done = trackCommand('oml-validate');
|
|
178
214
|
try {
|
|
179
|
-
const authToken =
|
|
215
|
+
const authToken = resolveServerRequestToken();
|
|
180
216
|
await validateAction({
|
|
181
217
|
...(args[0] as Record<string, unknown> | undefined ?? {}),
|
|
182
218
|
authToken,
|
|
@@ -188,67 +224,22 @@ export async function runCli(argv: string[] = process.argv): Promise<void> {
|
|
|
188
224
|
}
|
|
189
225
|
});
|
|
190
226
|
|
|
191
|
-
const server = program
|
|
192
|
-
.command('server')
|
|
193
|
-
.description('manage the standalone OML language server daemon');
|
|
194
|
-
|
|
195
|
-
server
|
|
196
|
-
.command('start [port]')
|
|
197
|
-
.option('-p, --port <port>', 'bind port (default: auto-select free port)')
|
|
198
|
-
.option('--workspace <workspace>', 'workspace root used by REST facade initialize (default: cwd)')
|
|
199
|
-
.description('start the OML server as a background daemon (CI/CD, requires OML_PLATFORM_API_KEY)')
|
|
200
|
-
.action(async (port: string | undefined, options: { port?: string; workspace?: string }) => {
|
|
201
|
-
await serverStartAction(port, { ...options, auth: await resolveServerStartAuth() });
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
server
|
|
205
|
-
.command('run [port]')
|
|
206
|
-
.option('-p, --port <port>', 'bind port (default: auto-select free port)')
|
|
207
|
-
.option('--workspace <workspace>', 'workspace root (default: cwd)')
|
|
208
|
-
.description('run the OML server in the foreground with interactive authentication (Ctrl-C to stop)')
|
|
209
|
-
.action(async (port: string | undefined, options: { port?: string; workspace?: string }) => {
|
|
210
|
-
await serverRunAction(port, { ...options, auth: await resolveServerRunAuth(authService) });
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
server
|
|
214
|
-
.command('stop')
|
|
215
|
-
.description('stop the OML language server daemon')
|
|
216
|
-
.action(async () => {
|
|
217
|
-
await serverStopAction();
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
server
|
|
221
|
-
.command('status')
|
|
222
|
-
.description('print server daemon status')
|
|
223
|
-
.action(async () => {
|
|
224
|
-
await serverStatusAction();
|
|
225
|
-
});
|
|
226
|
-
|
|
227
227
|
program.hook('preAction', async (_thisCommand, actionCommand) => {
|
|
228
228
|
if (
|
|
229
229
|
actionCommand.name() === 'login'
|
|
230
230
|
|| actionCommand.name() === 'logout'
|
|
231
231
|
|| actionCommand.name() === 'whoami'
|
|
232
232
|
|| actionCommand.name() === 'start'
|
|
233
|
-
|| actionCommand.name() === 'run'
|
|
234
233
|
|| actionCommand.name() === 'stop'
|
|
235
234
|
|| actionCommand.name() === 'status'
|
|
235
|
+
|| actionCommand.name() === 'list'
|
|
236
236
|
) {
|
|
237
237
|
return;
|
|
238
238
|
}
|
|
239
239
|
await assertServerRunning();
|
|
240
|
-
// Require either GitHub auth or API key, then connect to platform
|
|
241
|
-
if (!process.env.OML_PLATFORM_API_KEY) {
|
|
242
|
-
await authService.ensureAuthenticated('OML CLI');
|
|
243
|
-
}
|
|
244
|
-
await initializePlatform(authService);
|
|
245
240
|
});
|
|
246
241
|
|
|
247
|
-
|
|
248
|
-
await program.parseAsync(argv);
|
|
249
|
-
} finally {
|
|
250
|
-
await disposePlatform();
|
|
251
|
-
}
|
|
242
|
+
await program.parseAsync(argv);
|
|
252
243
|
await updateCheck;
|
|
253
244
|
}
|
|
254
245
|
|
|
@@ -319,53 +310,37 @@ function hasDebugFlag(argv: string[]): boolean {
|
|
|
319
310
|
return argv.includes('--debug') || argv.includes('-d');
|
|
320
311
|
}
|
|
321
312
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (apiKey && apiKey.length > 0) {
|
|
325
|
-
const oidcToken = process.env.OML_CI_TOKEN?.trim();
|
|
326
|
-
const apiBaseUrl = process.env.OML_PLATFORM_API_URL?.trim() ?? DEFAULT_API_BASE_URL;
|
|
327
|
-
const exchanged = await exchangeApiToken(apiBaseUrl, apiKey, oidcToken || undefined);
|
|
328
|
-
return exchanged.accessToken;
|
|
329
|
-
}
|
|
330
|
-
const snapshot = await authService.getServerAuthSnapshot();
|
|
331
|
-
return snapshot.accessToken;
|
|
313
|
+
function resolveServerRequestToken(): string | undefined {
|
|
314
|
+
return undefined;
|
|
332
315
|
}
|
|
333
316
|
|
|
334
317
|
async function resolveServerStartAuth(): Promise<{ accessToken: string }> {
|
|
335
318
|
const apiKey = process.env.OML_PLATFORM_API_KEY?.trim();
|
|
336
319
|
if (!apiKey) {
|
|
337
320
|
throw new CliExitError(
|
|
338
|
-
'OML_PLATFORM_API_KEY is not set.
|
|
339
|
-
'For interactive use,
|
|
321
|
+
'OML_PLATFORM_API_KEY is not set. For interactive use, run \'oml start\'. ' +
|
|
322
|
+
'For non-interactive use, set OML_PLATFORM_API_KEY and retry.'
|
|
340
323
|
);
|
|
341
324
|
}
|
|
342
|
-
|
|
343
|
-
const apiBaseUrl = process.env.OML_PLATFORM_API_URL?.trim() ?? DEFAULT_API_BASE_URL;
|
|
344
|
-
const result = await exchangeApiToken(apiBaseUrl, apiKey, oidcToken || undefined);
|
|
345
|
-
return { accessToken: result.accessToken };
|
|
325
|
+
return { accessToken: apiKey };
|
|
346
326
|
}
|
|
347
327
|
|
|
348
328
|
async function resolveServerRunAuth(authService: OmlCliAuthService): Promise<{
|
|
349
329
|
accessToken: string;
|
|
350
|
-
refreshToken: string;
|
|
351
|
-
expiresAtMs: number;
|
|
352
|
-
onRefresh: (newAccessToken: string, newRefreshToken: string, newExpiresAtMs: number) => Promise<void>;
|
|
353
330
|
}> {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
331
|
+
let snapshot;
|
|
332
|
+
try {
|
|
333
|
+
snapshot = await authService.getServerAuthSnapshot();
|
|
334
|
+
} catch {
|
|
335
|
+
await authService.login({});
|
|
336
|
+
snapshot = await authService.getServerAuthSnapshot();
|
|
358
337
|
}
|
|
359
338
|
return {
|
|
360
339
|
accessToken: snapshot.accessToken,
|
|
361
|
-
refreshToken: snapshot.refreshToken,
|
|
362
|
-
expiresAtMs: snapshot.expiresAtMs,
|
|
363
|
-
onRefresh: async (newAccessToken, newRefreshToken, newExpiresAtMs) => {
|
|
364
|
-
await authService.storeRefreshedTokens(newAccessToken, newRefreshToken, newExpiresAtMs);
|
|
365
|
-
},
|
|
366
340
|
};
|
|
367
341
|
}
|
|
368
342
|
|
|
343
|
+
|
|
369
344
|
function formatDetailedError(error: unknown): string {
|
|
370
345
|
if (!(error instanceof Error)) {
|
|
371
346
|
return String(error);
|
|
@@ -9,6 +9,7 @@ import { createHash } from 'node:crypto';
|
|
|
9
9
|
import { createRequire } from 'node:module';
|
|
10
10
|
import { spawn, execFile, type ChildProcess } from 'node:child_process';
|
|
11
11
|
import { promisify } from 'node:util';
|
|
12
|
+
import type { OmlCliAuthService } from '../../auth/auth.js';
|
|
12
13
|
|
|
13
14
|
const DEFAULT_HOST = '127.0.0.1';
|
|
14
15
|
const STARTUP_TIMEOUT_MS = 15_000;
|
|
@@ -34,6 +35,7 @@ type ServerLockState = {
|
|
|
34
35
|
pid: number;
|
|
35
36
|
port: number;
|
|
36
37
|
owner?: 'daemon' | 'extension';
|
|
38
|
+
workspaceRoot?: string;
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
function workspaceHash(workspaceRoot: string): string {
|
|
@@ -56,7 +58,7 @@ async function cleanupStateFile(paths: ServerStatePaths): Promise<void> {
|
|
|
56
58
|
|
|
57
59
|
function parseServerLock(raw: string): ServerLockState | undefined {
|
|
58
60
|
try {
|
|
59
|
-
const parsed = JSON.parse(raw) as { pid?: unknown; port?: unknown; owner?: unknown };
|
|
61
|
+
const parsed = JSON.parse(raw) as { pid?: unknown; port?: unknown; owner?: unknown; workspaceRoot?: unknown };
|
|
60
62
|
const pid = Number(parsed.pid);
|
|
61
63
|
const port = Number(parsed.port);
|
|
62
64
|
if (!Number.isFinite(pid) || !Number.isFinite(port)) {
|
|
@@ -70,7 +72,10 @@ function parseServerLock(raw: string): ServerLockState | undefined {
|
|
|
70
72
|
const owner = parsed.owner === 'daemon' || parsed.owner === 'extension'
|
|
71
73
|
? parsed.owner
|
|
72
74
|
: undefined;
|
|
73
|
-
|
|
75
|
+
const workspaceRoot = typeof parsed.workspaceRoot === 'string' && parsed.workspaceRoot.trim().length > 0
|
|
76
|
+
? parsed.workspaceRoot.trim()
|
|
77
|
+
: undefined;
|
|
78
|
+
return { pid: pidInt, port: portInt, owner, workspaceRoot };
|
|
74
79
|
} catch {
|
|
75
80
|
return undefined;
|
|
76
81
|
}
|
|
@@ -88,6 +93,50 @@ async function readServerLock(lockFile: string): Promise<ServerLockState | undef
|
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
type RunningServer = {
|
|
97
|
+
pid: number;
|
|
98
|
+
port: number;
|
|
99
|
+
owner: 'daemon' | 'extension' | 'unknown';
|
|
100
|
+
workspaceRoot?: string;
|
|
101
|
+
lockFile: string;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
async function listRunningServers(): Promise<RunningServer[]> {
|
|
105
|
+
const baseDir = path.join(os.homedir(), '.oml', 'workspaces');
|
|
106
|
+
let entries: string[];
|
|
107
|
+
try {
|
|
108
|
+
entries = await fs.readdir(baseDir);
|
|
109
|
+
} catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const servers: RunningServer[] = [];
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const lockFile = path.join(baseDir, entry, 'server.lock');
|
|
116
|
+
const state = await readServerLock(lockFile);
|
|
117
|
+
if (!state) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!isProcessAlive(state.pid)) {
|
|
121
|
+
await fs.rm(lockFile, { force: true });
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
servers.push({
|
|
125
|
+
pid: state.pid,
|
|
126
|
+
port: state.port,
|
|
127
|
+
owner: state.owner ?? 'unknown',
|
|
128
|
+
workspaceRoot: state.workspaceRoot,
|
|
129
|
+
lockFile,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
servers.sort((left, right) =>
|
|
134
|
+
String(left.workspaceRoot ?? '').localeCompare(String(right.workspaceRoot ?? ''))
|
|
135
|
+
|| left.port - right.port
|
|
136
|
+
);
|
|
137
|
+
return servers;
|
|
138
|
+
}
|
|
139
|
+
|
|
91
140
|
function isProcessAlive(pid: number): boolean {
|
|
92
141
|
try {
|
|
93
142
|
process.kill(pid, 0);
|
|
@@ -99,9 +148,7 @@ function isProcessAlive(pid: number): boolean {
|
|
|
99
148
|
|
|
100
149
|
function resolveServerMainScript(): string {
|
|
101
150
|
const require = createRequire(import.meta.url);
|
|
102
|
-
|
|
103
|
-
const serverPackageDir = path.dirname(serverPackageJson);
|
|
104
|
-
return path.join(serverPackageDir, 'out', 'cli.js');
|
|
151
|
+
return require.resolve('@oml/server/server');
|
|
105
152
|
}
|
|
106
153
|
|
|
107
154
|
function canReadFile(filePath: string): Promise<boolean> {
|
|
@@ -319,7 +366,7 @@ async function serverStartDetached(
|
|
|
319
366
|
|
|
320
367
|
const endpoint = `http://${DEFAULT_HOST}:${state.port}`;
|
|
321
368
|
console.log(`OML server started on ${endpoint} (pid ${state.pid})`);
|
|
322
|
-
console.log("Use 'oml
|
|
369
|
+
console.log("Use 'oml stop' to stop the server");
|
|
323
370
|
}
|
|
324
371
|
|
|
325
372
|
export async function serverStopAction(): Promise<void> {
|
|
@@ -368,14 +415,25 @@ export async function serverStatusAction(): Promise<void> {
|
|
|
368
415
|
process.stdout.write(`${output}\n`);
|
|
369
416
|
}
|
|
370
417
|
|
|
418
|
+
export async function serverListAction(): Promise<void> {
|
|
419
|
+
const servers = await listRunningServers();
|
|
420
|
+
if (servers.length === 0) {
|
|
421
|
+
process.stdout.write('No active OML servers.\n');
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
process.stdout.write('Active OML servers:\n');
|
|
425
|
+
for (const server of servers) {
|
|
426
|
+
const workspace = server.workspaceRoot ?? '(unknown workspace)';
|
|
427
|
+
process.stdout.write(`- ${workspace} | http://${DEFAULT_HOST}:${server.port} | pid ${server.pid} | owner ${server.owner}\n`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
371
431
|
export type RunServerOptions = {
|
|
372
432
|
port?: number | string;
|
|
373
433
|
workspace?: string;
|
|
434
|
+
authService: OmlCliAuthService;
|
|
374
435
|
auth: {
|
|
375
436
|
accessToken: string;
|
|
376
|
-
refreshToken: string;
|
|
377
|
-
expiresAtMs: number;
|
|
378
|
-
onRefresh: (newAccessToken: string, newRefreshToken: string, newExpiresAtMs: number) => Promise<void>;
|
|
379
437
|
};
|
|
380
438
|
};
|
|
381
439
|
|
|
@@ -388,8 +446,8 @@ function sendLspNotification(childStdin: NodeJS.WritableStream, method: string,
|
|
|
388
446
|
export async function serverRunAction(portArg: number | string | undefined, options: RunServerOptions): Promise<void> {
|
|
389
447
|
if (process.env.OML_PLATFORM_API_KEY?.trim()) {
|
|
390
448
|
throw new Error(
|
|
391
|
-
'OML_PLATFORM_API_KEY is set but
|
|
392
|
-
'Use \'oml
|
|
449
|
+
'OML_PLATFORM_API_KEY is set but interactive startup uses OAuth credentials. ' +
|
|
450
|
+
'Use \'oml start\' directly to let the CLI choose the correct auth mode.'
|
|
393
451
|
);
|
|
394
452
|
}
|
|
395
453
|
|
|
@@ -440,38 +498,59 @@ export async function serverRunAction(portArg: number | string | undefined, opti
|
|
|
440
498
|
|
|
441
499
|
const stdin = child.stdin;
|
|
442
500
|
const REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
443
|
-
|
|
444
|
-
|
|
501
|
+
const REFRESH_RETRY_BASE_MS = 15_000;
|
|
502
|
+
const REFRESH_RETRY_MAX_MS = 5 * 60 * 1000;
|
|
503
|
+
let currentAccessToken = options.auth.accessToken;
|
|
504
|
+
let refreshFailureCount = 0;
|
|
505
|
+
let refreshTimer: NodeJS.Timeout | undefined;
|
|
506
|
+
|
|
507
|
+
const scheduleRefresh = (delayMs: number): void => {
|
|
508
|
+
if (refreshTimer) {
|
|
509
|
+
clearTimeout(refreshTimer);
|
|
510
|
+
}
|
|
511
|
+
refreshTimer = setTimeout(() => {
|
|
512
|
+
void runRefreshCycle();
|
|
513
|
+
}, delayMs);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const retryDelayMs = (failureCount: number): number => {
|
|
517
|
+
const step = Math.max(0, failureCount - 1);
|
|
518
|
+
const candidate = REFRESH_RETRY_BASE_MS * (2 ** step);
|
|
519
|
+
return Math.min(REFRESH_RETRY_MAX_MS, candidate);
|
|
520
|
+
};
|
|
445
521
|
|
|
446
|
-
const
|
|
522
|
+
const runRefreshCycle = async (): Promise<void> => {
|
|
447
523
|
try {
|
|
448
|
-
const
|
|
449
|
-
if (
|
|
450
|
-
|
|
524
|
+
const snapshot = await options.authService.getServerAuthSnapshot();
|
|
525
|
+
if (snapshot.accessToken !== currentAccessToken) {
|
|
526
|
+
currentAccessToken = snapshot.accessToken;
|
|
527
|
+
sendLspNotification(stdin, '$/tokenRefreshed', { accessToken: snapshot.accessToken });
|
|
451
528
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const supabaseAnonKey = process.env.OML_SUPABASE_ANON_KEY?.trim() ?? '';
|
|
455
|
-
const refreshed = await refreshSupabaseAccessToken(supabaseUrl, supabaseAnonKey, currentRefreshToken);
|
|
456
|
-
currentRefreshToken = refreshed.refresh_token;
|
|
457
|
-
currentExpiresAtMs = Date.now() + refreshed.expires_in * 1000;
|
|
458
|
-
sendLspNotification(stdin, '$/tokenRefreshed', { accessToken: refreshed.access_token });
|
|
459
|
-
await options.auth.onRefresh(refreshed.access_token, refreshed.refresh_token, currentExpiresAtMs);
|
|
529
|
+
refreshFailureCount = 0;
|
|
530
|
+
scheduleRefresh(REFRESH_INTERVAL_MS);
|
|
460
531
|
} catch {
|
|
461
|
-
//
|
|
532
|
+
// Keep running with the in-memory token and retry sooner while offline.
|
|
533
|
+
refreshFailureCount += 1;
|
|
534
|
+
scheduleRefresh(retryDelayMs(refreshFailureCount));
|
|
462
535
|
}
|
|
463
|
-
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
scheduleRefresh(REFRESH_INTERVAL_MS);
|
|
464
539
|
|
|
465
540
|
await new Promise<void>((resolve) => {
|
|
466
541
|
const shutdown = (): void => {
|
|
467
|
-
|
|
542
|
+
if (refreshTimer) {
|
|
543
|
+
clearTimeout(refreshTimer);
|
|
544
|
+
}
|
|
468
545
|
child.kill('SIGTERM');
|
|
469
546
|
resolve();
|
|
470
547
|
};
|
|
471
548
|
process.once('SIGINT', shutdown);
|
|
472
549
|
process.once('SIGTERM', shutdown);
|
|
473
550
|
child.once('exit', () => {
|
|
474
|
-
|
|
551
|
+
if (refreshTimer) {
|
|
552
|
+
clearTimeout(refreshTimer);
|
|
553
|
+
}
|
|
475
554
|
resolve();
|
|
476
555
|
});
|
|
477
556
|
});
|
|
@@ -8,7 +8,7 @@ import { createHash } from 'node:crypto';
|
|
|
8
8
|
|
|
9
9
|
const DEFAULT_HOST = '127.0.0.1';
|
|
10
10
|
const CONNECT_TIMEOUT_MS = 400;
|
|
11
|
-
const START_SERVER_HINT = "start server first (run 'oml
|
|
11
|
+
const START_SERVER_HINT = "start server first (run 'oml start')";
|
|
12
12
|
|
|
13
13
|
type ServerState = {
|
|
14
14
|
pid: number;
|