Skip to content

Commit

Permalink
fix: improve plugin class
Browse files Browse the repository at this point in the history
  • Loading branch information
zAlweNy26 committed Oct 21, 2024
1 parent 4dd1938 commit 7613684
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 52 deletions.
Binary file modified bun.lockb
Binary file not shown.
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@
"d3-dsv": "^2.0.0",
"date-fns": "^4.1.0",
"destr": "^2.0.3",
"eslint-plugin-oxlint": "0.10.0",
"diff": "7.0.0",
"elysia": "^1.1.22",
"eslint-plugin-oxlint": "0.10.0",
"fastembed": "^1.14.1",
"get-port-please": "^3.1.2",
"html-to-text": "^9.0.5",
Expand All @@ -100,7 +99,7 @@
"ofetch": "^1.4.1",
"officeparser": "^4.2.0",
"pdf-parse": "^1.1.1",
"pkg-types": "^1.2.1",
"pkg-types": "1.2.1",
"scule": "^1.3.0",
"turbowatch": "^2.29.4",
"typeorm": "0.3.20",
Expand All @@ -112,7 +111,6 @@
"@antfu/eslint-config": "^3.8.0",
"@total-typescript/ts-reset": "^0.6.1",
"@types/bun": "^1.1.11",
"@types/diff": "5.2.3",
"@types/lodash": "^4.17.12",
"@types/nodemon": "^1.19.6",
"@types/qs": "^6.9.16",
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { semver } from 'bun'
import isDocker from 'is-docker'
import nodemon from 'nodemon'
import { log } from './logger.ts'
Expand All @@ -7,7 +6,7 @@ import { parsedEnv } from './utils.ts'
const inDocker = isDocker()
const { watch, verbose } = parsedEnv

if (!semver.satisfies(Bun.version, '>=1.1.19')) {
if (!Bun.semver.satisfies(Bun.version, '>=1.1.19')) {
log.error('The Cat requires Bun version 1.1.19 or higher.')
process.exit(1)
}
Expand Down
54 changes: 30 additions & 24 deletions src/mad_hatter/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Dirent } from 'node:fs'
import type { PackageJson } from 'pkg-types'
import { rm } from 'node:fs/promises'
import { basename, extname, join, relative } from 'node:path'
import { basename, join } from 'node:path'
import { log } from '@logger'
import { deepDefaults, existsDir, getFilesRecursively, getRandomString, getZodDefaults } from '@utils'
import { deepDefaults, existsDir, getZodDefaults } from '@utils'
import { destr } from 'destr'
import { diffLines } from 'diff'
import _CloneDeep from 'lodash/cloneDeep.js'
import _SampleSize from 'lodash/sampleSize.js'
import { titleCase } from 'scule'
import { z } from 'zod'
import { type Form, isForm } from './form.ts'
Expand Down Expand Up @@ -47,6 +47,11 @@ function isPluginEvent(event: any): event is PluginEvent {
&& ['installed', 'enabled', 'disabled', 'removed'].includes(event.name)
}

function getRandomString(length: number) {
const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
return _SampleSize(letters, length).join('')
}

export const CatPlugin = Object.freeze({
/**
* Add some settings to the plugin
Expand Down Expand Up @@ -74,7 +79,6 @@ export class Plugin<
private _id: string
private _reloading = false
private _active = false
#oldRequirements = ''
#hooks: Hook[] = []
#fileUrls: string[] = []
tools: Tool[] = []
Expand All @@ -95,12 +99,20 @@ export class Plugin<
}

async reload() {
const tsFiles = (await getFilesRecursively(this.path)).filter(file => extname(file.name) === '.ts')
const glob = new Bun.Glob('**/*.ts')
const tsFiles = await Array.fromAsync(glob.scan({
cwd: this.path,
followSymlinks: true,
throwErrorOnBrokenSymlink: true,
}))
if (tsFiles.length === 0) log.error(new Error('Plugin must contain at least one .ts file'))

if (this._reloading) return

this._reloading = true
this.tools = []
this.forms = []
this.#hooks = []
await this.installRequirements()
await this.importAll(tsFiles)
await this.loadManifest()
Expand Down Expand Up @@ -212,28 +224,22 @@ export class Plugin<

private async installRequirements() {
log.debug('Installing plugin requirements...')
const file = Bun.file(join(this.path, 'requirements.txt'))
const file = Bun.file(join(this.path, 'package.json'))
if (await file.exists()) {
const newRequirements = await file.text()
const differences = diffLines(this.#oldRequirements, newRequirements)
const newPkgs = differences.filter(d => d.added).map(d => d.value.trim())
const oldPkgs = differences.filter(d => d.removed).map(d => d.value.trim().split('@')[0]).filter(p => p !== undefined)
this.#oldRequirements = newRequirements
try { Bun.spawnSync(['bun', 'remove', ...oldPkgs]) }
catch (error) { log.error(`Error uninstalling requirements for ${this.id}: ${error}`) }
try { Bun.spawnSync(['bun', 'i', ...newPkgs]) }
const requirements = await file.json() as PackageJson
const deps = Object.entries(requirements).filter(([key]) => key.toLowerCase().includes('dependencies')).map<string>(([_, value]) => value)
try { Bun.spawnSync(['bun', 'add', ...deps]) }
catch (error) { log.error(`Error installing requirements for ${this.id}: ${error}`) }
}
}

private async importAll(files: Dirent[]) {
private async importAll(files: string[]) {
log.debug(`Importing plugin features...`)
for (const file of files) {
const normalizedPath = relative(process.cwd(), file.path)
const content = await Bun.file(normalizedPath).text()
const content = await Bun.file(join(this.path, file)).text()
const replaced = content.replace(/^Cat(Hook|Tool|Form|Plugin)\.(add|on|settings).*/gm, (match) => {
if (match.startsWith('export')) return match
else if (match.startsWith('const') || match.startsWith('let')) return `export ${match}`
else if (/^(?:const|let|var)\b/.test(match)) return `export ${match}`
else return `export const ${getRandomString(8)} = ${match}`
})
const jsCode = transpiler.transformSync(replaced)
Expand Down Expand Up @@ -263,17 +269,17 @@ export class Plugin<
* This method performs the following actions:
* 1. Triggers the 'removed' event.
* 2. Revokes all object URLs stored.
* 3. If `requirements.txt` exists, attempts to remove the packages.
* 3. If any dependencies are found in the plugin's package.json file, they are uninstalled.
* 4. Deletes the plugin's directory and its contents.
*/
async remove() {
this.triggerEvent('removed')
this.#fileUrls.forEach(u => URL.revokeObjectURL(u))
const file = Bun.file(join(this.path, 'requirements.txt'))
const file = Bun.file(join(this.path, 'package.json'))
if (await file.exists()) {
const requirements = await file.text()
const pkgs = requirements.split('\n').map(req => req.trim().split('@')[0]).filter(p => p !== undefined)
try { Bun.spawnSync(['bun', 'remove', ...pkgs]) }
const requirements = await file.json() as PackageJson
const deps = Object.entries(requirements).filter(([key]) => key.toLowerCase().includes('dependencies')).map<string>(([_, value]) => value)
try { Bun.spawnSync(['bun', 'remove', ...deps]) }
catch (error) { log.error(`Error uninstalling requirements for ${this.id}: ${error}`) }
}
await rm(this.path, { recursive: true, force: true })
Expand Down
23 changes: 1 addition & 22 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { BaseMessageChunk } from '@langchain/core/messages'
import { readdir, stat } from 'node:fs/promises'
import { stat } from 'node:fs/promises'
import { join } from 'node:path'
import { safeDestr } from 'destr'
import { type CriteriaLike, loadEvaluator } from 'langchain/evaluation'
import _DefaultsDeep from 'lodash/defaultsDeep.js'
import _SampleSize from 'lodash/sampleSize.js'
import { z } from 'zod'

export const LogLevel = ['error', 'warning', 'normal', 'info', 'debug'] as const
Expand Down Expand Up @@ -116,17 +115,6 @@ export async function logWelcome() {
console.log('=================================================')
}

/**
* Retrieves all files recursively from the specified path.
* @param path The path to search for files.
* @returns An array of Dirent objects representing the files found.
*/
export async function getFilesRecursively(path: string) {
const dirents = await readdir(path, { withFileTypes: true, recursive: true, encoding: 'utf-8' })
for (const dirent of dirents) dirent.path = join(dirent.path, dirent.name)
return dirents
}

/**
* Compares two strings using an evaluator.
* @param input The input string to compare.
Expand Down Expand Up @@ -174,15 +162,6 @@ export async function existsDir(path: string) {
return stats.isDirectory()
}

/**
* Generates a random string of the specified length.
* @param length The length of the random string to generate.
*/
export function getRandomString(length: number) {
const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
return _SampleSize(letters, length).join('')
}

/**
* Parses a JSON string using the specified Zod schema.
* It also cleans a few common issues with generated JSON strings.
Expand Down

0 comments on commit 7613684

Please sign in to comment.