Vendor Cordis framework packages as source
cordis 4.0.0-rc.6, plugin-loader, -include, -group, -timer, -hmr, -logger-console, cosmokit 1.8.1, schemastery 3.18.0 — copied from the cordis-workspace checkout, flattened under vendor/, original npm names, private: true. vendor/README.md is the manifest: upstream repos + commit SHAs, local-modification log, sync procedure. Local modification: hmr's locale YAML imports and .i18n() call removed (avoids a runtime YAML import hook we don't vendor).
This commit is contained in:
184
vendor/loader/src/config/entry.ts
vendored
Normal file
184
vendor/loader/src/config/entry.ts
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user