@oml/cli 0.14.2 → 0.14.3
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 +232 -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 +87 -22
- 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 +261 -115
- package/src/auth/platform.ts +4 -30
- package/src/cli.ts +62 -87
- package/src/commands/server/actions.ts +107 -26
- 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);
|
|
@@ -319,7 +368,7 @@ async function serverStartDetached(
|
|
|
319
368
|
|
|
320
369
|
const endpoint = `http://${DEFAULT_HOST}:${state.port}`;
|
|
321
370
|
console.log(`OML server started on ${endpoint} (pid ${state.pid})`);
|
|
322
|
-
console.log("Use 'oml
|
|
371
|
+
console.log("Use 'oml stop' to stop the server");
|
|
323
372
|
}
|
|
324
373
|
|
|
325
374
|
export async function serverStopAction(): Promise<void> {
|
|
@@ -368,14 +417,25 @@ export async function serverStatusAction(): Promise<void> {
|
|
|
368
417
|
process.stdout.write(`${output}\n`);
|
|
369
418
|
}
|
|
370
419
|
|
|
420
|
+
export async function serverListAction(): Promise<void> {
|
|
421
|
+
const servers = await listRunningServers();
|
|
422
|
+
if (servers.length === 0) {
|
|
423
|
+
process.stdout.write('No active OML servers.\n');
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
process.stdout.write('Active OML servers:\n');
|
|
427
|
+
for (const server of servers) {
|
|
428
|
+
const workspace = server.workspaceRoot ?? '(unknown workspace)';
|
|
429
|
+
process.stdout.write(`- ${workspace} | http://${DEFAULT_HOST}:${server.port} | pid ${server.pid} | owner ${server.owner}\n`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
371
433
|
export type RunServerOptions = {
|
|
372
434
|
port?: number | string;
|
|
373
435
|
workspace?: string;
|
|
436
|
+
authService: OmlCliAuthService;
|
|
374
437
|
auth: {
|
|
375
438
|
accessToken: string;
|
|
376
|
-
refreshToken: string;
|
|
377
|
-
expiresAtMs: number;
|
|
378
|
-
onRefresh: (newAccessToken: string, newRefreshToken: string, newExpiresAtMs: number) => Promise<void>;
|
|
379
439
|
};
|
|
380
440
|
};
|
|
381
441
|
|
|
@@ -388,8 +448,8 @@ function sendLspNotification(childStdin: NodeJS.WritableStream, method: string,
|
|
|
388
448
|
export async function serverRunAction(portArg: number | string | undefined, options: RunServerOptions): Promise<void> {
|
|
389
449
|
if (process.env.OML_PLATFORM_API_KEY?.trim()) {
|
|
390
450
|
throw new Error(
|
|
391
|
-
'OML_PLATFORM_API_KEY is set but
|
|
392
|
-
'Use \'oml
|
|
451
|
+
'OML_PLATFORM_API_KEY is set but interactive startup uses OAuth credentials. ' +
|
|
452
|
+
'Use \'oml start\' directly to let the CLI choose the correct auth mode.'
|
|
393
453
|
);
|
|
394
454
|
}
|
|
395
455
|
|
|
@@ -440,38 +500,59 @@ export async function serverRunAction(portArg: number | string | undefined, opti
|
|
|
440
500
|
|
|
441
501
|
const stdin = child.stdin;
|
|
442
502
|
const REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
443
|
-
|
|
444
|
-
|
|
503
|
+
const REFRESH_RETRY_BASE_MS = 15_000;
|
|
504
|
+
const REFRESH_RETRY_MAX_MS = 5 * 60 * 1000;
|
|
505
|
+
let currentAccessToken = options.auth.accessToken;
|
|
506
|
+
let refreshFailureCount = 0;
|
|
507
|
+
let refreshTimer: NodeJS.Timeout | undefined;
|
|
508
|
+
|
|
509
|
+
const scheduleRefresh = (delayMs: number): void => {
|
|
510
|
+
if (refreshTimer) {
|
|
511
|
+
clearTimeout(refreshTimer);
|
|
512
|
+
}
|
|
513
|
+
refreshTimer = setTimeout(() => {
|
|
514
|
+
void runRefreshCycle();
|
|
515
|
+
}, delayMs);
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const retryDelayMs = (failureCount: number): number => {
|
|
519
|
+
const step = Math.max(0, failureCount - 1);
|
|
520
|
+
const candidate = REFRESH_RETRY_BASE_MS * (2 ** step);
|
|
521
|
+
return Math.min(REFRESH_RETRY_MAX_MS, candidate);
|
|
522
|
+
};
|
|
445
523
|
|
|
446
|
-
const
|
|
524
|
+
const runRefreshCycle = async (): Promise<void> => {
|
|
447
525
|
try {
|
|
448
|
-
const
|
|
449
|
-
if (
|
|
450
|
-
|
|
526
|
+
const snapshot = await options.authService.getServerAuthSnapshot();
|
|
527
|
+
if (snapshot.accessToken !== currentAccessToken) {
|
|
528
|
+
currentAccessToken = snapshot.accessToken;
|
|
529
|
+
sendLspNotification(stdin, '$/tokenRefreshed', { accessToken: snapshot.accessToken });
|
|
451
530
|
}
|
|
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);
|
|
531
|
+
refreshFailureCount = 0;
|
|
532
|
+
scheduleRefresh(REFRESH_INTERVAL_MS);
|
|
460
533
|
} catch {
|
|
461
|
-
//
|
|
534
|
+
// Keep running with the in-memory token and retry sooner while offline.
|
|
535
|
+
refreshFailureCount += 1;
|
|
536
|
+
scheduleRefresh(retryDelayMs(refreshFailureCount));
|
|
462
537
|
}
|
|
463
|
-
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
scheduleRefresh(REFRESH_INTERVAL_MS);
|
|
464
541
|
|
|
465
542
|
await new Promise<void>((resolve) => {
|
|
466
543
|
const shutdown = (): void => {
|
|
467
|
-
|
|
544
|
+
if (refreshTimer) {
|
|
545
|
+
clearTimeout(refreshTimer);
|
|
546
|
+
}
|
|
468
547
|
child.kill('SIGTERM');
|
|
469
548
|
resolve();
|
|
470
549
|
};
|
|
471
550
|
process.once('SIGINT', shutdown);
|
|
472
551
|
process.once('SIGTERM', shutdown);
|
|
473
552
|
child.once('exit', () => {
|
|
474
|
-
|
|
553
|
+
if (refreshTimer) {
|
|
554
|
+
clearTimeout(refreshTimer);
|
|
555
|
+
}
|
|
475
556
|
resolve();
|
|
476
557
|
});
|
|
477
558
|
});
|
|
@@ -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;
|