diff --git a/.pnp.cjs b/.pnp.cjs index 294447c..59e97a2 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -8643,6 +8643,7 @@ const RAW_RUNTIME_STATE = ["@allurereport/core", "workspace:packages/core"],\ ["@allurereport/core-api", "workspace:packages/core-api"],\ ["@allurereport/directory-watcher", "workspace:packages/directory-watcher"],\ + ["@allurereport/plugin-api", "workspace:packages/plugin-api"],\ ["@allurereport/plugin-awesome", "workspace:packages/plugin-awesome"],\ ["@allurereport/plugin-progress", "workspace:packages/plugin-progress"],\ ["@allurereport/plugin-server-reload", "workspace:packages/plugin-server-reload"],\ diff --git a/packages/cli/package.json b/packages/cli/package.json index 7d2074d..cfeeb32 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,6 +33,7 @@ "@allurereport/core": "workspace:*", "@allurereport/core-api": "workspace:*", "@allurereport/directory-watcher": "workspace:*", + "@allurereport/plugin-api": "workspace:*", "@allurereport/plugin-awesome": "workspace:*", "@allurereport/plugin-progress": "workspace:*", "@allurereport/plugin-server-reload": "workspace:*", diff --git a/packages/cli/src/commands/generate.ts b/packages/cli/src/commands/generate.ts index c3439cf..3b12879 100644 --- a/packages/cli/src/commands/generate.ts +++ b/packages/cli/src/commands/generate.ts @@ -1,4 +1,4 @@ -import { AllureReport, readRuntimeConfig } from "@allurereport/core"; +import { AllureReport, readConfig } from "@allurereport/core"; import { createCommand } from "../utils/commands.js"; type CommandOptions = { @@ -10,7 +10,7 @@ type CommandOptions = { export const GenerateCommandAction = async (resultsDir: string, options: CommandOptions) => { const { config: configPath, output, cwd, reportName } = options; - const config = await readRuntimeConfig(configPath, cwd, output, reportName); + const config = await readConfig(cwd, configPath, { name: reportName, output }); const allureReport = new AllureReport(config); await allureReport.start(); diff --git a/packages/cli/src/commands/knownIssue.ts b/packages/cli/src/commands/knownIssue.ts index 9ddb991..a4b29f7 100644 --- a/packages/cli/src/commands/knownIssue.ts +++ b/packages/cli/src/commands/knownIssue.ts @@ -1,4 +1,4 @@ -import { AllureReport, createConfig, writeKnownIssues } from "@allurereport/core"; +import { AllureReport, resolveConfig, writeKnownIssues } from "@allurereport/core"; import console from "node:console"; import { resolve } from "node:path"; import { createCommand } from "../utils/commands.js"; @@ -9,11 +9,17 @@ type CommandOptions = { export const KnownIssueCommandAction = async (resultsDir: string, options: CommandOptions) => { const { output = "known-issues.json" } = options; - const config = await createConfig({}); + const config = await resolveConfig({ + plugins: {}, + }); + const allureReport = new AllureReport(config); - const targetPath = resolve(output); + await allureReport.start(); await allureReport.readDirectory(resultsDir); + await allureReport.done(); + + const targetPath = resolve(output); await writeKnownIssues(allureReport.store, output); console.log(`writing known-issues.json to ${targetPath}`); diff --git a/packages/cli/src/commands/open.ts b/packages/cli/src/commands/open.ts index 8f886b6..97e5534 100644 --- a/packages/cli/src/commands/open.ts +++ b/packages/cli/src/commands/open.ts @@ -1,16 +1,17 @@ -import { readRuntimeConfig } from "@allurereport/core"; +import { readConfig } from "@allurereport/core"; import { serve } from "@allurereport/static-server"; import { createCommand } from "../utils/commands.js"; type CommandOptions = { config?: string; + cwd?: string; port?: number; live: boolean; }; export const OpenCommandAction = async (reportDir: string | undefined, options: CommandOptions) => { - const { config: configPath, port, live } = options; - const config = await readRuntimeConfig(configPath, undefined, reportDir); + const { config: configPath, port, live, cwd } = options; + const config = await readConfig(cwd, configPath, { output: reportDir }); await serve({ port: port, @@ -43,6 +44,12 @@ export const OpenCommand = createCommand({ default: false, }, ], + [ + "--cwd ", + { + description: "The working directory for the command to run (default: current working directory)", + }, + ], ], action: OpenCommandAction, }); diff --git a/packages/cli/src/commands/qualityGate.ts b/packages/cli/src/commands/qualityGate.ts index 6c9f720..624029a 100644 --- a/packages/cli/src/commands/qualityGate.ts +++ b/packages/cli/src/commands/qualityGate.ts @@ -1,16 +1,17 @@ -import { AllureReport, readRuntimeConfig } from "@allurereport/core"; +import { AllureReport, readConfig } from "@allurereport/core"; import console from "node:console"; import process from "node:process"; import { bold, red } from "yoctocolors"; import { createCommand } from "../utils/commands.js"; type QualityGateCommandOptions = { + config?: string; cwd?: string; }; export const QualityGateCommandAction = async (resultsDir: string, options: QualityGateCommandOptions) => { - const cwd = options.cwd ?? process.cwd(); - const fullConfig = await readRuntimeConfig(cwd, "./allure-report"); + const { cwd, config: configPath } = options; + const fullConfig = await readConfig(cwd, configPath); const allureReport = new AllureReport(fullConfig); await allureReport.start(); @@ -51,10 +52,16 @@ export const QualityGateCommand = createCommand({ name: "quality-gate ", description: "Returns status code 1 if there any test failure above specified success rate", options: [ + [ + "--config, -c ", + { + description: "The path Allure config file", + }, + ], [ "--cwd ", { - description: "The working directory for the command to run (default: current working directory)", + description: "The working directory for the command to run (Default: current working directory)", }, ], ], diff --git a/packages/cli/src/commands/run.ts b/packages/cli/src/commands/run.ts index c0289e2..6cdb4b0 100644 --- a/packages/cli/src/commands/run.ts +++ b/packages/cli/src/commands/run.ts @@ -1,7 +1,6 @@ -import { AllureReport, isFileNotFoundError, readRuntimeConfig } from "@allurereport/core"; +import { AllureReport, isFileNotFoundError, readConfig } from "@allurereport/core"; import { createTestPlan } from "@allurereport/core-api"; -import type { - Watcher} from "@allurereport/directory-watcher"; +import type { Watcher } from "@allurereport/directory-watcher"; import { allureResultsDirectoriesWatcher, delayedFileProcessingWatcher, @@ -127,7 +126,7 @@ export const RunCommandAction = async (options: RunCommandOptions) => { console.log(`${command} ${commandArgs.join(" ")}`); const { config: configPath, output, reportName, rerun: maxRerun = 0, silent = false } = options; - const config = await readRuntimeConfig(configPath, cwd, output, reportName); + const config = await readConfig(cwd, configPath, { output, name: reportName }); try { await rm(config.output, { recursive: true }); diff --git a/packages/cli/src/commands/watch.ts b/packages/cli/src/commands/watch.ts index a4f1dfe..2d084ee 100644 --- a/packages/cli/src/commands/watch.ts +++ b/packages/cli/src/commands/watch.ts @@ -1,4 +1,4 @@ -import { AllureReport, isFileNotFoundError, readRuntimeConfig } from "@allurereport/core"; +import { AllureReport, isFileNotFoundError, readConfig } from "@allurereport/core"; import { newFilesInDirectoryWatcher } from "@allurereport/directory-watcher"; import AllureAwesome from "@allurereport/plugin-awesome"; import ProgressPlugin from "@allurereport/plugin-progress"; @@ -29,7 +29,7 @@ export const WatchCommandAction = async (resultsDir: string, options: WatchComma }); const { config: configPath, output, cwd, reportName } = options; - const config = await readRuntimeConfig(configPath, cwd, output, reportName); + const config = await readConfig(cwd, configPath, { output, name: reportName }); try { await rm(config.output, { recursive: true }); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 47d81cc..e18f058 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,4 +1,3 @@ -import { defineConfig } from "@allurereport/core"; import { cac } from "cac"; import console from "node:console"; import { readFileSync } from "node:fs"; @@ -53,4 +52,4 @@ console.log(cwd()); cli.parse(); -export { defineConfig }; +export { defineConfig } from "@allurereport/plugin-api"; diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index 188036b..2b80713 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -8,9 +8,10 @@ export interface FullConfig { reportFiles: ReportFiles; readers?: ResultsReader[]; plugins?: PluginInstance[]; - history?: HistoryDataPoint[]; - historyPath?: string; + history: HistoryDataPoint[]; + historyPath: string; appendHistory?: boolean; + knownIssuesPath: string; known?: KnownTestFailure[]; qualityGate?: QualityGateConfig; // TODO: https://github.com/qameta/allure3/issues/180 diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 2523ffe..2fe5f8d 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1,7 +1,7 @@ import type { Config, PluginDescriptor } from "@allurereport/plugin-api"; -import type { QualityGateConfig } from "@allurereport/plugin-api"; -import { readdir } from "node:fs/promises"; -import { join, resolve } from "node:path"; +import * as console from "node:console"; +import { stat } from "node:fs/promises"; +import { resolve } from "node:path"; import * as process from "node:process"; import type { FullConfig, PluginInstance } from "./api.js"; import { readHistory } from "./history.js"; @@ -9,96 +9,82 @@ import { readKnownIssues } from "./known.js"; import { FileSystemReportFiles } from "./plugin.js"; import { importWrapper } from "./utils/module.js"; -export { defineConfig } from "@allurereport/plugin-api"; - -export const createConfig = async (opts: { - reportName?: string; - output?: string; - historyPath?: string; - knownIssuesPath?: string; - plugins?: PluginInstance[]; - qualityGate?: QualityGateConfig; - cwd?: string; -}): Promise => { - const { - reportName = "Allure Report", - output = "allure-report", - historyPath, - knownIssuesPath, - qualityGate, - cwd, - } = opts; - const workingDirectory = cwd ?? process.cwd(); - const target = resolve(workingDirectory, output); - const history = historyPath ? await readHistory(resolve(workingDirectory, historyPath)) : []; - const known = knownIssuesPath ? await readKnownIssues(resolve(workingDirectory, knownIssuesPath)) : []; - - return { - name: reportName, - history, - historyPath, - known, - qualityGate, - plugins: opts.plugins ?? [], - reportFiles: new FileSystemReportFiles(target), - output: target, - }; -}; - -const defaultRuntimeConfig: Config = {}; - export const getPluginId = (key: string) => { return key.replace(/^@.*\//, "").replace(/[/\\]/g, "-"); }; -export const findRuntimeConfigPath = async (cwd: string = process.cwd()): Promise => { - const files = await readdir(cwd); +const configNames = ["allurerc.js", "allurerc.mjs"]; +const defaultConfig: Config = {}; - if (files.includes("allurerc.js")) { - return join(cwd, "allurerc.js"); +export const findConfig = async (cwd: string, configPath?: string) => { + if (configPath) { + const resolved = resolve(cwd, configPath); + try { + const stats = await stat(resolved); + if (stats.isFile()) { + return resolved; + } + } catch (e) { + console.error(e); + } + throw new Error(`invalid config path ${resolved}: not a regular file`); } - if (files.includes("allurerc.mjs")) { - return join(cwd, "allurerc.mjs"); + for (const configName of configNames) { + const resolved = resolve(cwd, configName); + try { + const stats = await stat(resolved); + if (stats.isFile()) { + return resolved; + } + } catch (ignored) { + // ignore + } } - - return undefined; }; -// more advanced override, e.g. default plugins -export const readRuntimeConfig = async ( +export interface ConfigOverride { + name?: string; + output?: string; + historyPath?: string; + knownIssuesPath?: string; +} + +export const readConfig = async ( + cwd: string = process.cwd(), configPath?: string, - cwd?: string, - output?: string, - name?: string, + override?: ConfigOverride, ): Promise => { - const runtimeConfigPath = !configPath ? await findRuntimeConfigPath(cwd) : resolve(cwd ?? process.cwd(), configPath); - const runtimeConfig = runtimeConfigPath ? await loadConfig(runtimeConfigPath) : defaultRuntimeConfig; - - return await resolveConfig(runtimeConfig, { output, name }); + const cfg = await findConfig(cwd, configPath); + const config = cfg ? await loadConfig(cfg) : { ...defaultConfig }; + return await resolveConfig(config, override); }; export const loadConfig = async (configPath: string): Promise => { return (await import(configPath)).default; }; -export const resolveConfig = async ( - config: Config, - override: { name?: string; output?: string } = {}, -): Promise => { - const { plugins = {}, name, output, historyPath, ...rest } = config; - const pluginInstances = await resolvePlugins(plugins); - const out = resolve(override.output ?? output ?? "./allure-report"); - const history = historyPath ? await readHistory(historyPath) : []; +export const resolveConfig = async (config: Config, override: ConfigOverride = {}): Promise => { + const name = override.name ?? config.name ?? "Allure Report"; + const historyPath = resolve(override.historyPath ?? config.historyPath ?? "./.allure/history.jsonl"); + const knownIssuesPath = resolve(override.knownIssuesPath ?? config.knownIssuesPath ?? "./allure/known.json"); + const output = resolve(override.output ?? config.output ?? "./allure-report"); + + const history = await readHistory(historyPath); + const known = await readKnownIssues(knownIssuesPath); + + const pluginInstances = await resolvePlugins(config.plugins ?? {}); return { - ...rest, - name: override.name ?? name ?? "Allure Report", - reportFiles: new FileSystemReportFiles(out), + name, + reportFiles: new FileSystemReportFiles(output), plugins: pluginInstances, - output: out, + output, history, historyPath, + knownIssuesPath, + known, + qualityGate: config.qualityGate, }; }; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 726ac16..2ed5380 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -3,7 +3,7 @@ export * from "./utils/misc.js"; export * from "./utils/crypto.js"; export * from "./history.js"; export * from "./known.js"; -export * from "./config.js"; +export { resolveConfig, readConfig } from "./config.js"; export * from "./report.js"; export * from "./plugin.js"; export * from "./qualityGate.js"; diff --git a/packages/core/src/report.ts b/packages/core/src/report.ts index f8ae4ab..0e133a5 100644 --- a/packages/core/src/report.ts +++ b/packages/core/src/report.ts @@ -53,7 +53,7 @@ export class AllureReport { this.#events = new Events(this.#eventEmitter); this.#realTime = realTime; this.#appendHistory = appendHistory ?? true; - this.#historyPath = historyPath ?? "./.allure/history.jsonl"; + this.#historyPath = historyPath; this.#store = new DefaultAllureStore(history, known, this.#eventEmitter); this.#readers = [...readers]; this.#plugins = [...plugins]; diff --git a/packages/core/test/config.test.ts b/packages/core/test/config.test.ts index 51f73b3..53173e7 100644 --- a/packages/core/test/config.test.ts +++ b/packages/core/test/config.test.ts @@ -1,11 +1,73 @@ -import { MockInstance, beforeEach, describe, expect, it, vi } from "vitest"; -import { defineConfig, getPluginId, resolvePlugin } from "../src/config.js"; +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import type { MockInstance } from "vitest"; +import { afterEach } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { findConfig, getPluginId, resolvePlugin } from "../src/config.js"; import { importWrapper } from "../src/utils/module.js"; vi.mock("../src/utils/module.js", () => ({ importWrapper: vi.fn(), })); +describe("findConfig", () => { + let fixturesDir: string; + + beforeEach(async () => { + fixturesDir = await mkdtemp("config.test.ts-findConfig-"); + }); + + afterEach(async () => { + try { + await rm(fixturesDir, { recursive: true }); + } catch (err) {} + }); + + it("should find allurerc.js in cwd", async () => { + await writeFile(join(fixturesDir, "allurerc.js"), "some content", "utf-8"); + + const found = await findConfig(fixturesDir); + expect(found).toEqual(resolve(fixturesDir, "allurerc.js")); + }); + + it("should find allurerc.mjs in cwd", async () => { + await writeFile(join(fixturesDir, "allurerc.mjs"), "some content", "utf-8"); + + const found = await findConfig(fixturesDir); + expect(found).toEqual(resolve(fixturesDir, "allurerc.mjs")); + }); + + it("should find allurerc.js first", async () => { + await writeFile(join(fixturesDir, "allurerc.js"), "some content", "utf-8"); + await writeFile(join(fixturesDir, "allurerc.mjs"), "some other content", "utf-8"); + + const found = await findConfig(fixturesDir); + expect(found).toEqual(resolve(fixturesDir, "allurerc.js")); + }); + + it("should find provided config path first", async () => { + const fileName = "config.js"; + await writeFile(join(fixturesDir, fileName), "some content", "utf-8"); + + const found = await findConfig(fixturesDir, fileName); + expect(found).toEqual(resolve(fixturesDir, fileName)); + }); + + it("should fail if provided config file is not found", async () => { + const fileName = "config.js"; + + await expect(findConfig(fixturesDir, fileName)).rejects.toThrow("invalid config path"); + }); + + it("should accept absolute path to config", async () => { + const fileName = "config.js"; + await writeFile(join(fixturesDir, fileName), "some content", "utf-8"); + + const found = await findConfig(fixturesDir, resolve(fixturesDir, fileName)); + expect(found).toEqual(resolve(fixturesDir, fileName)); + }); +}); + describe("getPluginId", () => { it("cuts off npm package scope and returns the rest part", () => { expect(getPluginId("@allurereport/classic")).toEqual("classic"); @@ -51,37 +113,3 @@ describe("resolvePlugin", () => { expect(() => resolvePlugin("classic")).rejects.toThrow("Cannot resolve plugin: classic"); }); }); - -describe("defineConfig", () => { - it("returns config which includes plugins descriptors with normalized id and the plugin instance", async () => { - class Plugin {} - - (importWrapper as unknown as MockInstance).mockImplementation((path: string) => { - return { default: Plugin }; - }); - - const result = await defineConfig({ - name: "foo", - output: "bar", - plugins: { - "allure/plugin-foo": { - import: "classic", - options: {}, - }, - }, - }); - - expect(result).toEqual( - expect.objectContaining({ - name: "foo", - output: "bar", - plugins: { - "allure/plugin-foo": { - import: "classic", - options: {}, - }, - }, - }), - ); - }); -}); diff --git a/packages/core/test/report.test.ts b/packages/core/test/report.test.ts index bd48e27..4f1b6d5 100644 --- a/packages/core/test/report.test.ts +++ b/packages/core/test/report.test.ts @@ -1,7 +1,8 @@ -import { Plugin } from "@allurereport/plugin-api"; +import type { Plugin } from "@allurereport/plugin-api"; import { BufferResultFile } from "@allurereport/reader-api"; -import { Mocked, describe, expect, it, vi } from "vitest"; -import { createConfig } from "../src/config.js"; +import type { Mocked } from "vitest"; +import { describe, expect, it, vi } from "vitest"; +import { resolveConfig } from "../src/index.js"; import { AllureReport } from "../src/report.js"; const createPlugin = (id: string, enabled: boolean = true, options: Record = {}) => { @@ -21,8 +22,8 @@ const createPlugin = (id: string, enabled: boolean = true, options: Record { it("should not fail with the empty report", async () => { - const config = await createConfig({ - reportName: "Allure Report", + const config = await resolveConfig({ + name: "Allure Report", }); const allureReport = new AllureReport(config); @@ -32,8 +33,8 @@ describe("report", () => { }); it("should not allow call done() before start()", async () => { - const config = await createConfig({ - reportName: "Allure Report", + const config = await resolveConfig({ + name: "Allure Report", }); const allureReport = new AllureReport(config); @@ -43,8 +44,8 @@ describe("report", () => { }); it("should not allow to readDirectory() before start()", async () => { - const config = await createConfig({ - reportName: "Allure Report", + const config = await resolveConfig({ + name: "Allure Report", }); const allureReport = new AllureReport(config); @@ -54,8 +55,8 @@ describe("report", () => { }); it("should not allow to readFile() before start()", async () => { - const config = await createConfig({ - reportName: "Allure Report", + const config = await resolveConfig({ + name: "Allure Report", }); const allureReport = new AllureReport(config); @@ -65,8 +66,8 @@ describe("report", () => { }); it("should not allow to readResult() before start()", async () => { - const config = await createConfig({ - reportName: "Allure Report", + const config = await resolveConfig({ + name: "Allure Report", }); const allureReport = new AllureReport(config); @@ -80,10 +81,10 @@ describe("report", () => { const p1 = createPlugin("p1"); const p2 = createPlugin("p2"); const p3 = createPlugin("p3"); - const config = await createConfig({ - reportName: "Allure Report", - plugins: [p1, p2, p3], + const config = await resolveConfig({ + name: "Allure Report", }); + config.plugins?.push(p1, p2, p3); const allureReport = new AllureReport(config); await allureReport.start(); @@ -100,10 +101,10 @@ describe("report", () => { const p1 = createPlugin("p1"); const p2 = createPlugin("p2", false); const p3 = createPlugin("p3"); - const config = await createConfig({ - reportName: "Allure Report", - plugins: [p1, p2, p3], + const config = await resolveConfig({ + name: "Allure Report", }); + config.plugins?.push(p1, p2, p3); const allureReport = new AllureReport(config); await allureReport.start(); @@ -119,10 +120,10 @@ describe("report", () => { const p1 = createPlugin("p1"); const p2 = createPlugin("p2"); const p3 = createPlugin("p3"); - const config = await createConfig({ - reportName: "Allure Report", - plugins: [p1, p2, p3], + const config = await resolveConfig({ + name: "Allure Report", }); + config.plugins?.push(p1, p2, p3); const allureReport = new AllureReport(config); await allureReport.start(); @@ -140,10 +141,10 @@ describe("report", () => { const p1 = createPlugin("p1"); const p2 = createPlugin("p2", false); const p3 = createPlugin("p3"); - const config = await createConfig({ - reportName: "Allure Report", - plugins: [p1, p2, p3], + const config = await resolveConfig({ + name: "Allure Report", }); + config.plugins?.push(p1, p2, p3); const allureReport = new AllureReport(config); await allureReport.start(); diff --git a/packages/plugin-api/src/config.ts b/packages/plugin-api/src/config.ts index 4dfa187..334ed3f 100644 --- a/packages/plugin-api/src/config.ts +++ b/packages/plugin-api/src/config.ts @@ -36,6 +36,6 @@ export interface Config { plugins?: Record; } -export const defineConfig = async (allureConfig: Config): Promise => { +export const defineConfig = (allureConfig: Config): Config => { return allureConfig; }; diff --git a/packages/plugin-api/test/config.test.ts b/packages/plugin-api/test/config.test.ts new file mode 100644 index 0000000..f56b81f --- /dev/null +++ b/packages/plugin-api/test/config.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from "vitest"; +import { type Config, defineConfig } from "../src/config.js"; + +describe("defineConfig", () => { + it("should return provided config", () => { + const config: Config = {}; + const defined = defineConfig(config); + + expect(defined).toBe(config); + }); +}); diff --git a/packages/plugin-api/test/dummy.test.ts b/packages/plugin-api/test/dummy.test.ts deleted file mode 100644 index f4f0ecd..0000000 --- a/packages/plugin-api/test/dummy.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { describe, it } from "vitest"; - -describe("dummy.test", () => { - it("dummy test", () => {}); -}); diff --git a/yarn.lock b/yarn.lock index 3692368..1e65418 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4962,6 +4962,7 @@ __metadata: "@allurereport/core": "workspace:*" "@allurereport/core-api": "workspace:*" "@allurereport/directory-watcher": "workspace:*" + "@allurereport/plugin-api": "workspace:*" "@allurereport/plugin-awesome": "workspace:*" "@allurereport/plugin-progress": "workspace:*" "@allurereport/plugin-server-reload": "workspace:*"