Files
deepseek-harness/vendor/loader/src/config/entry.ts
T
Tianyi Cui 07f4047ff0 Use explicit ts specifiers for declarations
Restore explicit .ts relative specifiers in source and enable rewriteRelativeImportExtensions so emitted JS uses .js while declarations keep explicit .ts specifiers. Add a NodeNext declaration-consumer gate to prevent extensionless declaration regressions.
2026-06-22 06:11:00 +08:00

185 lines
5.2 KiB
TypeScript

import { Context, Fiber, Inject } from 'cordis'
import { deepEqual, isNullable } from 'cosmokit'
import { Loader } from '../index.ts'
import { EntryGroup } from './group.ts'
import { EntryTree } from './tree.ts'
import { evaluate, interpolate } from './utils.ts'
/** Serialized plugin entry options stored in loader config files. */
export interface EntryOptions {
/** Stable id inside the containing entry tree. */
id: string
/** Module specifier imported by the entry tree. */
name: string
/** Config passed to the plugin. */
config?: any
/** Marks this entry as a nested group. */
group?: boolean | null
/** Prevents this entry and descendants from running. */
disabled?: boolean | null
/** Required services or service intercept config for this entry. */
inject?: Inject | null
}
function takeEntries(object: {}, keys: string[]) {
const result: [string, any][] = []
for (const key of keys) {
if (!(key in object)) continue
result.push([key, object[key]])
delete object[key]
}
return result
}
function sortKeys<T extends {}>(object: T, prepend = ['id', 'name'], append = ['config']): T {
const part1 = takeEntries(object, prepend)
const part2 = takeEntries(object, append)
const rest = takeEntries(object, Object.keys(object)).sort(([a], [b]) => a.localeCompare(b))
return Object.assign(object, Object.fromEntries([...part1, ...rest, ...part2]))
}
/** One configured plugin node inside an `EntryTree`. */
export class Entry {
static readonly key = Symbol.for('cordis.entry')
public ctx: Context
public fiber?: Fiber
public parent!: EntryGroup
// safety: call `entry.update()` immediately after creating an entry
public options = {} as EntryOptions
public subgroup?: EntryGroup
public subtree?: EntryTree
_initTask?: Promise<void>
constructor(public loader: Loader) {
this.ctx = loader.ctx.extend({ [Entry.key]: this })
this.context.emit('loader/entry-init', this)
}
get context(): Context {
return this.ctx
}
get id() {
let id = this.options.id
if (this.parent.tree.ctx.fiber.entry) {
id = this.parent.tree.ctx.fiber.entry.id + EntryTree.sep + id
}
return id
}
/** True when this entry or any owning parent entry is disabled. */
get disabled() {
// group is always enabled
if (this.options.group) return false
let entry: Entry | undefined = this
do {
if (entry.options.disabled) return true
entry = entry.parent.ctx.fiber.entry
} while (entry)
return false
}
evaluate(expr: string) {
return evaluate(this.ctx, expr)
}
_resolveConfig(plugin: any): [any, any?] {
if (plugin[EntryGroup.key]) return this.options.config
return interpolate(this.ctx, this.options.config)
}
private _patchContext(diff: string[]) {
this.context.waterfall('loader/patch-context', this, () => {
Object.setPrototypeOf(this.ctx, this.parent.ctx)
if (this.fiber?.uid && (diff.includes('config') || this.options.group)) {
this.fiber.update(this._resolveConfig(this.fiber.runtime!.callback), true)
}
})
}
async refresh() {
if (this.fiber) return
if (this.disabled) return
await this.init()
}
/** Merge new options, restart as needed, and persist through the parent tree. */
async update(options: Partial<EntryOptions>, create = false, force = false) {
const legacy = { ...this.options }
// step 1: update options
if (create) {
this.options = options as EntryOptions
} else {
for (const [key, value] of Object.entries(options)) {
if (isNullable(value)) {
delete this.options[key]
} else {
this.options[key] = value
}
}
}
sortKeys(this.options)
// step 2: execute
if (this.disabled) {
this.fiber?.dispose()
return
}
// step 3: check if options are changed
if (this.fiber?.uid) {
const diff = Object
.keys({ ...this.options, ...legacy })
.filter(key => !deepEqual(this.options[key], legacy[key]))
if (!diff.length && !force) return
this.context.emit('loader/partial-dispose', this, legacy, true)
this._patchContext(diff)
} else {
await this.init()
}
}
getOuterStack = () => {
let entry: Entry | undefined = this
const result: string[] = []
do {
result.push(` at ${entry.parent.tree.ctx.baseUrl}#${entry.options.id}`)
entry = entry.parent.ctx.fiber.entry
} while (entry)
return result
}
/** Import and start the configured plugin if it is not already running. */
async init() {
try {
await (this._initTask ??= this._init())
} finally {
this._initTask = undefined
}
this.fiber?.await().finally(() => {
if (this.loader.getTasks().length) return
this.ctx.reflect.notify(['loader'])
})
}
private async _init() {
let exports: any
try {
exports = await this.parent.tree.import(this.options.name, this.getOuterStack)
} catch (error) {
this.ctx.logger.error(error)
return
} finally {
this._initTask = undefined
}
const plugin = this.loader.unwrapExports(exports)
this._patchContext([])
this.loader.showLog(this, 'apply')
this.fiber = this.ctx.registry.plugin(plugin, this._resolveConfig(plugin), this.getOuterStack)
}
}