mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-21 21:45:42 -05:00
fix(cli): resolve subdir plugins during build installs
This commit is contained in:
parent
166b9c87d8
commit
f2c2a31292
@ -6,12 +6,11 @@ import { getPluginSubpathEntry, toFileUrl } from "./gitLoader"
|
|||||||
export async function loadComponentsFromPackage(
|
export async function loadComponentsFromPackage(
|
||||||
pluginName: string,
|
pluginName: string,
|
||||||
manifest: PluginManifest | null,
|
manifest: PluginManifest | null,
|
||||||
subdir?: string,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!manifest?.components) return
|
if (!manifest?.components) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const componentsPath = getPluginSubpathEntry(pluginName, "./components", subdir)
|
const componentsPath = getPluginSubpathEntry(pluginName, "./components")
|
||||||
|
|
||||||
let componentsModule: Record<string, unknown>
|
let componentsModule: Record<string, unknown>
|
||||||
if (componentsPath) {
|
if (componentsPath) {
|
||||||
|
|||||||
@ -189,7 +189,7 @@ function validateDependencies(
|
|||||||
async function resolvePluginManifest(source: PluginSource): Promise<PluginManifest | null> {
|
async function resolvePluginManifest(source: PluginSource): Promise<PluginManifest | null> {
|
||||||
try {
|
try {
|
||||||
const gitSpec = parsePluginSource(source)
|
const gitSpec = parsePluginSource(source)
|
||||||
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
const entryPoint = getPluginEntryPoint(gitSpec.name)
|
||||||
const module = await import(toFileUrl(entryPoint))
|
const module = await import(toFileUrl(entryPoint))
|
||||||
return module.manifest ?? null
|
return module.manifest ?? null
|
||||||
} catch {
|
} catch {
|
||||||
@ -343,7 +343,7 @@ export async function loadQuartzConfig(
|
|||||||
// Always import the main entry point for component-only plugins.
|
// Always import the main entry point for component-only plugins.
|
||||||
// Some plugins (e.g. Bases view registrations) rely on side effects
|
// Some plugins (e.g. Bases view registrations) rely on side effects
|
||||||
// in their index module to register functionality.
|
// in their index module to register functionality.
|
||||||
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
const entryPoint = getPluginEntryPoint(gitSpec.name)
|
||||||
try {
|
try {
|
||||||
const module = await import(toFileUrl(entryPoint))
|
const module = await import(toFileUrl(entryPoint))
|
||||||
// If the module exports an init() function, call it with merged options
|
// If the module exports an init() function, call it with merged options
|
||||||
@ -356,22 +356,22 @@ export async function loadQuartzConfig(
|
|||||||
// Side-effect import failed — continue with manifest-based loading
|
// Side-effect import failed — continue with manifest-based loading
|
||||||
}
|
}
|
||||||
if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
||||||
await loadComponentsFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
await loadComponentsFromPackage(gitSpec.name, manifest)
|
||||||
}
|
}
|
||||||
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
||||||
await loadFramesFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
await loadFramesFromPackage(gitSpec.name, manifest)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
const entryPoint = getPluginEntryPoint(gitSpec.name)
|
||||||
try {
|
try {
|
||||||
const module = await import(toFileUrl(entryPoint))
|
const module = await import(toFileUrl(entryPoint))
|
||||||
const detected = detectCategoryFromModule(module)
|
const detected = detectCategoryFromModule(module)
|
||||||
if (detected) {
|
if (detected) {
|
||||||
categoryMap[detected].push({ entry, manifest })
|
categoryMap[detected].push({ entry, manifest })
|
||||||
} else if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
} else if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
||||||
await loadComponentsFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
await loadComponentsFromPackage(gitSpec.name, manifest)
|
||||||
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
||||||
await loadFramesFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
await loadFramesFromPackage(gitSpec.name, manifest)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
@ -383,10 +383,10 @@ export async function loadQuartzConfig(
|
|||||||
const hasComponents = manifest?.components && Object.keys(manifest.components).length > 0
|
const hasComponents = manifest?.components && Object.keys(manifest.components).length > 0
|
||||||
const hasFrames = manifest?.frames && Object.keys(manifest.frames).length > 0
|
const hasFrames = manifest?.frames && Object.keys(manifest.frames).length > 0
|
||||||
if (hasComponents) {
|
if (hasComponents) {
|
||||||
await loadComponentsFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
await loadComponentsFromPackage(gitSpec.name, manifest)
|
||||||
}
|
}
|
||||||
if (hasFrames) {
|
if (hasFrames) {
|
||||||
await loadFramesFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
await loadFramesFromPackage(gitSpec.name, manifest)
|
||||||
}
|
}
|
||||||
if (!hasComponents && !hasFrames) {
|
if (!hasComponents && !hasFrames) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@ -423,13 +423,13 @@ export async function loadQuartzConfig(
|
|||||||
for (const { entry, manifest } of items) {
|
for (const { entry, manifest } of items) {
|
||||||
try {
|
try {
|
||||||
const gitSpec = parsePluginSource(entry.source)
|
const gitSpec = parsePluginSource(entry.source)
|
||||||
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
const entryPoint = getPluginEntryPoint(gitSpec.name)
|
||||||
const module = await import(toFileUrl(entryPoint))
|
const module = await import(toFileUrl(entryPoint))
|
||||||
if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
if (manifest?.components && Object.keys(manifest.components).length > 0) {
|
||||||
await loadComponentsFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
await loadComponentsFromPackage(gitSpec.name, manifest)
|
||||||
}
|
}
|
||||||
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
if (manifest?.frames && Object.keys(manifest.frames).length > 0) {
|
||||||
await loadFramesFromPackage(gitSpec.name, manifest, gitSpec.subdir)
|
await loadFramesFromPackage(gitSpec.name, manifest)
|
||||||
}
|
}
|
||||||
|
|
||||||
const factory = findFactory(module, expectedCategory)
|
const factory = findFactory(module, expectedCategory)
|
||||||
|
|||||||
@ -6,12 +6,11 @@ import { getPluginSubpathEntry, toFileUrl } from "./gitLoader"
|
|||||||
export async function loadFramesFromPackage(
|
export async function loadFramesFromPackage(
|
||||||
pluginName: string,
|
pluginName: string,
|
||||||
manifest: PluginManifest | null,
|
manifest: PluginManifest | null,
|
||||||
subdir?: string,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!manifest?.frames) return
|
if (!manifest?.frames) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const framesPath = getPluginSubpathEntry(pluginName, "./frames", subdir)
|
const framesPath = getPluginSubpathEntry(pluginName, "./frames")
|
||||||
|
|
||||||
let framesModule: Record<string, unknown>
|
let framesModule: Record<string, unknown>
|
||||||
if (framesPath) {
|
if (framesPath) {
|
||||||
|
|||||||
@ -76,8 +76,18 @@ export function parsePluginSource(source: PluginSource): GitPluginSpec {
|
|||||||
return { name, repo: resolved, local: true, subdir }
|
return { name, repo: resolved, local: true, subdir }
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = source.name ?? extractRepoName(url)
|
// Expand shorthand formats in the repo field (e.g. "github:user/repo")
|
||||||
return { name, repo: url, ref: ref || undefined, subdir }
|
// by recursing through the string-based parsing path, then overlay
|
||||||
|
// the object-level fields (subdir, ref, name) on top.
|
||||||
|
const expanded = parsePluginSource(url)
|
||||||
|
const name = source.name ?? expanded.name
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
repo: expanded.repo,
|
||||||
|
ref: ref || expanded.ref || undefined,
|
||||||
|
subdir,
|
||||||
|
local: expanded.local,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle local paths
|
// Handle local paths
|
||||||
@ -248,6 +258,129 @@ export function installNativeDeps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDistGitignored(pluginDir: string): boolean {
|
||||||
|
const gitignorePath = path.join(pluginDir, ".gitignore")
|
||||||
|
if (!fs.existsSync(gitignorePath)) return false
|
||||||
|
|
||||||
|
const lines = fs.readFileSync(gitignorePath, "utf-8").split("\n")
|
||||||
|
return lines.some((line) => {
|
||||||
|
const trimmed = line.trim()
|
||||||
|
return trimmed === "dist" || trimmed === "dist/" || trimmed === "/dist" || trimmed === "/dist/"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function needsBuild(pluginDir: string): boolean {
|
||||||
|
if (isDistGitignored(pluginDir)) return true
|
||||||
|
const distDir = path.join(pluginDir, "dist")
|
||||||
|
return !fs.existsSync(distDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPluginByPackageName(packageName: string): string | null {
|
||||||
|
if (!fs.existsSync(PLUGINS_CACHE_DIR)) return null
|
||||||
|
|
||||||
|
const plugins = fs.readdirSync(PLUGINS_CACHE_DIR).filter((entry) => {
|
||||||
|
const entryPath = path.join(PLUGINS_CACHE_DIR, entry)
|
||||||
|
return fs.statSync(entryPath).isDirectory()
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const pluginDirName of plugins) {
|
||||||
|
const pkgPath = path.join(PLUGINS_CACHE_DIR, pluginDirName, "package.json")
|
||||||
|
if (!fs.existsSync(pkgPath)) continue
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
|
||||||
|
if (pkg.name === packageName) {
|
||||||
|
return path.join(PLUGINS_CACHE_DIR, pluginDirName)
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symlink peer dependencies to the host Quartz node_modules so plugins
|
||||||
|
* share a single copy of packages like unified, vfile, preact, etc.
|
||||||
|
* @quartz-community/* peers resolve to co-installed sibling plugins instead.
|
||||||
|
*/
|
||||||
|
function linkPeerDependencies(pluginDir: string): void {
|
||||||
|
const pkgPath = path.join(pluginDir, "package.json")
|
||||||
|
if (!fs.existsSync(pkgPath)) return
|
||||||
|
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
|
||||||
|
const peers: Record<string, string> = pkg.peerDependencies ?? {}
|
||||||
|
|
||||||
|
const quartzRoot = path.resolve(pluginDir, "..", "..", "..")
|
||||||
|
const hostNodeModules = path.join(quartzRoot, "node_modules")
|
||||||
|
|
||||||
|
for (const peerName of Object.keys(peers)) {
|
||||||
|
const peerNodeModulesPath = path.join(pluginDir, "node_modules", ...peerName.split("/"))
|
||||||
|
if (fs.existsSync(peerNodeModulesPath)) continue
|
||||||
|
|
||||||
|
if (peerName.startsWith("@quartz-community/")) {
|
||||||
|
const siblingPlugin = findPluginByPackageName(peerName)
|
||||||
|
if (!siblingPlugin) continue
|
||||||
|
|
||||||
|
const scopeDir = path.join(pluginDir, "node_modules", peerName.split("/")[0])
|
||||||
|
fs.mkdirSync(scopeDir, { recursive: true })
|
||||||
|
|
||||||
|
const target = path.relative(scopeDir, siblingPlugin)
|
||||||
|
fs.symlinkSync(target, peerNodeModulesPath, "dir")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostPeerPath = path.join(hostNodeModules, ...peerName.split("/"))
|
||||||
|
if (!fs.existsSync(hostPeerPath)) continue
|
||||||
|
|
||||||
|
const parts = peerName.split("/")
|
||||||
|
if (parts.length > 1) {
|
||||||
|
const scopeDir = path.join(pluginDir, "node_modules", parts[0])
|
||||||
|
fs.mkdirSync(scopeDir, { recursive: true })
|
||||||
|
} else {
|
||||||
|
fs.mkdirSync(path.join(pluginDir, "node_modules"), { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = path.relative(path.dirname(peerNodeModulesPath), hostPeerPath)
|
||||||
|
fs.symlinkSync(target, peerNodeModulesPath, "dir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildInstalledPlugin(pluginDir: string, name: string, verbose?: boolean): void {
|
||||||
|
try {
|
||||||
|
const shouldBuild = needsBuild(pluginDir)
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
console.log(styleText("cyan", `→`), `${name}: installing dependencies...`)
|
||||||
|
}
|
||||||
|
execSync("npm install --ignore-scripts", {
|
||||||
|
cwd: pluginDir,
|
||||||
|
stdio: verbose ? "inherit" : "pipe",
|
||||||
|
timeout: 120_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (shouldBuild) {
|
||||||
|
if (verbose) {
|
||||||
|
console.log(styleText("cyan", `→`), `${name}: building...`)
|
||||||
|
}
|
||||||
|
execSync("npm run build", {
|
||||||
|
cwd: pluginDir,
|
||||||
|
stdio: verbose ? "inherit" : "pipe",
|
||||||
|
timeout: 120_000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
execSync("npm prune --omit=dev", {
|
||||||
|
cwd: pluginDir,
|
||||||
|
stdio: verbose ? "inherit" : "pipe",
|
||||||
|
timeout: 60_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
linkPeerDependencies(pluginDir)
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error)
|
||||||
|
console.error(styleText("red", `✗`), `${name}: post-install build failed: ${message}`)
|
||||||
|
throw new Error(`Failed to build plugin ${name}: ${message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface PluginInstallResult {
|
interface PluginInstallResult {
|
||||||
pluginDir: string
|
pluginDir: string
|
||||||
nativeDeps: Map<string, string>
|
nativeDeps: Map<string, string>
|
||||||
@ -316,39 +449,66 @@ export async function installPlugin(
|
|||||||
// Git source: clone
|
// Git source: clone
|
||||||
// Check if already installed
|
// Check if already installed
|
||||||
if (!options.force && fs.existsSync(pluginDir)) {
|
if (!options.force && fs.existsSync(pluginDir)) {
|
||||||
// Check if it's a git repo by trying to resolve HEAD
|
// For subdir installs, the .git directory is removed after extraction,
|
||||||
try {
|
// so check for package.json instead. For full-repo installs, check git HEAD.
|
||||||
await git.resolveRef({ fs, dir: pluginDir, ref: "HEAD" })
|
if (spec.subdir) {
|
||||||
if (options.verbose) {
|
const pkgPath = path.join(pluginDir, "package.json")
|
||||||
console.log(styleText("cyan", `→`), `Plugin ${spec.name} already installed`)
|
if (fs.existsSync(pkgPath)) {
|
||||||
|
if (options.verbose) {
|
||||||
|
console.log(styleText("cyan", `→`), `Plugin ${spec.name} already installed`)
|
||||||
|
}
|
||||||
|
return { pluginDir, nativeDeps: collectNativeDeps(pluginDir) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await git.resolveRef({ fs, dir: pluginDir, ref: "HEAD" })
|
||||||
|
if (options.verbose) {
|
||||||
|
console.log(styleText("cyan", `→`), `Plugin ${spec.name} already installed`)
|
||||||
|
}
|
||||||
|
return { pluginDir, nativeDeps: collectNativeDeps(pluginDir) }
|
||||||
|
} catch {
|
||||||
|
// If git operations fail, re-clone
|
||||||
}
|
}
|
||||||
return { pluginDir, nativeDeps: collectNativeDeps(pluginDir) }
|
|
||||||
} catch {
|
|
||||||
// If git operations fail, re-clone
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up if force reinstall
|
// Clean up if force reinstall or stale install
|
||||||
if (options.force && fs.existsSync(pluginDir)) {
|
if (fs.existsSync(pluginDir)) {
|
||||||
fs.rmSync(pluginDir, { recursive: true })
|
fs.rmSync(pluginDir, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
const refSuffix = spec.ref ? `#${spec.ref}` : ""
|
const refSuffix = spec.ref ? `#${spec.ref}` : ""
|
||||||
console.log(styleText("cyan", `→`), `Cloning ${spec.name} from ${spec.repo}${refSuffix}...`)
|
const subdirSuffix = spec.subdir ? ` (subdir: ${spec.subdir})` : ""
|
||||||
|
console.log(
|
||||||
|
styleText("cyan", `→`),
|
||||||
|
`Cloning ${spec.name} from ${spec.repo}${refSuffix}${subdirSuffix}...`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone the repository
|
if (spec.subdir) {
|
||||||
await git.clone({
|
const tmpDir = pluginDir + ".__tmp__"
|
||||||
fs,
|
if (fs.existsSync(tmpDir)) {
|
||||||
http,
|
fs.rmSync(tmpDir, { recursive: true })
|
||||||
dir: pluginDir,
|
}
|
||||||
url: spec.repo,
|
|
||||||
ref: spec.ref,
|
const branchArg = spec.ref ? ` --branch ${spec.ref}` : ""
|
||||||
singleBranch: true,
|
execSync(`git clone --depth 1${branchArg} "${spec.repo}" "${tmpDir}"`, { stdio: "pipe" })
|
||||||
depth: 1,
|
|
||||||
noCheckout: false,
|
const subdirPath = path.join(tmpDir, spec.subdir)
|
||||||
})
|
if (!fs.existsSync(subdirPath)) {
|
||||||
|
fs.rmSync(tmpDir, { recursive: true })
|
||||||
|
throw new Error(`Subdirectory "${spec.subdir}" not found in repository ${spec.repo}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.renameSync(subdirPath, pluginDir)
|
||||||
|
fs.rmSync(tmpDir, { recursive: true })
|
||||||
|
} else {
|
||||||
|
const branchArg = spec.ref ? ` --branch ${spec.ref}` : ""
|
||||||
|
execSync(`git clone --depth 1${branchArg} "${spec.repo}" "${pluginDir}"`, { stdio: "pipe" })
|
||||||
|
}
|
||||||
|
|
||||||
|
buildInstalledPlugin(pluginDir, spec.name, options.verbose)
|
||||||
|
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
console.log(styleText("green", `✓`), `Installed ${spec.name}`)
|
console.log(styleText("green", `✓`), `Installed ${spec.name}`)
|
||||||
@ -408,9 +568,9 @@ export function isPluginInstalled(name: string): boolean {
|
|||||||
* Get the entry point for a plugin.
|
* Get the entry point for a plugin.
|
||||||
* Prefers compiled dist/ output over raw src/ to avoid ESM resolution issues.
|
* Prefers compiled dist/ output over raw src/ to avoid ESM resolution issues.
|
||||||
*/
|
*/
|
||||||
export function getPluginEntryPoint(name: string, subdir?: string): string {
|
export function getPluginEntryPoint(name: string): string {
|
||||||
const pluginDir = getPluginDir(name)
|
const pluginDir = getPluginDir(name)
|
||||||
const searchDir = subdir ? path.join(pluginDir, subdir) : pluginDir
|
const searchDir = pluginDir
|
||||||
// Check package.json exports first (most reliable)
|
// Check package.json exports first (most reliable)
|
||||||
const pkgJsonPath = path.join(searchDir, "package.json")
|
const pkgJsonPath = path.join(searchDir, "package.json")
|
||||||
if (fs.existsSync(pkgJsonPath)) {
|
if (fs.existsSync(pkgJsonPath)) {
|
||||||
@ -459,13 +619,9 @@ export function getPluginEntryPoint(name: string, subdir?: string): string {
|
|||||||
* Resolve a subpath export for a plugin (e.g. "./components").
|
* Resolve a subpath export for a plugin (e.g. "./components").
|
||||||
* Uses package.json exports map, then falls back to dist/ directory structure.
|
* Uses package.json exports map, then falls back to dist/ directory structure.
|
||||||
*/
|
*/
|
||||||
export function getPluginSubpathEntry(
|
export function getPluginSubpathEntry(name: string, subpath: string): string | null {
|
||||||
name: string,
|
|
||||||
subpath: string,
|
|
||||||
subdir?: string,
|
|
||||||
): string | null {
|
|
||||||
const pluginDir = getPluginDir(name)
|
const pluginDir = getPluginDir(name)
|
||||||
const searchDir = subdir ? path.join(pluginDir, subdir) : pluginDir
|
const searchDir = pluginDir
|
||||||
|
|
||||||
// Check package.json exports map
|
// Check package.json exports map
|
||||||
const pkgJsonPath = path.join(searchDir, "package.json")
|
const pkgJsonPath = path.join(searchDir, "package.json")
|
||||||
|
|||||||
@ -186,7 +186,7 @@ async function resolveSinglePlugin(
|
|||||||
try {
|
try {
|
||||||
const gitSpec = parsePluginSource(packageName)
|
const gitSpec = parsePluginSource(packageName)
|
||||||
await installPlugin(gitSpec, { verbose: options.verbose })
|
await installPlugin(gitSpec, { verbose: options.verbose })
|
||||||
const entryPoint = getPluginEntryPoint(gitSpec.name, gitSpec.subdir)
|
const entryPoint = getPluginEntryPoint(gitSpec.name)
|
||||||
|
|
||||||
// Import the plugin
|
// Import the plugin
|
||||||
const module = await import(toFileUrl(entryPoint))
|
const module = await import(toFileUrl(entryPoint))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user