-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(plugin): use recursively dfs to stable sort result & add tes…
…t case
- Loading branch information
1 parent
a06f8e5
commit 993f8f0
Showing
3 changed files
with
61 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,86 @@ | ||
import path from 'path'; | ||
import compatibleRequire from '../utils/compatible_require'; | ||
import { PluginType } from './types'; | ||
import { LoggerType } from '../logger'; | ||
import path from "path"; | ||
import compatibleRequire from "../utils/compatible_require"; | ||
import { PluginType } from "./types"; | ||
import { LoggerType } from "../logger"; | ||
|
||
export function sortPlugins(pluginInstanceMap: Map<string, PluginType>, logger: LoggerType) { | ||
export function sortPlugins( | ||
pluginInstanceMap: Map<string, PluginType>, | ||
logger: LoggerType, | ||
): PluginType[] { | ||
const sortedPlugins: PluginType[] = []; | ||
const visited: Record<string, boolean> = {}; | ||
|
||
while (sortedPlugins.length < pluginInstanceMap.size) { | ||
let added = false; | ||
const visit = (pluginName: string, depChain: string[] = []) => { | ||
if (depChain.includes(pluginName)) { | ||
throw new Error( | ||
`Circular dependency found in plugins: ${depChain.join(", ")}`, | ||
); | ||
} | ||
|
||
for (const [pluginName, plugin] of pluginInstanceMap) { | ||
if (visited[pluginName]) { | ||
continue; | ||
} | ||
if (visited[pluginName]) return; | ||
|
||
visited[pluginName] = true; | ||
|
||
let depsSatisfied = true; | ||
const plugin = pluginInstanceMap.get(pluginName); | ||
if (plugin) { | ||
for (const dep of plugin.metadata.dependencies ?? []) { | ||
const depPlugin = pluginInstanceMap.get(dep.name); | ||
if (!depPlugin || !depPlugin.enable) { | ||
if (dep.optional) { | ||
logger?.warn(`Plugin ${plugin.name} need have optional dependency: ${dep.name}.`); | ||
logger?.warn( | ||
`Plugin ${plugin.name} need have optional dependency: ${dep.name}.`, | ||
); | ||
} else { | ||
throw new Error(`Plugin ${plugin.name} need have dependency: ${dep.name}.`); | ||
throw new Error( | ||
`Plugin ${plugin.name} need have dependency: ${dep.name}.`, | ||
); | ||
} | ||
} else if (!visited[dep.name]) { // Plugin exist and enabled, need check visited | ||
depsSatisfied = false; | ||
} else { | ||
// Plugin exist and enabled, need visit | ||
visit(dep.name, depChain.concat(pluginName)); | ||
} | ||
} | ||
|
||
if (depsSatisfied) { | ||
sortedPlugins.push(plugin); | ||
visited[plugin.name] = true; | ||
added = true; | ||
} | ||
sortedPlugins.push(plugin); | ||
} | ||
}; | ||
|
||
if (!added) { | ||
const cyclePluginNames: string[] = []; | ||
const sortedPluginSet = new Set(sortedPlugins.map(p => p.name)); | ||
for (const pluginName of pluginInstanceMap.keys()) { | ||
if (!sortedPluginSet.has(pluginName)) { | ||
cyclePluginNames.push(pluginName); | ||
} | ||
} | ||
throw new Error(`Circular dependency found in plugins: ${cyclePluginNames.join(', ')}`); | ||
} | ||
for (const pluginName of pluginInstanceMap.keys()) { | ||
visit(pluginName); | ||
} | ||
|
||
return sortedPlugins; | ||
} | ||
|
||
// A util function of get package path for plugin | ||
export function getPackagePath(packageName: string, paths: string[] = []): string { | ||
export function getPackagePath( | ||
packageName: string, | ||
paths: string[] = [], | ||
): string { | ||
const opts = { | ||
paths: paths.concat(__dirname), | ||
}; | ||
return path.dirname(require.resolve(packageName, opts)); | ||
} | ||
|
||
export async function getInlinePackageEntryPath(packagePath: string): Promise<string> { | ||
export async function getInlinePackageEntryPath( | ||
packagePath: string, | ||
): Promise<string> { | ||
const pkgJson = await compatibleRequire(`${packagePath}/package.json`); | ||
let entryFilePath = ''; | ||
let entryFilePath = ""; | ||
if (pkgJson.exports) { | ||
if (Array.isArray(pkgJson.exports)) { | ||
throw new Error(`inline package multi exports is not supported`); | ||
} else if (typeof pkgJson.exports === 'string') { | ||
} else if (typeof pkgJson.exports === "string") { | ||
entryFilePath = pkgJson.exports; | ||
} else if (pkgJson.exports?.['.']) { | ||
entryFilePath = pkgJson.exports['.']; | ||
} else if (pkgJson.exports?.["."]) { | ||
entryFilePath = pkgJson.exports["."]; | ||
} | ||
} | ||
if (!entryFilePath && pkgJson.main) { | ||
entryFilePath = pkgJson.main; | ||
} | ||
// will use package root path if no entry file found | ||
return entryFilePath ? path.resolve(packagePath, entryFilePath, '..') : packagePath; | ||
return entryFilePath | ||
? path.resolve(packagePath, entryFilePath, "..") | ||
: packagePath; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters