fix: replace any types with proper TypeScript types
Replace all `any` types with properly defined TypeScript interfaces and types throughout the codebase to improve type safety and eliminate type-related code quality issues. Changes: - Define ElectronSafeStorage interface for Electron's safeStorage API - Create LegacySettings interface for settings migration in main.ts - Define JSONValue, JSONRPCParams types for JSON-RPC protocol - Define JSONSchemaProperty for tool input schemas - Create YAMLValue type for frontmatter values - Define FrontmatterValue type for adapter interfaces - Update middleware to use proper Express NextFunction and JSONRPCResponse types - Fix tool registry to handle args with proper typing (with eslint-disable for dynamic dispatch) - Fix notifications to use proper types with eslint-disable where dynamic access is needed - Add proper null safety assertions where appropriate - Fix TFolder stat access with proper type extension All type errors resolved. TypeScript compilation passes with --skipLibCheck.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { FileManager, TAbstractFile, TFile } from 'obsidian';
|
import { FileManager, TAbstractFile, TFile } from 'obsidian';
|
||||||
import { IFileManagerAdapter } from './interfaces';
|
import { IFileManagerAdapter, FrontmatterValue } from './interfaces';
|
||||||
|
|
||||||
export class FileManagerAdapter implements IFileManagerAdapter {
|
export class FileManagerAdapter implements IFileManagerAdapter {
|
||||||
constructor(private fileManager: FileManager) {}
|
constructor(private fileManager: FileManager) {}
|
||||||
@@ -12,7 +12,7 @@ export class FileManagerAdapter implements IFileManagerAdapter {
|
|||||||
await this.fileManager.trashFile(file);
|
await this.fileManager.trashFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
async processFrontMatter(file: TFile, fn: (frontmatter: any) => void): Promise<void> {
|
async processFrontMatter(file: TFile, fn: (frontmatter: Record<string, FrontmatterValue>) => void): Promise<void> {
|
||||||
await this.fileManager.processFrontMatter(file, fn);
|
await this.fileManager.processFrontMatter(file, fn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import { TAbstractFile, TFile, TFolder, CachedMetadata, DataWriteOptions } from 'obsidian';
|
import { TAbstractFile, TFile, TFolder, CachedMetadata, DataWriteOptions } from 'obsidian';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frontmatter data structure (YAML-compatible types)
|
||||||
|
*/
|
||||||
|
export type FrontmatterValue = string | number | boolean | null | FrontmatterValue[] | { [key: string]: FrontmatterValue };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter interface for Obsidian Vault operations
|
* Adapter interface for Obsidian Vault operations
|
||||||
*/
|
*/
|
||||||
@@ -56,5 +61,5 @@ export interface IFileManagerAdapter {
|
|||||||
// File operations
|
// File operations
|
||||||
renameFile(file: TAbstractFile, newPath: string): Promise<void>;
|
renameFile(file: TAbstractFile, newPath: string): Promise<void>;
|
||||||
trashFile(file: TAbstractFile): Promise<void>;
|
trashFile(file: TAbstractFile): Promise<void>;
|
||||||
processFrontMatter(file: TFile, fn: (frontmatter: any) => void): Promise<void>;
|
processFrontMatter(file: TFile, fn: (frontmatter: Record<string, FrontmatterValue>) => void): Promise<void>;
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,11 @@ export default class MCPServerPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Migrate legacy settings (remove enableCORS and allowedOrigins)
|
// Migrate legacy settings (remove enableCORS and allowedOrigins)
|
||||||
const legacySettings = this.settings as any;
|
interface LegacySettings extends MCPPluginSettings {
|
||||||
|
enableCORS?: boolean;
|
||||||
|
allowedOrigins?: string[];
|
||||||
|
}
|
||||||
|
const legacySettings = this.settings as LegacySettings;
|
||||||
if ('enableCORS' in legacySettings || 'allowedOrigins' in legacySettings) {
|
if ('enableCORS' in legacySettings || 'allowedOrigins' in legacySettings) {
|
||||||
console.log('Migrating legacy CORS settings...');
|
console.log('Migrating legacy CORS settings...');
|
||||||
delete legacySettings.enableCORS;
|
delete legacySettings.enableCORS;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Server } from 'http';
|
|||||||
import {
|
import {
|
||||||
JSONRPCRequest,
|
JSONRPCRequest,
|
||||||
JSONRPCResponse,
|
JSONRPCResponse,
|
||||||
|
JSONRPCParams,
|
||||||
|
JSONValue,
|
||||||
ErrorCodes,
|
ErrorCodes,
|
||||||
InitializeResult,
|
InitializeResult,
|
||||||
ListToolsResult,
|
ListToolsResult,
|
||||||
@@ -36,11 +38,11 @@ export class MCPServer {
|
|||||||
try {
|
try {
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'initialize':
|
case 'initialize':
|
||||||
return this.createSuccessResponse(request.id, await this.handleInitialize(request.params));
|
return this.createSuccessResponse(request.id, await this.handleInitialize(request.params ?? {}));
|
||||||
case 'tools/list':
|
case 'tools/list':
|
||||||
return this.createSuccessResponse(request.id, await this.handleListTools());
|
return this.createSuccessResponse(request.id, await this.handleListTools());
|
||||||
case 'tools/call':
|
case 'tools/call':
|
||||||
return this.createSuccessResponse(request.id, await this.handleCallTool(request.params));
|
return this.createSuccessResponse(request.id, await this.handleCallTool(request.params ?? {}));
|
||||||
case 'ping':
|
case 'ping':
|
||||||
return this.createSuccessResponse(request.id, {});
|
return this.createSuccessResponse(request.id, {});
|
||||||
default:
|
default:
|
||||||
@@ -52,7 +54,7 @@ export class MCPServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleInitialize(_params: any): Promise<InitializeResult> {
|
private async handleInitialize(_params: JSONRPCParams): Promise<InitializeResult> {
|
||||||
return {
|
return {
|
||||||
protocolVersion: "2024-11-05",
|
protocolVersion: "2024-11-05",
|
||||||
capabilities: {
|
capabilities: {
|
||||||
@@ -71,20 +73,20 @@ export class MCPServer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleCallTool(params: any): Promise<CallToolResult> {
|
private async handleCallTool(params: JSONRPCParams): Promise<CallToolResult> {
|
||||||
const { name, arguments: args } = params;
|
const paramsObj = params as { name: string; arguments: Record<string, unknown> };
|
||||||
return await this.toolRegistry.callTool(name, args);
|
return await this.toolRegistry.callTool(paramsObj.name, paramsObj.arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createSuccessResponse(id: string | number | undefined, result: any): JSONRPCResponse {
|
private createSuccessResponse(id: string | number | undefined, result: unknown): JSONRPCResponse {
|
||||||
return {
|
return {
|
||||||
jsonrpc: "2.0",
|
jsonrpc: "2.0",
|
||||||
id: id ?? null,
|
id: id ?? null,
|
||||||
result
|
result: result as JSONValue
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private createErrorResponse(id: string | number | undefined | null, code: number, message: string, data?: any): JSONRPCResponse {
|
private createErrorResponse(id: string | number | undefined | null, code: number, message: string, data?: JSONValue): JSONRPCResponse {
|
||||||
return {
|
return {
|
||||||
jsonrpc: "2.0",
|
jsonrpc: "2.0",
|
||||||
id: id ?? null,
|
id: id ?? null,
|
||||||
@@ -104,7 +106,7 @@ export class MCPServer {
|
|||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.server.on('error', (error: any) => {
|
this.server.on('error', (error: NodeJS.ErrnoException) => {
|
||||||
if (error.code === 'EADDRINUSE') {
|
if (error.code === 'EADDRINUSE') {
|
||||||
reject(new Error(`Port ${this.settings.port} is already in use`));
|
reject(new Error(`Port ${this.settings.port} is already in use`));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Express, Request, Response } from 'express';
|
import { Express, Request, Response, NextFunction } from 'express';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import { MCPServerSettings } from '../types/settings-types';
|
import { MCPServerSettings } from '../types/settings-types';
|
||||||
import { ErrorCodes } from '../types/mcp-types';
|
import { ErrorCodes, JSONRPCResponse } from '../types/mcp-types';
|
||||||
|
|
||||||
export function setupMiddleware(app: Express, settings: MCPServerSettings, createErrorResponse: (id: any, code: number, message: string) => any): void {
|
export function setupMiddleware(app: Express, settings: MCPServerSettings, createErrorResponse: (id: string | number | null, code: number, message: string) => JSONRPCResponse): void {
|
||||||
// Parse JSON bodies
|
// Parse JSON bodies
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export function setupMiddleware(app: Express, settings: MCPServerSettings, creat
|
|||||||
app.use(cors(corsOptions));
|
app.use(cors(corsOptions));
|
||||||
|
|
||||||
// Authentication middleware - Always enabled
|
// Authentication middleware - Always enabled
|
||||||
app.use((req: Request, res: Response, next: any) => {
|
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||||
// Defensive check: if no API key is set, reject all requests
|
// Defensive check: if no API key is set, reject all requests
|
||||||
if (!settings.apiKey || settings.apiKey.trim() === '') {
|
if (!settings.apiKey || settings.apiKey.trim() === '') {
|
||||||
return res.status(500).json(createErrorResponse(null, ErrorCodes.InternalError, 'Server misconfigured: No API key set'));
|
return res.status(500).json(createErrorResponse(null, ErrorCodes.InternalError, 'Server misconfigured: No API key set'));
|
||||||
@@ -45,7 +45,7 @@ export function setupMiddleware(app: Express, settings: MCPServerSettings, creat
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Origin validation for security (DNS rebinding protection)
|
// Origin validation for security (DNS rebinding protection)
|
||||||
app.use((req: Request, res: Response, next: any) => {
|
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||||
const host = req.headers.host;
|
const host = req.headers.host;
|
||||||
|
|
||||||
// Only allow localhost connections
|
// Only allow localhost connections
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { Express, Request, Response } from 'express';
|
|||||||
import { JSONRPCRequest, JSONRPCResponse, ErrorCodes } from '../types/mcp-types';
|
import { JSONRPCRequest, JSONRPCResponse, ErrorCodes } from '../types/mcp-types';
|
||||||
|
|
||||||
export function setupRoutes(
|
export function setupRoutes(
|
||||||
app: Express,
|
app: Express,
|
||||||
handleRequest: (request: JSONRPCRequest) => Promise<JSONRPCResponse>,
|
handleRequest: (request: JSONRPCRequest) => Promise<JSONRPCResponse>,
|
||||||
createErrorResponse: (id: any, code: number, message: string) => JSONRPCResponse
|
createErrorResponse: (id: string | number | null, code: number, message: string) => JSONRPCResponse
|
||||||
): void {
|
): void {
|
||||||
// Main MCP endpoint
|
// Main MCP endpoint
|
||||||
app.post('/mcp', async (req: Request, res: Response) => {
|
app.post('/mcp', async (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -474,6 +474,7 @@ export class ToolRegistry {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
async callTool(name: string, args: any): Promise<CallToolResult> {
|
async callTool(name: string, args: any): Promise<CallToolResult> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from '../types/mcp-types';
|
} from '../types/mcp-types';
|
||||||
import { PathUtils } from '../utils/path-utils';
|
import { PathUtils } from '../utils/path-utils';
|
||||||
import { ErrorMessages } from '../utils/error-messages';
|
import { ErrorMessages } from '../utils/error-messages';
|
||||||
import { FrontmatterUtils } from '../utils/frontmatter-utils';
|
import { FrontmatterUtils, YAMLValue } from '../utils/frontmatter-utils';
|
||||||
import { WaypointUtils } from '../utils/waypoint-utils';
|
import { WaypointUtils } from '../utils/waypoint-utils';
|
||||||
import { VersionUtils } from '../utils/version-utils';
|
import { VersionUtils } from '../utils/version-utils';
|
||||||
import { ContentUtils } from '../utils/content-utils';
|
import { ContentUtils } from '../utils/content-utils';
|
||||||
@@ -364,16 +364,28 @@ export class NoteTools {
|
|||||||
await this.vault.modify(file, content);
|
await this.vault.modify(file, content);
|
||||||
|
|
||||||
// Build response with word count and link validation
|
// Build response with word count and link validation
|
||||||
const result: any = {
|
interface UpdateNoteResult {
|
||||||
|
success: boolean;
|
||||||
|
path: string;
|
||||||
|
versionId: string;
|
||||||
|
modified: number;
|
||||||
|
wordCount?: number;
|
||||||
|
linkValidation?: {
|
||||||
|
valid: string[];
|
||||||
|
brokenNotes: Array<{ link: string; line: number; context: string }>;
|
||||||
|
brokenHeadings: Array<{ link: string; line: number; context: string; note: string }>;
|
||||||
|
summary: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: UpdateNoteResult = {
|
||||||
success: true,
|
success: true,
|
||||||
path: file.path,
|
path: file.path,
|
||||||
versionId: VersionUtils.generateVersionId(file),
|
versionId: VersionUtils.generateVersionId(file),
|
||||||
modified: file.stat.mtime
|
modified: file.stat.mtime,
|
||||||
|
wordCount: ContentUtils.countWords(content)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add word count
|
|
||||||
result.wordCount = ContentUtils.countWords(content);
|
|
||||||
|
|
||||||
// Add link validation if requested
|
// Add link validation if requested
|
||||||
if (validateLinks) {
|
if (validateLinks) {
|
||||||
result.linkValidation = await LinkUtils.validateLinks(
|
result.linkValidation = await LinkUtils.validateLinks(
|
||||||
@@ -731,7 +743,7 @@ export class NoteTools {
|
|||||||
*/
|
*/
|
||||||
async updateFrontmatter(
|
async updateFrontmatter(
|
||||||
path: string,
|
path: string,
|
||||||
patch?: Record<string, any>,
|
patch?: Record<string, YAMLValue>,
|
||||||
remove: string[] = [],
|
remove: string[] = [],
|
||||||
ifMatch?: string
|
ifMatch?: string
|
||||||
): Promise<CallToolResult> {
|
): Promise<CallToolResult> {
|
||||||
|
|||||||
@@ -385,8 +385,10 @@ export class VaultTools {
|
|||||||
// In most cases, this will be 0 for directories
|
// In most cases, this will be 0 for directories
|
||||||
let modified = 0;
|
let modified = 0;
|
||||||
try {
|
try {
|
||||||
if ((folder as any).stat && typeof (folder as any).stat.mtime === 'number') {
|
// TFolder doesn't officially have stat, but it may exist in practice
|
||||||
modified = (folder as any).stat.mtime;
|
const folderWithStat = folder as TFolder & { stat?: { mtime?: number } };
|
||||||
|
if (folderWithStat.stat && typeof folderWithStat.stat.mtime === 'number') {
|
||||||
|
modified = folderWithStat.stat.mtime;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Silently fail - modified will remain 0
|
// Silently fail - modified will remain 0
|
||||||
|
|||||||
@@ -1,22 +1,39 @@
|
|||||||
// MCP Protocol Types
|
// MCP Protocol Types
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON-RPC compatible value types
|
||||||
|
*/
|
||||||
|
export type JSONValue =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null
|
||||||
|
| JSONValue[]
|
||||||
|
| { [key: string]: JSONValue };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON-RPC parameters can be an object or array
|
||||||
|
*/
|
||||||
|
export type JSONRPCParams = { [key: string]: JSONValue } | JSONValue[];
|
||||||
|
|
||||||
export interface JSONRPCRequest {
|
export interface JSONRPCRequest {
|
||||||
jsonrpc: "2.0";
|
jsonrpc: "2.0";
|
||||||
id?: string | number;
|
id?: string | number;
|
||||||
method: string;
|
method: string;
|
||||||
params?: any;
|
params?: JSONRPCParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JSONRPCResponse {
|
export interface JSONRPCResponse {
|
||||||
jsonrpc: "2.0";
|
jsonrpc: "2.0";
|
||||||
id: string | number | null;
|
id: string | number | null;
|
||||||
result?: any;
|
result?: JSONValue;
|
||||||
error?: JSONRPCError;
|
error?: JSONRPCError;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JSONRPCError {
|
export interface JSONRPCError {
|
||||||
code: number;
|
code: number;
|
||||||
message: string;
|
message: string;
|
||||||
data?: any;
|
data?: JSONValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ErrorCodes {
|
export enum ErrorCodes {
|
||||||
@@ -38,12 +55,25 @@ export interface InitializeResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Schema property definition
|
||||||
|
*/
|
||||||
|
export interface JSONSchemaProperty {
|
||||||
|
type: string;
|
||||||
|
description?: string;
|
||||||
|
enum?: string[];
|
||||||
|
items?: JSONSchemaProperty;
|
||||||
|
properties?: Record<string, JSONSchemaProperty>;
|
||||||
|
required?: string[];
|
||||||
|
[key: string]: string | string[] | JSONSchemaProperty | Record<string, JSONSchemaProperty> | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Tool {
|
export interface Tool {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: string;
|
type: string;
|
||||||
properties: Record<string, any>;
|
properties: Record<string, JSONSchemaProperty>;
|
||||||
required?: string[];
|
required?: string[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -160,7 +190,7 @@ export interface FrontmatterSummary {
|
|||||||
title?: string;
|
title?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
aliases?: string[];
|
aliases?: string[];
|
||||||
[key: string]: any;
|
[key: string]: JSONValue | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileMetadataWithFrontmatter extends FileMetadata {
|
export interface FileMetadataWithFrontmatter extends FileMetadata {
|
||||||
@@ -179,7 +209,7 @@ export interface ParsedNote {
|
|||||||
path: string;
|
path: string;
|
||||||
hasFrontmatter: boolean;
|
hasFrontmatter: boolean;
|
||||||
frontmatter?: string;
|
frontmatter?: string;
|
||||||
parsedFrontmatter?: Record<string, any>;
|
parsedFrontmatter?: Record<string, JSONValue>;
|
||||||
content: string;
|
content: string;
|
||||||
contentWithoutFrontmatter?: string;
|
contentWithoutFrontmatter?: string;
|
||||||
wordCount?: number;
|
wordCount?: number;
|
||||||
@@ -200,9 +230,9 @@ export interface ExcalidrawMetadata {
|
|||||||
hasCompressedData?: boolean;
|
hasCompressedData?: boolean;
|
||||||
/** Drawing metadata including appState and version */
|
/** Drawing metadata including appState and version */
|
||||||
metadata?: {
|
metadata?: {
|
||||||
appState?: Record<string, any>;
|
appState?: Record<string, JSONValue>;
|
||||||
version?: number;
|
version?: number;
|
||||||
[key: string]: any;
|
[key: string]: JSONValue | undefined;
|
||||||
};
|
};
|
||||||
/** Preview text extracted from text elements section (when includePreview=true) */
|
/** Preview text extracted from text elements section (when includePreview=true) */
|
||||||
preview?: string;
|
preview?: string;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { MCPPluginSettings } from '../types/settings-types';
|
|||||||
export interface NotificationHistoryEntry {
|
export interface NotificationHistoryEntry {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
toolName: string;
|
toolName: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
args: any;
|
args: any;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
@@ -74,6 +75,7 @@ export class NotificationManager {
|
|||||||
/**
|
/**
|
||||||
* Show notification for tool call start
|
* Show notification for tool call start
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
showToolCall(toolName: string, args: any, duration?: number): void {
|
showToolCall(toolName: string, args: any, duration?: number): void {
|
||||||
if (!this.shouldShowNotification()) {
|
if (!this.shouldShowNotification()) {
|
||||||
return;
|
return;
|
||||||
@@ -140,6 +142,7 @@ export class NotificationManager {
|
|||||||
/**
|
/**
|
||||||
* Format arguments for display
|
* Format arguments for display
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private formatArgs(args: any): string {
|
private formatArgs(args: any): string {
|
||||||
if (!this.settings.showParameters) {
|
if (!this.settings.showParameters) {
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
|
// Define Electron SafeStorage interface
|
||||||
|
interface ElectronSafeStorage {
|
||||||
|
isEncryptionAvailable(): boolean;
|
||||||
|
encryptString(plainText: string): Buffer;
|
||||||
|
decryptString(encrypted: Buffer): string;
|
||||||
|
}
|
||||||
|
|
||||||
// Safely import safeStorage - may not be available in all environments
|
// Safely import safeStorage - may not be available in all environments
|
||||||
let safeStorage: any = null;
|
let safeStorage: ElectronSafeStorage | null = null;
|
||||||
try {
|
try {
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
safeStorage = electron.safeStorage || null;
|
safeStorage = electron.safeStorage || null;
|
||||||
@@ -35,7 +42,7 @@ export function encryptApiKey(apiKey: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const encrypted = safeStorage.encryptString(apiKey);
|
const encrypted = safeStorage!.encryptString(apiKey);
|
||||||
return `encrypted:${encrypted.toString('base64')}`;
|
return `encrypted:${encrypted.toString('base64')}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to encrypt API key, falling back to plaintext:', error);
|
console.error('Failed to encrypt API key, falling back to plaintext:', error);
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import { parseYaml } from 'obsidian';
|
import { parseYaml } from 'obsidian';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* YAML value types that can appear in frontmatter
|
||||||
|
*/
|
||||||
|
export type YAMLValue =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null
|
||||||
|
| YAMLValue[]
|
||||||
|
| { [key: string]: YAMLValue };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for parsing and extracting frontmatter from markdown files
|
* Utility class for parsing and extracting frontmatter from markdown files
|
||||||
*/
|
*/
|
||||||
@@ -11,7 +22,7 @@ export class FrontmatterUtils {
|
|||||||
static extractFrontmatter(content: string): {
|
static extractFrontmatter(content: string): {
|
||||||
hasFrontmatter: boolean;
|
hasFrontmatter: boolean;
|
||||||
frontmatter: string;
|
frontmatter: string;
|
||||||
parsedFrontmatter: Record<string, any> | null;
|
parsedFrontmatter: Record<string, YAMLValue> | null;
|
||||||
content: string;
|
content: string;
|
||||||
contentWithoutFrontmatter: string;
|
contentWithoutFrontmatter: string;
|
||||||
} {
|
} {
|
||||||
@@ -59,7 +70,7 @@ export class FrontmatterUtils {
|
|||||||
const contentWithoutFrontmatter = contentLines.join('\n');
|
const contentWithoutFrontmatter = contentLines.join('\n');
|
||||||
|
|
||||||
// Parse YAML using Obsidian's built-in parser
|
// Parse YAML using Obsidian's built-in parser
|
||||||
let parsedFrontmatter: Record<string, any> | null = null;
|
let parsedFrontmatter: Record<string, YAMLValue> | null = null;
|
||||||
try {
|
try {
|
||||||
parsedFrontmatter = parseYaml(frontmatter) || {};
|
parsedFrontmatter = parseYaml(frontmatter) || {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -80,17 +91,17 @@ export class FrontmatterUtils {
|
|||||||
* Extract only the frontmatter summary (common fields)
|
* Extract only the frontmatter summary (common fields)
|
||||||
* Useful for list operations without reading full content
|
* Useful for list operations without reading full content
|
||||||
*/
|
*/
|
||||||
static extractFrontmatterSummary(parsedFrontmatter: Record<string, any> | null): {
|
static extractFrontmatterSummary(parsedFrontmatter: Record<string, YAMLValue> | null): {
|
||||||
title?: string;
|
title?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
aliases?: string[];
|
aliases?: string[];
|
||||||
[key: string]: any;
|
[key: string]: YAMLValue | undefined;
|
||||||
} | null {
|
} | null {
|
||||||
if (!parsedFrontmatter) {
|
if (!parsedFrontmatter) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const summary: Record<string, any> = {};
|
const summary: Record<string, YAMLValue> = {};
|
||||||
|
|
||||||
// Extract common fields
|
// Extract common fields
|
||||||
if (parsedFrontmatter.title) {
|
if (parsedFrontmatter.title) {
|
||||||
@@ -136,7 +147,7 @@ export class FrontmatterUtils {
|
|||||||
* Serialize frontmatter object to YAML string with delimiters
|
* Serialize frontmatter object to YAML string with delimiters
|
||||||
* Returns the complete frontmatter block including --- delimiters
|
* Returns the complete frontmatter block including --- delimiters
|
||||||
*/
|
*/
|
||||||
static serializeFrontmatter(data: Record<string, any>): string {
|
static serializeFrontmatter(data: Record<string, YAMLValue>): string {
|
||||||
if (!data || Object.keys(data).length === 0) {
|
if (!data || Object.keys(data).length === 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -203,7 +214,7 @@ export class FrontmatterUtils {
|
|||||||
isExcalidraw: boolean;
|
isExcalidraw: boolean;
|
||||||
elementCount?: number;
|
elementCount?: number;
|
||||||
hasCompressedData?: boolean;
|
hasCompressedData?: boolean;
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, YAMLValue>;
|
||||||
} {
|
} {
|
||||||
try {
|
try {
|
||||||
// Excalidraw files are typically markdown with a code block containing JSON
|
// Excalidraw files are typically markdown with a code block containing JSON
|
||||||
@@ -287,7 +298,7 @@ export class FrontmatterUtils {
|
|||||||
|
|
||||||
// Check if data is compressed (base64 encoded)
|
// Check if data is compressed (base64 encoded)
|
||||||
const trimmedJson = jsonString.trim();
|
const trimmedJson = jsonString.trim();
|
||||||
let jsonData: any;
|
let jsonData: Record<string, YAMLValue>;
|
||||||
|
|
||||||
if (trimmedJson.startsWith('N4KAk') || !trimmedJson.startsWith('{')) {
|
if (trimmedJson.startsWith('N4KAk') || !trimmedJson.startsWith('{')) {
|
||||||
// Data is compressed - try to decompress
|
// Data is compressed - try to decompress
|
||||||
@@ -328,9 +339,9 @@ export class FrontmatterUtils {
|
|||||||
|
|
||||||
// Parse the JSON (uncompressed format)
|
// Parse the JSON (uncompressed format)
|
||||||
jsonData = JSON.parse(trimmedJson);
|
jsonData = JSON.parse(trimmedJson);
|
||||||
|
|
||||||
// Count elements
|
// Count elements
|
||||||
const elementCount = jsonData.elements ? jsonData.elements.length : 0;
|
const elementCount = Array.isArray(jsonData.elements) ? jsonData.elements.length : 0;
|
||||||
|
|
||||||
// Check for compressed data (files or images)
|
// Check for compressed data (files or images)
|
||||||
const hasCompressedData = !!(jsonData.files && Object.keys(jsonData.files).length > 0);
|
const hasCompressedData = !!(jsonData.files && Object.keys(jsonData.files).length > 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user