From c9c1ff17786709c02f9b07b08e8f068e1c131f39 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 23 Jun 2025 22:22:03 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8refactor:=20clean=20up=20core=20node?= =?UTF-8?q?=20packages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/README.md | 3 - core/package.json | 3 - core/rolldown.config.mjs | 33 +-- core/src/browser/core.ts | 20 -- .../extensions/engines/LocalOAIEngine.ts | 42 +--- core/src/node/api/common/adapter.test.ts | 10 - core/src/node/api/common/adapter.ts | 37 ---- core/src/node/api/common/handler.test.ts | 25 --- core/src/node/api/common/handler.ts | 20 -- core/src/node/api/index.ts | 1 - .../src/node/api/processors/Processor.test.ts | 6 - core/src/node/api/processors/Processor.ts | 3 - core/src/node/api/processors/app.test.ts | 50 ----- core/src/node/api/processors/app.ts | 83 ------- .../src/node/api/processors/extension.test.ts | 40 ---- core/src/node/api/processors/extension.ts | 88 -------- core/src/node/api/processors/fs.test.ts | 18 -- core/src/node/api/processors/fs.ts | 94 -------- core/src/node/api/processors/fsExt.test.ts | 34 --- core/src/node/api/processors/fsExt.ts | 130 ----------- core/src/node/extension/extension.test.ts | 122 ---------- core/src/node/extension/extension.ts | 209 ------------------ core/src/node/extension/index.test.ts | 7 - core/src/node/extension/index.ts | 136 ------------ core/src/node/extension/manager.test.ts | 28 --- core/src/node/extension/manager.ts | 45 ---- core/src/node/extension/store.test.ts | 43 ---- core/src/node/extension/store.ts | 125 ----------- core/src/node/helper/config.test.ts | 19 -- core/src/node/helper/config.ts | 91 -------- core/src/node/helper/index.ts | 5 - core/src/node/helper/logger.test.ts | 47 ---- core/src/node/helper/logger.ts | 81 ------- core/src/node/helper/module.test.ts | 23 -- core/src/node/helper/module.ts | 31 --- core/src/node/helper/path.test.ts | 29 --- core/src/node/helper/path.ts | 37 ---- core/src/node/helper/resource.test.ts | 9 - core/src/node/helper/resource.ts | 7 - core/src/node/index.ts | 8 - .../src/@types/global.d.ts | 1 - 41 files changed, 3 insertions(+), 1840 deletions(-) delete mode 100644 core/src/node/api/common/adapter.test.ts delete mode 100644 core/src/node/api/common/adapter.ts delete mode 100644 core/src/node/api/common/handler.test.ts delete mode 100644 core/src/node/api/common/handler.ts delete mode 100644 core/src/node/api/index.ts delete mode 100644 core/src/node/api/processors/Processor.test.ts delete mode 100644 core/src/node/api/processors/Processor.ts delete mode 100644 core/src/node/api/processors/app.test.ts delete mode 100644 core/src/node/api/processors/app.ts delete mode 100644 core/src/node/api/processors/extension.test.ts delete mode 100644 core/src/node/api/processors/extension.ts delete mode 100644 core/src/node/api/processors/fs.test.ts delete mode 100644 core/src/node/api/processors/fs.ts delete mode 100644 core/src/node/api/processors/fsExt.test.ts delete mode 100644 core/src/node/api/processors/fsExt.ts delete mode 100644 core/src/node/extension/extension.test.ts delete mode 100644 core/src/node/extension/extension.ts delete mode 100644 core/src/node/extension/index.test.ts delete mode 100644 core/src/node/extension/index.ts delete mode 100644 core/src/node/extension/manager.test.ts delete mode 100644 core/src/node/extension/manager.ts delete mode 100644 core/src/node/extension/store.test.ts delete mode 100644 core/src/node/extension/store.ts delete mode 100644 core/src/node/helper/config.test.ts delete mode 100644 core/src/node/helper/config.ts delete mode 100644 core/src/node/helper/index.ts delete mode 100644 core/src/node/helper/logger.test.ts delete mode 100644 core/src/node/helper/logger.ts delete mode 100644 core/src/node/helper/module.test.ts delete mode 100644 core/src/node/helper/module.ts delete mode 100644 core/src/node/helper/path.test.ts delete mode 100644 core/src/node/helper/path.ts delete mode 100644 core/src/node/helper/resource.test.ts delete mode 100644 core/src/node/helper/resource.ts delete mode 100644 core/src/node/index.ts diff --git a/core/README.md b/core/README.md index e22bed42d..aeb92b084 100644 --- a/core/README.md +++ b/core/README.md @@ -9,9 +9,6 @@ ```js // Web / extension runtime import * as core from '@janhq/core' - -// Node runtime -import * as node from '@janhq/core/node' ``` ## Build an Extension diff --git a/core/package.json b/core/package.json index 22c815e5b..f97966e3c 100644 --- a/core/package.json +++ b/core/package.json @@ -25,9 +25,6 @@ "@npmcli/arborist": "^7.1.0", "@types/jest": "^30.0.0", "@types/node": "^22.10.0", - "@types/pacote": "^11.1.7", - "@types/request": "^2.48.12", - "electron": "33.2.1", "eslint": "8.57.0", "eslint-plugin-jest": "^27.9.0", "jest": "^30.0.3", diff --git a/core/rolldown.config.mjs b/core/rolldown.config.mjs index ea488df33..fd3329ee0 100644 --- a/core/rolldown.config.mjs +++ b/core/rolldown.config.mjs @@ -15,36 +15,5 @@ export default defineConfig([ NODE: JSON.stringify(`${pkgJson.name}/${pkgJson.node}`), VERSION: JSON.stringify(pkgJson.version), }, - }, - { - input: 'src/node/index.ts', - external: [ - 'fs/promises', - 'path', - 'pacote', - '@types/pacote', - '@npmcli/arborist', - 'ulidx', - 'fs', - 'request', - 'crypto', - 'url', - 'http', - 'os', - 'util', - 'child_process', - 'electron', - 'request-progress', - ], - output: { - format: 'cjs', - file: 'dist/node/index.cjs.js', - sourcemap: true, - inlineDynamicImports: true, - }, - resolve: { - extensions: ['.js', '.ts'], - }, - platform: 'node', - }, + } ]) diff --git a/core/src/browser/core.ts b/core/src/browser/core.ts index 3025ba963..93bea50c9 100644 --- a/core/src/browser/core.ts +++ b/core/src/browser/core.ts @@ -1,24 +1,5 @@ import { SystemInformation } from '../types' -/** - * Execute a extension module function in main process - * - * @param extension extension name to import - * @param method function name to execute - * @param args arguments to pass to the function - * @returns Promise - * - */ -const executeOnMain: (extension: string, method: string, ...args: any[]) => Promise = ( - extension, - method, - ...args -) => { - if ('electronAPI' in window && window.electronAPI) - return globalThis.core?.api?.invokeExtensionFunc(extension, method, ...args) - return () => {} -} - /** * Gets Jan's data folder path. * @@ -127,7 +108,6 @@ export type RegisterExtensionPoint = ( * Functions exports */ export { - executeOnMain, getJanDataFolderPath, openFileExplorer, getResourcePath, diff --git a/core/src/browser/extensions/engines/LocalOAIEngine.ts b/core/src/browser/extensions/engines/LocalOAIEngine.ts index f26c5b573..7c465384c 100644 --- a/core/src/browser/extensions/engines/LocalOAIEngine.ts +++ b/core/src/browser/extensions/engines/LocalOAIEngine.ts @@ -1,4 +1,3 @@ -import { executeOnMain, systemInformation, dirName, joinPath, getJanDataFolderPath } from '../../core' import { events } from '../../events' import { Model, ModelEvent } from '../../../types' import { OAIEngine } from './OAIEngine' @@ -29,46 +28,9 @@ export abstract class LocalOAIEngine extends OAIEngine { /** * Load the model. */ - async loadModel(model: Model & { file_path?: string }): Promise { - if (model.engine.toString() !== this.provider) return - const modelFolder = 'file_path' in model && model.file_path ? await dirName(model.file_path) : await this.getModelFilePath(model.id) - const systemInfo = await systemInformation() - const res = await executeOnMain( - this.nodeModule, - this.loadModelFunctionName, - { - modelFolder, - model, - }, - systemInfo - ) - - if (res?.error) { - events.emit(ModelEvent.OnModelFail, { error: res.error }) - return Promise.reject(res.error) - } else { - this.loadedModel = model - events.emit(ModelEvent.OnModelReady, model) - return Promise.resolve() - } - } + async loadModel(model: Model & { file_path?: string }): Promise {} /** * Stops the model. */ - async unloadModel(model?: Model) { - if (model?.engine && model.engine?.toString() !== this.provider) return Promise.resolve() - - this.loadedModel = undefined - await executeOnMain(this.nodeModule, this.unloadModelFunctionName).then(() => { - events.emit(ModelEvent.OnModelStopped, {}) - }) - } - - /// Legacy - private getModelFilePath = async ( - id: string, - ): Promise => { - return joinPath([await getJanDataFolderPath(), 'models', id]) - } - /// + async unloadModel(model?: Model) {} } diff --git a/core/src/node/api/common/adapter.test.ts b/core/src/node/api/common/adapter.test.ts deleted file mode 100644 index 38fd2857f..000000000 --- a/core/src/node/api/common/adapter.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RequestAdapter } from './adapter'; - -it('should return undefined for unknown route', () => { - const adapter = new RequestAdapter(); - const route = 'unknownRoute'; - - const result = adapter.process(route, 'arg1', 'arg2'); - - expect(result).toBeUndefined(); -}); diff --git a/core/src/node/api/common/adapter.ts b/core/src/node/api/common/adapter.ts deleted file mode 100644 index b0c8173a9..000000000 --- a/core/src/node/api/common/adapter.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - AppRoute, - ExtensionRoute, - FileManagerRoute, - FileSystemRoute, -} from '../../../types/api' -import { FileSystem } from '../processors/fs' -import { Extension } from '../processors/extension' -import { FSExt } from '../processors/fsExt' -import { App } from '../processors/app' - -export class RequestAdapter { - fileSystem: FileSystem - extension: Extension - fsExt: FSExt - app: App - - constructor(observer?: Function) { - this.fileSystem = new FileSystem() - this.extension = new Extension() - this.fsExt = new FSExt() - this.app = new App() - } - - // TODO: Clearer Factory pattern here - process(route: string, ...args: any) { - if (route in FileSystemRoute) { - return this.fileSystem.process(route, ...args) - } else if (route in ExtensionRoute) { - return this.extension.process(route, ...args) - } else if (route in FileManagerRoute) { - return this.fsExt.process(route, ...args) - } else if (route in AppRoute) { - return this.app.process(route, ...args) - } - } -} diff --git a/core/src/node/api/common/handler.test.ts b/core/src/node/api/common/handler.test.ts deleted file mode 100644 index bd55d41cc..000000000 --- a/core/src/node/api/common/handler.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { CoreRoutes } from '../../../types/api'; -import { RequestHandler } from './handler'; -import { RequestAdapter } from './adapter'; - -it('should not call handler if CoreRoutes is empty', () => { - const mockHandler = jest.fn(); - const mockObserver = jest.fn(); - const requestHandler = new RequestHandler(mockHandler, mockObserver); - - CoreRoutes.length = 0; // Ensure CoreRoutes is empty - - requestHandler.handle(); - - expect(mockHandler).not.toHaveBeenCalled(); -}); - - -it('should initialize handler and adapter correctly', () => { - const mockHandler = jest.fn(); - const mockObserver = jest.fn(); - const requestHandler = new RequestHandler(mockHandler, mockObserver); - - expect(requestHandler.handler).toBe(mockHandler); - expect(requestHandler.adapter).toBeInstanceOf(RequestAdapter); -}); diff --git a/core/src/node/api/common/handler.ts b/core/src/node/api/common/handler.ts deleted file mode 100644 index 5cf232d8a..000000000 --- a/core/src/node/api/common/handler.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { CoreRoutes } from '../../../types/api' -import { RequestAdapter } from './adapter' - -export type Handler = (route: string, args: any) => any - -export class RequestHandler { - handler: Handler - adapter: RequestAdapter - - constructor(handler: Handler, observer?: Function) { - this.handler = handler - this.adapter = new RequestAdapter(observer) - } - - handle() { - CoreRoutes.map((route) => { - this.handler(route, async (...args: any[]) => this.adapter.process(route, ...args)) - }) - } -} diff --git a/core/src/node/api/index.ts b/core/src/node/api/index.ts deleted file mode 100644 index 56becd054..000000000 --- a/core/src/node/api/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './common/handler' diff --git a/core/src/node/api/processors/Processor.test.ts b/core/src/node/api/processors/Processor.test.ts deleted file mode 100644 index fd913c481..000000000 --- a/core/src/node/api/processors/Processor.test.ts +++ /dev/null @@ -1,6 +0,0 @@ - -import { Processor } from './Processor'; - -it('should be defined', () => { - expect(Processor).toBeDefined(); -}); diff --git a/core/src/node/api/processors/Processor.ts b/core/src/node/api/processors/Processor.ts deleted file mode 100644 index 8ef0c6e19..000000000 --- a/core/src/node/api/processors/Processor.ts +++ /dev/null @@ -1,3 +0,0 @@ -export abstract class Processor { - abstract process(key: string, ...args: any[]): any -} diff --git a/core/src/node/api/processors/app.test.ts b/core/src/node/api/processors/app.test.ts deleted file mode 100644 index f0e45af74..000000000 --- a/core/src/node/api/processors/app.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -jest.mock('../../helper', () => ({ - ...jest.requireActual('../../helper'), - getJanDataFolderPath: () => './app', -})) -import { App } from './app' - -it('should correctly retrieve basename', () => { - const app = new App() - const result = app.baseName('/path/to/file.txt') - expect(result).toBe('file.txt') -}) - -it('should correctly identify subdirectories', () => { - const app = new App() - const basePath = process.platform === 'win32' ? 'C:\\path\\to' : '/path/to' - const subPath = - process.platform === 'win32' ? 'C:\\path\\to\\subdir' : '/path/to/subdir' - const result = app.isSubdirectory(basePath, subPath) - expect(result).toBe(true) -}) - -it('should correctly join multiple paths', () => { - const app = new App() - const result = app.joinPath(['path', 'to', 'file']) - const expectedPath = - process.platform === 'win32' ? 'path\\to\\file' : 'path/to/file' - expect(result).toBe(expectedPath) -}) - -it('should call correct function with provided arguments using process method', () => { - const app = new App() - const mockFunc = jest.fn() - app.joinPath = mockFunc - app.process('joinPath', ['path1', 'path2']) - expect(mockFunc).toHaveBeenCalledWith(['path1', 'path2']) -}) - -it('should retrieve the directory name from a file path (Unix/Windows)', async () => { - const app = new App() - const path = 'C:/Users/John Doe/Desktop/file.txt' - expect(await app.dirName(path)).toBe('C:/Users/John Doe/Desktop') -}) - -it('should retrieve the directory name when using file protocol', async () => { - const app = new App() - const path = 'file:/models/file.txt' - expect(await app.dirName(path)).toBe( - process.platform === 'win32' ? 'app\\models' : 'app/models' - ) -}) diff --git a/core/src/node/api/processors/app.ts b/core/src/node/api/processors/app.ts deleted file mode 100644 index d35fd1fd6..000000000 --- a/core/src/node/api/processors/app.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { basename, dirname, isAbsolute, join, relative } from 'path' - -import { Processor } from './Processor' -import { - log as writeLog, - getAppConfigurations as appConfiguration, - updateAppConfiguration, - normalizeFilePath, - getJanDataFolderPath, -} from '../../helper' -import { readdirSync, readFileSync } from 'fs' - -export class App implements Processor { - observer?: Function - - constructor(observer?: Function) { - this.observer = observer - } - - process(key: string, ...args: any[]): any { - const instance = this as any - const func = instance[key] - return func(...args) - } - - /** - * Joins multiple paths together, respect to the current OS. - */ - joinPath(args: any) { - return join(...('args' in args ? args.args : args)) - } - - /** - * Get dirname of a file path. - * @param path - The file path to retrieve dirname. - */ - dirName(path: string) { - const arg = - path.startsWith(`file:/`) || path.startsWith(`file:\\`) - ? join(getJanDataFolderPath(), normalizeFilePath(path)) - : path - return dirname(arg) - } - - /** - * Checks if the given path is a subdirectory of the given directory. - * - * @param from - The path to check. - * @param to - The directory to check against. - */ - isSubdirectory(from: any, to: any) { - const rel = relative(from, to) - const isSubdir = rel && !rel.startsWith('..') && !isAbsolute(rel) - - if (isSubdir === '') return false - else return isSubdir - } - - /** - * Retrieve basename from given path, respect to the current OS. - */ - baseName(args: any) { - return basename(args) - } - - /** - * Log message to log file. - */ - log(args: any) { - writeLog(args) - } - - /** - * Get app configurations. - */ - getAppConfigurations() { - return appConfiguration() - } - - async updateAppConfiguration(args: any) { - await updateAppConfiguration(args) - } -} diff --git a/core/src/node/api/processors/extension.test.ts b/core/src/node/api/processors/extension.test.ts deleted file mode 100644 index 2067c5c42..000000000 --- a/core/src/node/api/processors/extension.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Extension } from './extension'; - -it('should call function associated with key in process method', () => { - const mockFunc = jest.fn(); - const extension = new Extension(); - (extension as any).testKey = mockFunc; - extension.process('testKey', 'arg1', 'arg2'); - expect(mockFunc).toHaveBeenCalledWith('arg1', 'arg2'); -}); - - -it('should_handle_empty_extension_list_for_install', async () => { - jest.mock('../../extension/store', () => ({ - installExtensions: jest.fn(() => Promise.resolve([])), - })); - const extension = new Extension(); - const result = await extension.installExtension([]); - expect(result).toEqual([]); -}); - - -it('should_handle_empty_extension_list_for_update', async () => { - jest.mock('../../extension/store', () => ({ - getExtension: jest.fn(() => ({ update: jest.fn(() => Promise.resolve(true)) })), - })); - const extension = new Extension(); - const result = await extension.updateExtension([]); - expect(result).toEqual([]); -}); - - -it('should_handle_empty_extension_list', async () => { - jest.mock('../../extension/store', () => ({ - getExtension: jest.fn(() => ({ uninstall: jest.fn(() => Promise.resolve(true)) })), - removeExtension: jest.fn(), - })); - const extension = new Extension(); - const result = await extension.uninstallExtension([]); - expect(result).toBe(true); -}); diff --git a/core/src/node/api/processors/extension.ts b/core/src/node/api/processors/extension.ts deleted file mode 100644 index c8637d004..000000000 --- a/core/src/node/api/processors/extension.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { readdirSync } from 'fs' -import { join, extname } from 'path' - -import { Processor } from './Processor' -import { ModuleManager } from '../../helper/module' -import { getJanExtensionsPath as getPath } from '../../helper' -import { - getActiveExtensions as getExtensions, - getExtension, - removeExtension, - installExtensions, -} from '../../extension/store' -import { appResourcePath } from '../../helper/path' - -export class Extension implements Processor { - observer?: Function - - constructor(observer?: Function) { - this.observer = observer - } - - process(key: string, ...args: any[]): any { - const instance = this as any - const func = instance[key] - return func(...args) - } - - invokeExtensionFunc(modulePath: string, method: string, ...params: any[]) { - const module = require(join(getPath(), modulePath)) - ModuleManager.instance.setModule(modulePath, module) - - if (typeof module[method] === 'function') { - return module[method](...params) - } else { - console.debug(module[method]) - console.error(`Function "${method}" does not exist in the module.`) - } - } - - /** - * Returns the paths of the base extensions. - * @returns An array of paths to the base extensions. - */ - async baseExtensions() { - const baseExtensionPath = join(appResourcePath(), 'pre-install') - return readdirSync(baseExtensionPath) - .filter((file) => extname(file) === '.tgz') - .map((file) => join(baseExtensionPath, file)) - } - - /**MARK: Extension Manager handlers */ - async installExtension(extensions: any) { - // Install and activate all provided extensions - const installed = await installExtensions(extensions) - return JSON.parse(JSON.stringify(installed)) - } - - // Register IPC route to uninstall a extension - async uninstallExtension(extensions: any) { - // Uninstall all provided extensions - for (const ext of extensions) { - const extension = getExtension(ext) - await extension.uninstall() - if (extension.name) removeExtension(extension.name) - } - - // Reload all renderer pages if needed - return true - } - - // Register IPC route to update a extension - async updateExtension(extensions: any) { - // Update all provided extensions - const updated: any[] = [] - for (const ext of extensions) { - const extension = getExtension(ext) - const res = await extension.update() - if (res) updated.push(extension) - } - - // Reload all renderer pages if needed - return JSON.parse(JSON.stringify(updated)) - } - - getActiveExtensions() { - return JSON.parse(JSON.stringify(getExtensions())) - } -} diff --git a/core/src/node/api/processors/fs.test.ts b/core/src/node/api/processors/fs.test.ts deleted file mode 100644 index 3cac2e2ff..000000000 --- a/core/src/node/api/processors/fs.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { FileSystem } from './fs'; - -it('should throw an error when the route does not exist in process', async () => { - const fileSystem = new FileSystem(); - await expect(fileSystem.process('nonExistentRoute', 'arg1')).rejects.toThrow(); -}); - - -it('should throw an error for invalid argument in mkdir', async () => { - const fileSystem = new FileSystem(); - expect(() => fileSystem.mkdir(123)).toThrow('mkdir error: Invalid argument [123]'); -}); - - -it('should throw an error for invalid argument in rm', async () => { - const fileSystem = new FileSystem(); - expect(() => fileSystem.rm(123)).toThrow('rm error: Invalid argument [123]'); -}); diff --git a/core/src/node/api/processors/fs.ts b/core/src/node/api/processors/fs.ts deleted file mode 100644 index 7bc5f1e20..000000000 --- a/core/src/node/api/processors/fs.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { join, resolve } from 'path' -import { normalizeFilePath } from '../../helper/path' -import { getJanDataFolderPath } from '../../helper' -import { Processor } from './Processor' -import fs from 'fs' - -export class FileSystem implements Processor { - observer?: Function - private static moduleName = 'fs' - - constructor(observer?: Function) { - this.observer = observer - } - - process(route: string, ...args: any): any { - const instance = this as any - const func = instance[route] - if (func) { - return func(...args) - } else { - return import(FileSystem.moduleName).then((mdl) => - mdl[route]( - ...args.map((arg: any, index: number) => { - const arg0 = args[0] - if ('args' in arg0) arg = arg0.args - if (Array.isArray(arg)) arg = arg[0] - if (index !== 0) { - return arg - } - if (index === 0 && typeof arg !== 'string') { - throw new Error(`Invalid argument ${JSON.stringify(args)}`) - } - const path = - arg.startsWith(`file:/`) || arg.startsWith(`file:\\`) - ? join(getJanDataFolderPath(), normalizeFilePath(arg)) - : arg - - if (path.startsWith(`http://`) || path.startsWith(`https://`)) { - return path - } - const absolutePath = resolve(path) - return absolutePath - }) - ) - ) - } - } - - rm(...args: any): Promise { - if (typeof args[0] !== 'string') { - throw new Error(`rm error: Invalid argument ${JSON.stringify(args)}`) - } - - let path = args[0] - if (path.startsWith(`file:/`) || path.startsWith(`file:\\`)) { - path = join(getJanDataFolderPath(), normalizeFilePath(path)) - } - - const absolutePath = resolve(path) - - return new Promise((resolve, reject) => { - fs.rm(absolutePath, { recursive: true, force: true }, (err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - } - - mkdir(...args: any): Promise { - if (typeof args[0] !== 'string') { - throw new Error(`mkdir error: Invalid argument ${JSON.stringify(args)}`) - } - - let path = args[0] - if (path.startsWith(`file:/`) || path.startsWith(`file:\\`)) { - path = join(getJanDataFolderPath(), normalizeFilePath(path)) - } - - const absolutePath = resolve(path) - - return new Promise((resolve, reject) => { - fs.mkdir(absolutePath, { recursive: true }, (err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - } -} diff --git a/core/src/node/api/processors/fsExt.test.ts b/core/src/node/api/processors/fsExt.test.ts deleted file mode 100644 index bfc54897a..000000000 --- a/core/src/node/api/processors/fsExt.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { FSExt } from './fsExt'; -import { defaultAppConfig } from '../../helper'; - -it('should handle errors in writeBlob', () => { - const fsExt = new FSExt(); - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - fsExt.writeBlob('invalid-path', 'data'); - expect(consoleSpy).toHaveBeenCalled(); - consoleSpy.mockRestore(); -}); - -it('should call correct function in process method', () => { - const fsExt = new FSExt(); - const mockFunction = jest.fn(); - (fsExt as any).mockFunction = mockFunction; - fsExt.process('mockFunction', 'arg1', 'arg2'); - expect(mockFunction).toHaveBeenCalledWith('arg1', 'arg2'); -}); - - -it('should return correct user home path', () => { - const fsExt = new FSExt(); - const userHomePath = fsExt.getUserHomePath(); - expect(userHomePath).toBe(defaultAppConfig().data_folder); -}); - - - -it('should return empty array when no files are provided', async () => { - const fsExt = new FSExt(); - const result = await fsExt.getGgufFiles([]); - expect(result.supportedFiles).toEqual([]); - expect(result.unsupportedFiles).toEqual([]); -}); diff --git a/core/src/node/api/processors/fsExt.ts b/core/src/node/api/processors/fsExt.ts deleted file mode 100644 index 846d0c26a..000000000 --- a/core/src/node/api/processors/fsExt.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { basename, join } from 'path' -import fs, { readdirSync } from 'fs' -import { appResourcePath, normalizeFilePath } from '../../helper/path' -import { defaultAppConfig, getJanDataFolderPath, getJanDataFolderPath as getPath } from '../../helper' -import { Processor } from './Processor' -import { FileStat } from '../../../types' - -export class FSExt implements Processor { - observer?: Function - - constructor(observer?: Function) { - this.observer = observer - } - - process(key: string, ...args: any): any { - const instance = this as any - const func = instance[key] - return func(...args) - } - - // Handles the 'getJanDataFolderPath' IPC event. This event is triggered to get the user space path. - getJanDataFolderPath() { - return Promise.resolve(getPath()) - } - - // Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path. - getResourcePath() { - return appResourcePath() - } - - // Handles the 'getUserHomePath' IPC event. This event is triggered to get the user app data path. - // CAUTION: This would not return OS home path but the app data path. - getUserHomePath() { - return defaultAppConfig().data_folder - } - - // handle fs is directory here - fileStat(path: string, outsideJanDataFolder?: boolean) { - const normalizedPath = normalizeFilePath(path) - - const fullPath = outsideJanDataFolder - ? normalizedPath - : join(getJanDataFolderPath(), normalizedPath) - const isExist = fs.existsSync(fullPath) - if (!isExist) return undefined - - const isDirectory = fs.lstatSync(fullPath).isDirectory() - const size = fs.statSync(fullPath).size - - const fileStat: FileStat = { - isDirectory, - size, - } - - return fileStat - } - - writeBlob(path: string, data: any) { - try { - const normalizedPath = normalizeFilePath(path) - - const dataBuffer = Buffer.from(data, 'base64') - const writePath = join(getJanDataFolderPath(), normalizedPath) - fs.writeFileSync(writePath, dataBuffer) - } catch (err) { - console.error(`writeFile ${path} result: ${err}`) - } - } - - copyFile(src: string, dest: string): Promise { - return new Promise((resolve, reject) => { - fs.copyFile(src, dest, (err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - } - - async getGgufFiles(paths: string[]) { - const sanitizedFilePaths: { - path: string - name: string - size: number - }[] = [] - for (const filePath of paths) { - const normalizedPath = normalizeFilePath(filePath) - - const isExist = fs.existsSync(normalizedPath) - if (!isExist) continue - const fileStats = fs.statSync(normalizedPath) - if (!fileStats) continue - if (!fileStats.isDirectory()) { - const fileName = await basename(normalizedPath) - sanitizedFilePaths.push({ - path: normalizedPath, - name: fileName, - size: fileStats.size, - }) - } else { - // allowing only one level of directory - const files = await readdirSync(normalizedPath) - - for (const file of files) { - const fullPath = await join(normalizedPath, file) - const fileStats = await fs.statSync(fullPath) - if (!fileStats || fileStats.isDirectory()) continue - - sanitizedFilePaths.push({ - path: fullPath, - name: file, - size: fileStats.size, - }) - } - } - } - const unsupportedFiles = sanitizedFilePaths.filter( - (file) => !file.path.endsWith('.gguf') - ) - const supportedFiles = sanitizedFilePaths.filter((file) => - file.path.endsWith('.gguf') - ) - return { - unsupportedFiles, - supportedFiles, - } - } -} diff --git a/core/src/node/extension/extension.test.ts b/core/src/node/extension/extension.test.ts deleted file mode 100644 index c43b5c0cb..000000000 --- a/core/src/node/extension/extension.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import Extension from './extension'; -import { join } from 'path'; -import 'pacote'; - -it('should set active and call emitUpdate', () => { - const extension = new Extension(); - extension.emitUpdate = jest.fn(); - - extension.setActive(true); - - expect(extension._active).toBe(true); - expect(extension.emitUpdate).toHaveBeenCalled(); -}); - - -it('should return correct specifier', () => { - const origin = 'test-origin'; - const options = { version: '1.0.0' }; - const extension = new Extension(origin, options); - - expect(extension.specifier).toBe('test-origin@1.0.0'); -}); - - -it('should set origin and installOptions in constructor', () => { - const origin = 'test-origin'; - const options = { someOption: true }; - const extension = new Extension(origin, options); - - expect(extension.origin).toBe(origin); - expect(extension.installOptions.someOption).toBe(true); - expect(extension.installOptions.fullMetadata).toBe(true); // default option -}); - -it('should install extension and set url', async () => { - const origin = 'test-origin'; - const options = {}; - const extension = new Extension(origin, options); - - const mockManifest = { - name: 'test-name', - productName: 'Test Product', - version: '1.0.0', - main: 'index.js', - description: 'Test description' - }; - - jest.mock('pacote', () => ({ - manifest: jest.fn().mockResolvedValue(mockManifest), - extract: jest.fn().mockResolvedValue(null) - })); - - extension.emitUpdate = jest.fn(); - await extension._install(); - - expect(extension.url).toBe('extension://test-name/index.js'); - expect(extension.emitUpdate).toHaveBeenCalled(); -}); - - -it('should call all listeners in emitUpdate', () => { - const extension = new Extension(); - const callback1 = jest.fn(); - const callback2 = jest.fn(); - - extension.subscribe('listener1', callback1); - extension.subscribe('listener2', callback2); - - extension.emitUpdate(); - - expect(callback1).toHaveBeenCalledWith(extension); - expect(callback2).toHaveBeenCalledWith(extension); -}); - - -it('should remove listener in unsubscribe', () => { - const extension = new Extension(); - const callback = jest.fn(); - - extension.subscribe('testListener', callback); - extension.unsubscribe('testListener'); - - expect(extension.listeners['testListener']).toBeUndefined(); -}); - - -it('should add listener in subscribe', () => { - const extension = new Extension(); - const callback = jest.fn(); - - extension.subscribe('testListener', callback); - - expect(extension.listeners['testListener']).toBe(callback); -}); - - -it('should set properties from manifest', async () => { - const origin = 'test-origin'; - const options = {}; - const extension = new Extension(origin, options); - - const mockManifest = { - name: 'test-name', - productName: 'Test Product', - version: '1.0.0', - main: 'index.js', - description: 'Test description' - }; - - jest.mock('pacote', () => ({ - manifest: jest.fn().mockResolvedValue(mockManifest) - })); - - await extension.getManifest(); - - expect(extension.name).toBe('test-name'); - expect(extension.productName).toBe('Test Product'); - expect(extension.version).toBe('1.0.0'); - expect(extension.main).toBe('index.js'); - expect(extension.description).toBe('Test description'); -}); - diff --git a/core/src/node/extension/extension.ts b/core/src/node/extension/extension.ts deleted file mode 100644 index cd2bb0e06..000000000 --- a/core/src/node/extension/extension.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { rmdirSync } from 'fs' -import { resolve, join } from 'path' -import { ExtensionManager } from './manager' - -/** - * An NPM package that can be used as an extension. - * Used to hold all the information and functions necessary to handle the extension lifecycle. - */ -export default class Extension { - /** - * @property {string} origin Original specification provided to fetch the package. - * @property {Object} installOptions Options provided to pacote when fetching the manifest. - * @property {name} name The name of the extension as defined in the manifest. - * @property {name} productName The display name of the extension as defined in the manifest. - * @property {string} url Electron URL where the package can be accessed. - * @property {string} version Version of the package as defined in the manifest. - * @property {string} main The entry point as defined in the main entry of the manifest. - * @property {string} description The description of extension as defined in the manifest. - */ - origin?: string - installOptions: any - name?: string - productName?: string - url?: string - version?: string - main?: string - description?: string - - /** @private */ - _active = false - - /** - * @private - * @property {Object.} #listeners A list of callbacks to be executed when the Extension is updated. - */ - listeners: Record void> = {} - - /** - * Set installOptions with defaults for options that have not been provided. - * @param {string} [origin] Original specification provided to fetch the package. - * @param {Object} [options] Options provided to pacote when fetching the manifest. - */ - constructor(origin?: string, options = {}) { - const Arborist = require('@npmcli/arborist') - const defaultOpts = { - version: false, - fullMetadata: true, - Arborist, - } - - this.origin = origin - this.installOptions = { ...defaultOpts, ...options } - } - - /** - * Package name with version number. - * @type {string} - */ - get specifier() { - return ( - this.origin + - (this.installOptions.version ? '@' + this.installOptions.version : '') - ) - } - - /** - * Whether the extension should be registered with its activation points. - * @type {boolean} - */ - get active() { - return this._active - } - - /** - * Set Package details based on it's manifest - * @returns {Promise.} Resolves to true when the action completed - */ - async getManifest() { - // Get the package's manifest (package.json object) - try { - const pacote = require('pacote') - return pacote - .manifest(this.specifier, this.installOptions) - .then((mnf: any) => { - // set the Package properties based on the it's manifest - this.name = mnf.name - this.productName = mnf.productName as string | undefined - this.version = mnf.version - this.main = mnf.main - this.description = mnf.description - }) - } catch (error) { - throw new Error( - `Package ${this.origin} does not contain a valid manifest: ${error}` - ) - } - } - - /** - * Extract extension to extensions folder. - * @returns {Promise.} This extension - * @private - */ - async _install() { - try { - // import the manifest details - await this.getManifest() - - // Install the package in a child folder of the given folder - const pacote = require('pacote') - await pacote.extract( - this.specifier, - join( - ExtensionManager.instance.getExtensionsPath() ?? '', - this.name ?? '' - ), - this.installOptions - ) - - // Set the url using the custom extensions protocol - this.url = `extension://${this.name}/${this.main}` - - this.emitUpdate() - } catch (err) { - // Ensure the extension is not stored and the folder is removed if the installation fails - this.setActive(false) - throw err - } - - return [this] - } - - /** - * Subscribe to updates of this extension - * @param {string} name name of the callback to register - * @param {callback} cb The function to execute on update - */ - subscribe(name: string, cb: () => void) { - this.listeners[name] = cb - } - - /** - * Remove subscription - * @param {string} name name of the callback to remove - */ - unsubscribe(name: string) { - delete this.listeners[name] - } - - /** - * Execute listeners - */ - emitUpdate() { - for (const cb in this.listeners) { - this.listeners[cb].call(null, this) - } - } - - /** - * Check for updates and install if available. - * @param {string} version The version to update to. - * @returns {boolean} Whether an update was performed. - */ - async update(version = false) { - if (await this.isUpdateAvailable()) { - this.installOptions.version = version - await this._install() - return true - } - - return false - } - - /** - * Check if a new version of the extension is available at the origin. - * @returns the latest available version if a new version is available or false if not. - */ - async isUpdateAvailable() { - const pacote = require('pacote') - if (this.origin) { - return pacote.manifest(this.origin).then((mnf: any) => { - return mnf.version !== this.version ? mnf.version : false - }) - } - } - - /** - * Remove extension and refresh renderers. - * @returns {Promise} - */ - async uninstall(): Promise { - const path = ExtensionManager.instance.getExtensionsPath() - const extPath = resolve(path ?? '', this.name ?? '') - rmdirSync(extPath, { recursive: true }) - - this.emitUpdate() - } - - /** - * Set a extension's active state. This determines if a extension should be loaded on initialisation. - * @param {boolean} active State to set _active to - * @returns {Extension} This extension - */ - setActive(active: boolean) { - this._active = active - this.emitUpdate() - return this - } -} diff --git a/core/src/node/extension/index.test.ts b/core/src/node/extension/index.test.ts deleted file mode 100644 index ce9cb0d0a..000000000 --- a/core/src/node/extension/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ - - - import { useExtensions } from './index' - - test('testUseExtensionsMissingPath', () => { - expect(() => useExtensions(undefined as any)).toThrowError('A path to the extensions folder is required to use extensions') - }) diff --git a/core/src/node/extension/index.ts b/core/src/node/extension/index.ts deleted file mode 100644 index 994fc97f2..000000000 --- a/core/src/node/extension/index.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { readFileSync } from 'fs' - -import { normalize } from 'path' - -import Extension from './extension' -import { - getAllExtensions, - removeExtension, - persistExtensions, - installExtensions, - getExtension, - getActiveExtensions, - addExtension, -} from './store' -import { ExtensionManager } from './manager' - -export function init(options: any) { - // Create extensions protocol to serve extensions to renderer - registerExtensionProtocol() - - // perform full setup if extensionsPath is provided - if (options.extensionsPath) { - return useExtensions(options.extensionsPath) - } - - return {} -} - -/** - * Create extensions protocol to provide extensions to renderer - * @private - * @returns {boolean} Whether the protocol registration was successful - */ -async function registerExtensionProtocol() { - let electron: any = undefined - - try { - const moduleName = 'electron' - electron = await import(moduleName) - } catch (err) { - console.error('Electron is not available') - } - const extensionPath = ExtensionManager.instance.getExtensionsPath() - if (electron && electron.protocol) { - return electron.protocol?.registerFileProtocol('extension', (request: any, callback: any) => { - const entry = request.url.substr('extension://'.length - 1) - - const url = normalize(extensionPath + entry) - callback({ path: url }) - }) - } -} - -/** - * Set extensions up to run from the extensionPath folder if it is provided and - * load extensions persisted in that folder. - * @param {string} extensionsPath Path to the extensions folder. Required if not yet set up. - * @returns {extensionManager} A set of functions used to manage the extension lifecycle. - */ -export function useExtensions(extensionsPath: string) { - if (!extensionsPath) throw Error('A path to the extensions folder is required to use extensions') - // Store the path to the extensions folder - ExtensionManager.instance.setExtensionsPath(extensionsPath) - - // Remove any registered extensions - for (const extension of getAllExtensions()) { - if (extension.name) removeExtension(extension.name, false) - } - - // Read extension list from extensions folder - const extensions = JSON.parse( - readFileSync(ExtensionManager.instance.getExtensionsFile(), 'utf-8') - ) - try { - // Create and store a Extension instance for each extension in list - for (const p in extensions) { - loadExtension(extensions[p]) - } - persistExtensions() - } catch (error) { - // Throw meaningful error if extension loading fails - throw new Error( - 'Could not successfully rebuild list of installed extensions.\n' + - error + - '\nPlease check the extensions.json file in the extensions folder.' - ) - } - - // Return the extension lifecycle functions - return getStore() -} - -/** - * Check the given extension object. If it is marked for uninstalling, the extension files are removed. - * Otherwise a Extension instance for the provided object is created and added to the store. - * @private - * @param {Object} ext Extension info - */ -function loadExtension(ext: any) { - // Create new extension, populate it with ext details and save it to the store - const extension = new Extension() - - for (const key in ext) { - if (Object.prototype.hasOwnProperty.call(ext, key)) { - // Use Object.defineProperty to set the properties as writable - Object.defineProperty(extension, key, { - value: ext[key], - writable: true, - enumerable: true, - configurable: true, - }) - } - } - addExtension(extension, false) - extension.subscribe('pe-persist', persistExtensions) -} - -/** - * Returns the publicly available store functions. - * @returns {extensionManager} A set of functions used to manage the extension lifecycle. - */ -export function getStore() { - if (!ExtensionManager.instance.getExtensionsFile()) { - throw new Error( - 'The extension path has not yet been set up. Please run useExtensions before accessing the store' - ) - } - - return { - installExtensions, - getExtension, - getAllExtensions, - getActiveExtensions, - removeExtension, - } -} diff --git a/core/src/node/extension/manager.test.ts b/core/src/node/extension/manager.test.ts deleted file mode 100644 index 1c8123d21..000000000 --- a/core/src/node/extension/manager.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as fs from 'fs'; -import { join } from 'path'; -import { ExtensionManager } from './manager'; - -it('should throw an error when an invalid path is provided', () => { - const manager = new ExtensionManager(); - jest.spyOn(fs, 'existsSync').mockReturnValue(false); - expect(() => manager.setExtensionsPath('')).toThrow('Invalid path provided to the extensions folder'); -}); - - -it('should return an empty string when extensionsPath is not set', () => { - const manager = new ExtensionManager(); - expect(manager.getExtensionsFile()).toBe(join('', 'extensions.json')); -}); - - -it('should return undefined if no path is set', () => { - const manager = new ExtensionManager(); - expect(manager.getExtensionsPath()).toBeUndefined(); -}); - - -it('should return the singleton instance', () => { - const instance1 = new ExtensionManager(); - const instance2 = new ExtensionManager(); - expect(instance1).toBe(instance2); -}); diff --git a/core/src/node/extension/manager.ts b/core/src/node/extension/manager.ts deleted file mode 100644 index c66d7b163..000000000 --- a/core/src/node/extension/manager.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { join, resolve } from 'path' - -import { existsSync, mkdirSync, writeFileSync } from 'fs' - -/** - * Manages extension installation and migration. - */ - -export class ExtensionManager { - public static instance: ExtensionManager = new ExtensionManager() - - private extensionsPath: string | undefined - - constructor() { - if (ExtensionManager.instance) { - return ExtensionManager.instance - } - } - - getExtensionsPath(): string | undefined { - return this.extensionsPath - } - - setExtensionsPath(extPath: string) { - // Create folder if it does not exist - let extDir - try { - extDir = resolve(extPath) - if (extDir.length < 2) throw new Error() - - if (!existsSync(extDir)) mkdirSync(extDir) - - const extensionsJson = join(extDir, 'extensions.json') - if (!existsSync(extensionsJson)) writeFileSync(extensionsJson, '{}') - - this.extensionsPath = extDir - } catch (error) { - throw new Error('Invalid path provided to the extensions folder') - } - } - - getExtensionsFile() { - return join(this.extensionsPath ?? '', 'extensions.json') - } -} diff --git a/core/src/node/extension/store.test.ts b/core/src/node/extension/store.test.ts deleted file mode 100644 index cbaa84f7c..000000000 --- a/core/src/node/extension/store.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { getAllExtensions } from './store'; -import { getActiveExtensions } from './store'; -import { getExtension } from './store'; - -test('should return empty array when no extensions added', () => { - expect(getAllExtensions()).toEqual([]); -}); - - -test('should throw error when extension does not exist', () => { - expect(() => getExtension('nonExistentExtension')).toThrow('Extension nonExistentExtension does not exist'); -}); - -import { addExtension } from './store'; -import Extension from './extension'; - -test('should return all extensions when multiple extensions added', () => { - const ext1 = new Extension('ext1'); - ext1.name = 'ext1'; - const ext2 = new Extension('ext2'); - ext2.name = 'ext2'; - - addExtension(ext1, false); - addExtension(ext2, false); - - expect(getAllExtensions()).toEqual([ext1, ext2]); -}); - - - -test('should return only active extensions', () => { - const ext1 = new Extension('ext1'); - ext1.name = 'ext1'; - ext1.setActive(true); - const ext2 = new Extension('ext2'); - ext2.name = 'ext2'; - ext2.setActive(false); - - addExtension(ext1, false); - addExtension(ext2, false); - - expect(getActiveExtensions()).toEqual([ext1]); -}); diff --git a/core/src/node/extension/store.ts b/core/src/node/extension/store.ts deleted file mode 100644 index 630756485..000000000 --- a/core/src/node/extension/store.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { writeFileSync } from 'fs' -import Extension from './extension' -import { ExtensionManager } from './manager' - -/** - * @module store - * @private - */ - -/** - * Register of installed extensions - * @type {Object.} extension - List of installed extensions - */ -const extensions: Record = {} - -/** - * Get a extension from the stored extensions. - * @param {string} name Name of the extension to retrieve - * @returns {Extension} Retrieved extension - * @alias extensionManager.getExtension - */ -export function getExtension(name: string) { - if (!Object.prototype.hasOwnProperty.call(extensions, name)) { - throw new Error(`Extension ${name} does not exist`) - } - - return extensions[name] -} - -/** - * Get list of all extension objects. - * @returns {Array.} All extension objects - * @alias extensionManager.getAllExtensions - */ -export function getAllExtensions() { - return Object.values(extensions) -} - -/** - * Get list of active extension objects. - * @returns {Array.} Active extension objects - * @alias extensionManager.getActiveExtensions - */ -export function getActiveExtensions() { - return Object.values(extensions).filter((extension) => extension.active) -} - -/** - * Remove extension from store and maybe save stored extensions to file - * @param {string} name Name of the extension to remove - * @param {boolean} persist Whether to save the changes to extensions to file - * @returns {boolean} Whether the delete was successful - * @alias extensionManager.removeExtension - */ -export function removeExtension(name: string, persist = true) { - const del = delete extensions[name] - if (persist) persistExtensions() - return del -} - -/** - * Add extension to store and maybe save stored extensions to file - * @param {Extension} extension Extension to add to store - * @param {boolean} persist Whether to save the changes to extensions to file - * @returns {void} - */ -export function addExtension(extension: Extension, persist = true) { - if (extension.name) extensions[extension.name] = extension - if (persist) { - persistExtensions() - extension.subscribe('pe-persist', persistExtensions) - } -} - -/** - * Save stored extensions to file - * @returns {void} - */ -export function persistExtensions() { - const persistData: Record = {} - for (const name in extensions) { - persistData[name] = extensions[name] - } - writeFileSync(ExtensionManager.instance.getExtensionsFile(), JSON.stringify(persistData)) -} - -/** - * Create and install a new extension for the given specifier. - * @param {Array.} extensions A list of NPM specifiers, or installation configuration objects. - * @param {boolean} [store=true] Whether to store the installed extensions in the store - * @returns {Promise.>} New extension - * @alias extensionManager.installExtensions - */ -export async function installExtensions(extensions: any) { - const installed: Extension[] = [] - const installations = extensions.map((ext: any): Promise => { - const isObject = typeof ext === 'object' - const spec = isObject ? [ext.specifier, ext] : [ext] - const activate = isObject ? ext.activate !== false : true - - // Install and possibly activate extension - const extension = new Extension(...spec) - if (!extension.origin) { - return Promise.resolve() - } - return extension._install().then(() => { - if (activate) extension.setActive(true) - // Add extension to store if needed - addExtension(extension) - installed.push(extension) - }) - }) - - await Promise.all(installations) - - // Return list of all installed extensions - return installed -} - -/** - * @typedef {Object.} installOptions The {@link https://www.npmjs.com/package/pacote|pacote} - * options used to install the extension with some extra options. - * @param {string} specifier the NPM specifier that identifies the package. - * @param {boolean} [activate] Whether this extension should be activated after installation. Defaults to true. - */ diff --git a/core/src/node/helper/config.test.ts b/core/src/node/helper/config.test.ts deleted file mode 100644 index 617a8f7ef..000000000 --- a/core/src/node/helper/config.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { getAppConfigurations, defaultAppConfig } from './config' - -import { getJanExtensionsPath, getJanDataFolderPath } from './config' - -it('should return default config when CI is e2e', () => { - process.env.CI = 'e2e' - const config = getAppConfigurations() - expect(config).toEqual(defaultAppConfig()) -}) - -it('should return extensions path when retrieved successfully', () => { - const extensionsPath = getJanExtensionsPath() - expect(extensionsPath).not.toBeUndefined() -}) - -it('should return data folder path when retrieved successfully', () => { - const dataFolderPath = getJanDataFolderPath() - expect(dataFolderPath).not.toBeUndefined() -}) diff --git a/core/src/node/helper/config.ts b/core/src/node/helper/config.ts deleted file mode 100644 index 89955a2d6..000000000 --- a/core/src/node/helper/config.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { AppConfiguration } from '../../types' -import { join, resolve } from 'path' -import fs from 'fs' -import os from 'os' -const configurationFileName = 'settings.json' - -/** - * Getting App Configurations. - * - * @returns {AppConfiguration} The app configurations. - */ -export const getAppConfigurations = (): AppConfiguration => { - const appDefaultConfiguration = defaultAppConfig() - if (process.env.CI === 'e2e') return appDefaultConfiguration - // Retrieve Application Support folder path - // Fallback to user home directory if not found - const configurationFile = getConfigurationFilePath() - - if (!fs.existsSync(configurationFile)) { - // create default app config if we don't have one - console.debug(`App config not found, creating default config at ${configurationFile}`) - fs.writeFileSync(configurationFile, JSON.stringify(appDefaultConfiguration)) - return appDefaultConfiguration - } - - try { - const appConfigurations: AppConfiguration = JSON.parse( - fs.readFileSync(configurationFile, 'utf-8') - ) - return appConfigurations - } catch (err) { - console.error(`Failed to read app config, return default config instead! Err: ${err}`) - return defaultAppConfig() - } -} - -const getConfigurationFilePath = () => - join( - global.core?.appPath() || process.env[process.platform == 'win32' ? 'USERPROFILE' : 'HOME'], - configurationFileName - ) - -export const updateAppConfiguration = ({ - configuration, -}: { - configuration: AppConfiguration -}): Promise => { - const configurationFile = getConfigurationFilePath() - - fs.writeFileSync(configurationFile, JSON.stringify(configuration)) - return Promise.resolve() -} - -/** - * Utility function to get data folder path - * - * @returns {string} The data folder path. - */ -export const getJanDataFolderPath = (): string => { - const appConfigurations = getAppConfigurations() - return appConfigurations.data_folder -} - -/** - * Utility function to get extension path - * - * @returns {string} The extensions path. - */ -export const getJanExtensionsPath = (): string => { - const appConfigurations = getAppConfigurations() - return join(appConfigurations.data_folder, 'extensions') -} - -/** - * Default app configurations - * App Data Folder default to Electron's userData - * %APPDATA% on Windows - * $XDG_CONFIG_HOME or ~/.config on Linux - * ~/Library/Application Support on macOS - */ -export const defaultAppConfig = (): AppConfiguration => { - const { app } = require('electron') - const defaultJanDataFolder = join(app?.getPath('userData') ?? os?.homedir() ?? '', 'data') - return { - data_folder: - process.env.CI === 'e2e' - ? process.env.APP_CONFIG_PATH ?? resolve('./test-data') - : defaultJanDataFolder, - quick_ask: false, - } -} diff --git a/core/src/node/helper/index.ts b/core/src/node/helper/index.ts deleted file mode 100644 index 6464fbce2..000000000 --- a/core/src/node/helper/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './config' -export * from './logger' -export * from './module' -export * from './path' -export * from './resource' diff --git a/core/src/node/helper/logger.test.ts b/core/src/node/helper/logger.test.ts deleted file mode 100644 index 0f44bfcd4..000000000 --- a/core/src/node/helper/logger.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Logger, LoggerManager } from './logger'; - - it('should flush queued logs to registered loggers', () => { - class TestLogger extends Logger { - name = 'testLogger'; - log(args: any): void { - console.log(args); - } - } - const loggerManager = new LoggerManager(); - const testLogger = new TestLogger(); - loggerManager.register(testLogger); - const logSpy = jest.spyOn(testLogger, 'log'); - loggerManager.log('test log'); - expect(logSpy).toHaveBeenCalledWith('test log'); - }); - - - it('should unregister a logger', () => { - class TestLogger extends Logger { - name = 'testLogger'; - log(args: any): void { - console.log(args); - } - } - const loggerManager = new LoggerManager(); - const testLogger = new TestLogger(); - loggerManager.register(testLogger); - loggerManager.unregister('testLogger'); - const retrievedLogger = loggerManager.get('testLogger'); - expect(retrievedLogger).toBeUndefined(); - }); - - - it('should register and retrieve a logger', () => { - class TestLogger extends Logger { - name = 'testLogger'; - log(args: any): void { - console.log(args); - } - } - const loggerManager = new LoggerManager(); - const testLogger = new TestLogger(); - loggerManager.register(testLogger); - const retrievedLogger = loggerManager.get('testLogger'); - expect(retrievedLogger).toBe(testLogger); - }); diff --git a/core/src/node/helper/logger.ts b/core/src/node/helper/logger.ts deleted file mode 100644 index a6b3c8bef..000000000 --- a/core/src/node/helper/logger.ts +++ /dev/null @@ -1,81 +0,0 @@ -// Abstract Logger class that all loggers should extend. -export abstract class Logger { - // Each logger must have a unique name. - abstract name: string - - /** - * Log message to log file. - * This method should be overridden by subclasses to provide specific logging behavior. - */ - abstract log(args: any): void -} - -// LoggerManager is a singleton class that manages all registered loggers. -export class LoggerManager { - // Map of registered loggers, keyed by their names. - public loggers = new Map() - - // Array to store logs that are queued before the loggers are registered. - queuedLogs: any[] = [] - - // Flag to indicate whether flushLogs is currently running. - private isFlushing = false - - // Register a new logger. If a logger with the same name already exists, it will be replaced. - register(logger: Logger) { - this.loggers.set(logger.name, logger) - } - // Unregister a logger by its name. - unregister(name: string) { - this.loggers.delete(name) - } - - get(name: string) { - return this.loggers.get(name) - } - - // Flush queued logs to all registered loggers. - flushLogs() { - // If flushLogs is already running, do nothing. - if (this.isFlushing) { - return - } - - this.isFlushing = true - - while (this.queuedLogs.length > 0 && this.loggers.size > 0) { - const log = this.queuedLogs.shift() - this.loggers.forEach((logger) => { - logger.log(log) - }) - } - - this.isFlushing = false - } - - // Log message using all registered loggers. - log(args: any) { - this.queuedLogs.push(args) - - this.flushLogs() - } - - /** - * The instance of the logger. - * If an instance doesn't exist, it creates a new one. - * This ensures that there is only one LoggerManager instance at any time. - */ - static instance(): LoggerManager { - let instance: LoggerManager | undefined = global.core?.logger - if (!instance) { - instance = new LoggerManager() - if (!global.core) global.core = {} - global.core.logger = instance - } - return instance - } -} - -export const log = (...args: any) => { - LoggerManager.instance().log(args) -} diff --git a/core/src/node/helper/module.test.ts b/core/src/node/helper/module.test.ts deleted file mode 100644 index bb8327cbf..000000000 --- a/core/src/node/helper/module.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ModuleManager } from './module'; - -it('should clear all imported modules', () => { - const moduleManager = new ModuleManager(); - moduleManager.setModule('module1', { key: 'value1' }); - moduleManager.setModule('module2', { key: 'value2' }); - moduleManager.clearImportedModules(); - expect(moduleManager.requiredModules).toEqual({}); -}); - - -it('should set a module correctly', () => { - const moduleManager = new ModuleManager(); - moduleManager.setModule('testModule', { key: 'value' }); - expect(moduleManager.requiredModules['testModule']).toEqual({ key: 'value' }); -}); - - -it('should return the singleton instance', () => { - const instance1 = new ModuleManager(); - const instance2 = new ModuleManager(); - expect(instance1).toBe(instance2); -}); diff --git a/core/src/node/helper/module.ts b/core/src/node/helper/module.ts deleted file mode 100644 index 0919667df..000000000 --- a/core/src/node/helper/module.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Manages imported modules. - */ -export class ModuleManager { - public requiredModules: Record = {} - public cleaningResource = false - - public static instance: ModuleManager = new ModuleManager() - - constructor() { - if (ModuleManager.instance) { - return ModuleManager.instance - } - } - - /** - * Sets a module. - * @param {string} moduleName - The name of the module. - * @param {any | undefined} nodule - The module to set, or undefined to clear the module. - */ - setModule(moduleName: string, nodule: any | undefined) { - this.requiredModules[moduleName] = nodule - } - - /** - * Clears all imported modules. - */ - clearImportedModules() { - this.requiredModules = {} - } -} diff --git a/core/src/node/helper/path.test.ts b/core/src/node/helper/path.test.ts deleted file mode 100644 index f9a3b5766..000000000 --- a/core/src/node/helper/path.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { normalizeFilePath } from './path' - -import { jest } from '@jest/globals' -describe('Test file normalize', () => { - test('returns no file protocol prefix on Unix', async () => { - expect(normalizeFilePath('file://test.txt')).toBe('test.txt') - expect(normalizeFilePath('file:/test.txt')).toBe('test.txt') - }) - test('returns no file protocol prefix on Windows', async () => { - expect(normalizeFilePath('file:\\\\test.txt')).toBe('test.txt') - expect(normalizeFilePath('file:\\test.txt')).toBe('test.txt') - }) - - test('returns correct path when Electron is available and app is not packaged', () => { - const electronMock = { - app: { - getAppPath: jest.fn().mockReturnValue('/mocked/path'), - isPackaged: false, - }, - protocol: {}, - } - jest.mock('electron', () => electronMock) - - const { appResourcePath } = require('./path') - - const expectedPath = process.platform === 'win32' ? '\\mocked\\path' : '/mocked/path' - expect(appResourcePath()).toBe(expectedPath) - }) -}) diff --git a/core/src/node/helper/path.ts b/core/src/node/helper/path.ts deleted file mode 100644 index 5f6386640..000000000 --- a/core/src/node/helper/path.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { join } from 'path' - -/** - * Normalize file path - * Remove all file protocol prefix - * @param path - * @returns - */ -export function normalizeFilePath(path: string): string { - return path.replace(/^(file:[\\/]+)([^:\s]+)$/, '$2') -} - -/** - * App resources path - * Returns string - The current application directory. - */ -export function appResourcePath() { - try { - const electron = require('electron') - // electron - if (electron && electron.protocol) { - let appPath = join(electron.app.getAppPath(), '..', 'app.asar.unpacked') - - if (!electron.app.isPackaged) { - // for development mode - appPath = join(electron.app.getAppPath()) - } - return appPath - } - } catch (err) { - console.error('Electron is not available') - } - - // server - return join(global.core.appPath(), '../../..') -} - diff --git a/core/src/node/helper/resource.test.ts b/core/src/node/helper/resource.test.ts deleted file mode 100644 index c82d481db..000000000 --- a/core/src/node/helper/resource.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { getSystemResourceInfo } from './resource' - -it('should return the correct system resource information with a valid CPU count', async () => { - const result = await getSystemResourceInfo() - - expect(result).toEqual({ - memAvailable: 0, - }) -}) diff --git a/core/src/node/helper/resource.ts b/core/src/node/helper/resource.ts deleted file mode 100644 index 5d75e54eb..000000000 --- a/core/src/node/helper/resource.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SystemResourceInfo } from '../../types' - -export const getSystemResourceInfo = async (): Promise => { - return { - memAvailable: 0, // TODO: this should not be 0 - } -} diff --git a/core/src/node/index.ts b/core/src/node/index.ts deleted file mode 100644 index eb6027075..000000000 --- a/core/src/node/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './extension/index' -export * from './extension/extension' -export * from './extension/manager' -export * from './extension/store' -export * from './api' -export * from './helper' -export * from './../types' -export * from '../types/api' diff --git a/extensions/conversational-extension/src/@types/global.d.ts b/extensions/conversational-extension/src/@types/global.d.ts index abe60d318..4ec8b8825 100644 --- a/extensions/conversational-extension/src/@types/global.d.ts +++ b/extensions/conversational-extension/src/@types/global.d.ts @@ -6,5 +6,4 @@ interface Core { } interface Window { core?: Core | undefined - electronAPI?: any | undefined }