Add shared JSON file helpers
This commit is contained in:
parent
a1fc269911
commit
50f32e35ab
21
src/fs-json.ts
Normal file
21
src/fs-json.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
// readJsonFile reads a JSON file and returns undefined when the file does not exist.
|
||||
export async function readJsonFile<T = unknown>(filePath: string): Promise<T | undefined> {
|
||||
try {
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
return JSON.parse(content) as T;
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||
return undefined;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// writeJsonFile writes a JSON object to disk, ensuring parent directories are created first.
|
||||
export async function writeJsonFile(filePath: string, data: unknown): Promise<void> {
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
|
||||
}
|
||||
20
src/oauth.ts
20
src/oauth.ts
@ -12,6 +12,7 @@ import type {
|
||||
OAuthTokens,
|
||||
} from '@modelcontextprotocol/sdk/shared/auth.js';
|
||||
import type { ServerDefinition } from './config.js';
|
||||
import { readJsonFile, writeJsonFile } from './fs-json.js';
|
||||
|
||||
const CALLBACK_HOST = '127.0.0.1';
|
||||
const CALLBACK_PATH = '/callback';
|
||||
@ -61,25 +62,6 @@ async function ensureDirectory(dir: string) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
|
||||
// readJsonFile returns undefined for missing files instead of throwing.
|
||||
async function readJsonFile<T>(filePath: string): Promise<T | undefined> {
|
||||
try {
|
||||
const raw = await fs.readFile(filePath, 'utf8');
|
||||
return JSON.parse(raw) as T;
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||
return undefined;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// writeJsonFile persists JSON data to disk, creating parent directories as needed.
|
||||
async function writeJsonFile(filePath: string, data: unknown) {
|
||||
await ensureDirectory(path.dirname(filePath));
|
||||
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
// FileOAuthClientProvider persists OAuth session artifacts to disk and captures callback redirects.
|
||||
class FileOAuthClientProvider implements OAuthClientProvider {
|
||||
private readonly tokenPath: string;
|
||||
|
||||
35
tests/fs-json.test.ts
Normal file
35
tests/fs-json.test.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { readJsonFile, writeJsonFile } from '../src/fs-json.js';
|
||||
|
||||
describe('fs-json helpers', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcporter-fs-json-'));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('returns undefined when reading a missing file', async () => {
|
||||
const missingPath = path.join(tempDir, 'missing.json');
|
||||
const value = await readJsonFile<Record<string, string>>(missingPath);
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('writes JSON and reads it back, ensuring parent directories are created', async () => {
|
||||
const nestedPath = path.join(tempDir, 'nested', 'config.json');
|
||||
const payload = { apiKey: 'secret', retries: 2 };
|
||||
await writeJsonFile(nestedPath, payload);
|
||||
|
||||
const roundTripped = await readJsonFile<typeof payload>(nestedPath);
|
||||
expect(roundTripped).toEqual(payload);
|
||||
|
||||
const raw = await fs.readFile(nestedPath, 'utf8');
|
||||
expect(raw).toContain('\n "apiKey"');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user