mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 18:40:42 +00:00
refactor: consolidate plugin install index store
This commit is contained in:
@@ -33,11 +33,15 @@ export const listMarketplacePlugins: Mock<ListMarketplacePluginsFn> = vi.fn();
|
||||
export const resolveMarketplaceInstallShortcut: Mock<ResolveMarketplaceInstallShortcutFn> = vi.fn();
|
||||
export const enablePluginInConfig: UnknownMock = vi.fn();
|
||||
export const recordPluginInstall: UnknownMock = vi.fn();
|
||||
export const loadPluginInstallRecords: AsyncUnknownMock = vi.fn(async (...args: unknown[]) => {
|
||||
const params = args[0] as LoadPluginInstallRecordsParams | undefined;
|
||||
return structuredClone(params?.config?.plugins?.installs ?? {});
|
||||
});
|
||||
export const writePersistedPluginInstallLedger: AsyncUnknownMock = vi.fn(async () => undefined);
|
||||
export const loadInstalledPluginIndexInstallRecords: AsyncUnknownMock = vi.fn(
|
||||
async (...args: unknown[]) => {
|
||||
const params = args[0] as LoadPluginInstallRecordsParams | undefined;
|
||||
return structuredClone(params?.config?.plugins?.installs ?? {});
|
||||
},
|
||||
);
|
||||
export const writePersistedInstalledPluginIndexInstallRecords: AsyncUnknownMock = vi.fn(
|
||||
async () => undefined,
|
||||
);
|
||||
export const clearPluginManifestRegistryCache: UnknownMock = vi.fn();
|
||||
export const loadPluginManifestRegistry: UnknownMock = vi.fn();
|
||||
export const buildPluginSnapshotReport: UnknownMock = vi.fn();
|
||||
@@ -157,18 +161,20 @@ vi.mock("../plugins/installs.js", () => ({
|
||||
)) as (typeof import("../plugins/installs.js"))["recordPluginInstall"],
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/install-ledger-store.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../plugins/install-ledger-store.js")>();
|
||||
vi.mock("../plugins/installed-plugin-index-records.js", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("../plugins/installed-plugin-index-records.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadPluginInstallRecords: ((...args: unknown[]) =>
|
||||
invokeMock<unknown[], unknown>(loadPluginInstallRecords, ...args)) as (
|
||||
...args: unknown[]
|
||||
) => unknown,
|
||||
writePersistedPluginInstallLedger: ((...args: unknown[]) =>
|
||||
invokeMock<unknown[], unknown>(writePersistedPluginInstallLedger, ...args)) as (
|
||||
loadInstalledPluginIndexInstallRecords: ((...args: unknown[]) =>
|
||||
invokeMock<unknown[], unknown>(loadInstalledPluginIndexInstallRecords, ...args)) as (
|
||||
...args: unknown[]
|
||||
) => unknown,
|
||||
writePersistedInstalledPluginIndexInstallRecords: ((...args: unknown[]) =>
|
||||
invokeMock<unknown[], unknown>(
|
||||
writePersistedInstalledPluginIndexInstallRecords,
|
||||
...args,
|
||||
)) as (...args: unknown[]) => unknown,
|
||||
recordPluginInstallInRecords: (
|
||||
records: Record<string, unknown>,
|
||||
update: { pluginId: string; installedAt?: string } & Record<string, unknown>,
|
||||
@@ -459,8 +465,8 @@ export function resetPluginsCliTestState() {
|
||||
resolveMarketplaceInstallShortcut.mockReset();
|
||||
enablePluginInConfig.mockReset();
|
||||
recordPluginInstall.mockReset();
|
||||
loadPluginInstallRecords.mockReset();
|
||||
writePersistedPluginInstallLedger.mockReset();
|
||||
loadInstalledPluginIndexInstallRecords.mockReset();
|
||||
writePersistedInstalledPluginIndexInstallRecords.mockReset();
|
||||
clearPluginManifestRegistryCache.mockReset();
|
||||
loadPluginManifestRegistry.mockReset();
|
||||
buildPluginSnapshotReport.mockReset();
|
||||
@@ -519,11 +525,11 @@ export function resetPluginsCliTestState() {
|
||||
recordPluginInstall.mockImplementation(
|
||||
((cfg: OpenClawConfig) => cfg) as (...args: unknown[]) => unknown,
|
||||
);
|
||||
loadPluginInstallRecords.mockImplementation(async (...args: unknown[]) => {
|
||||
loadInstalledPluginIndexInstallRecords.mockImplementation(async (...args: unknown[]) => {
|
||||
const params = args[0] as LoadPluginInstallRecordsParams | undefined;
|
||||
return structuredClone(params?.config?.plugins?.installs ?? {});
|
||||
});
|
||||
writePersistedPluginInstallLedger.mockResolvedValue(undefined);
|
||||
writePersistedInstalledPluginIndexInstallRecords.mockResolvedValue(undefined);
|
||||
loadPluginManifestRegistry.mockReturnValue({
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
@@ -544,7 +550,7 @@ export function resetPluginsCliTestState() {
|
||||
version: 1,
|
||||
hostContractVersion: "2026.4.25",
|
||||
compatRegistryVersion: "compat-v1",
|
||||
migrationVersion: 2,
|
||||
migrationVersion: 1,
|
||||
policyHash: "policy-v1",
|
||||
generatedAtMs: 1777118400000,
|
||||
plugins: [],
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
runtimeErrors,
|
||||
runtimeLogs,
|
||||
writeConfigFile,
|
||||
writePersistedPluginInstallLedger,
|
||||
writePersistedInstalledPluginIndexInstallRecords,
|
||||
} from "./plugins-cli-test-helpers.js";
|
||||
|
||||
const CLI_STATE_ROOT = "/tmp/openclaw-state";
|
||||
@@ -290,7 +290,7 @@ describe("plugins cli install", () => {
|
||||
expect(writeConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("installs marketplace plugins and persists install ledger", async () => {
|
||||
it("installs marketplace plugins and persists plugin index", async () => {
|
||||
const cfg = {
|
||||
plugins: {
|
||||
entries: {},
|
||||
@@ -329,7 +329,7 @@ describe("plugins cli install", () => {
|
||||
await runPluginsCommand(["plugins", "install", "alpha", "--marketplace", "local/repo"]);
|
||||
|
||||
expect(clearPluginManifestRegistryCache).toHaveBeenCalledTimes(1);
|
||||
expect(writePersistedPluginInstallLedger).toHaveBeenCalledWith({
|
||||
expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith({
|
||||
alpha: expect.objectContaining({
|
||||
source: "marketplace",
|
||||
installPath: cliInstallPath("alpha"),
|
||||
@@ -384,7 +384,7 @@ describe("plugins cli install", () => {
|
||||
spec: "clawhub:demo",
|
||||
}),
|
||||
);
|
||||
expect(writePersistedPluginInstallLedger).toHaveBeenCalledWith({
|
||||
expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith({
|
||||
demo: expect.objectContaining({
|
||||
source: "clawhub",
|
||||
spec: "clawhub:demo@1.2.3",
|
||||
@@ -464,7 +464,7 @@ describe("plugins cli install", () => {
|
||||
}),
|
||||
);
|
||||
expect(installPluginFromNpmSpec).not.toHaveBeenCalled();
|
||||
expect(writePersistedPluginInstallLedger).toHaveBeenCalledWith({
|
||||
expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith({
|
||||
demo: expect.objectContaining({
|
||||
source: "clawhub",
|
||||
spec: "clawhub:demo@1.2.3",
|
||||
|
||||
@@ -7,13 +7,13 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import { enablePluginInConfig } from "../plugins/enable.js";
|
||||
import {
|
||||
loadPluginInstallRecords,
|
||||
loadInstalledPluginIndexInstallRecords,
|
||||
PLUGIN_INSTALLS_CONFIG_PATH,
|
||||
removePluginInstallRecordFromRecords,
|
||||
withoutPluginInstallRecords,
|
||||
writePersistedPluginInstallLedger,
|
||||
writePersistedInstalledPluginIndexInstallRecords,
|
||||
withPluginInstallRecords,
|
||||
} from "../plugins/install-ledger-store.js";
|
||||
} from "../plugins/installed-plugin-index-records.js";
|
||||
import { listMarketplacePlugins } from "../plugins/marketplace.js";
|
||||
import { inspectPluginRegistry, refreshPluginRegistry } from "../plugins/plugin-registry.js";
|
||||
import { defaultSlotIdForKey } from "../plugins/slots.js";
|
||||
@@ -290,7 +290,7 @@ export function registerPluginsCli(program: Command) {
|
||||
.option("--json", "Print JSON")
|
||||
.action(async (id: string | undefined, opts: PluginInspectOptions) => {
|
||||
const cfg = loadConfig();
|
||||
const installRecords = await loadPluginInstallRecords({ config: cfg });
|
||||
const installRecords = await loadInstalledPluginIndexInstallRecords();
|
||||
const report = buildPluginDiagnosticsReport({
|
||||
config: cfg,
|
||||
...(opts.json ? { logger: quietPluginJsonLogger } : {}),
|
||||
@@ -584,7 +584,7 @@ export function registerPluginsCli(program: Command) {
|
||||
.action(async (id: string, opts: PluginUninstallOptions) => {
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
const sourceConfig = (snapshot.sourceConfig ?? snapshot.config) as OpenClawConfig;
|
||||
const installRecords = await loadPluginInstallRecords({ config: sourceConfig });
|
||||
const installRecords = await loadInstalledPluginIndexInstallRecords();
|
||||
const cfg = withPluginInstallRecords(sourceConfig, installRecords);
|
||||
const report = buildPluginDiagnosticsReport({ config: cfg });
|
||||
const extensionsDir = path.join(resolveStateDir(process.env, os.homedir), "extensions");
|
||||
@@ -691,7 +691,7 @@ export function registerPluginsCli(program: Command) {
|
||||
defaultRuntime.log(theme.warn(warning));
|
||||
}
|
||||
|
||||
await writePersistedPluginInstallLedger(
|
||||
await writePersistedInstalledPluginIndexInstallRecords(
|
||||
removePluginInstallRecordFromRecords(installRecords, pluginId),
|
||||
);
|
||||
await replaceConfigFile({
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
runtimeLogs,
|
||||
uninstallPlugin,
|
||||
writeConfigFile,
|
||||
writePersistedPluginInstallLedger,
|
||||
writePersistedInstalledPluginIndexInstallRecords,
|
||||
} from "./plugins-cli-test-helpers.js";
|
||||
|
||||
const CLI_STATE_ROOT = "/tmp/openclaw-state";
|
||||
@@ -103,7 +103,7 @@ describe("plugins cli uninstall", () => {
|
||||
deleteFiles: false,
|
||||
}),
|
||||
);
|
||||
expect(writePersistedPluginInstallLedger).toHaveBeenCalledWith({});
|
||||
expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith({});
|
||||
expect(writeConfigFile).toHaveBeenCalledWith({
|
||||
plugins: {
|
||||
entries: {},
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
updateNpmInstalledHookPacks,
|
||||
updateNpmInstalledPlugins,
|
||||
writeConfigFile,
|
||||
writePersistedPluginInstallLedger,
|
||||
writePersistedInstalledPluginIndexInstallRecords,
|
||||
} from "./plugins-cli-test-helpers.js";
|
||||
|
||||
function createTrackedPluginConfig(params: {
|
||||
@@ -211,7 +211,9 @@ describe("plugins cli update", () => {
|
||||
dryRun: false,
|
||||
}),
|
||||
);
|
||||
expect(writePersistedPluginInstallLedger).toHaveBeenCalledWith(nextConfig.plugins?.installs);
|
||||
expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith(
|
||||
nextConfig.plugins?.installs,
|
||||
);
|
||||
expect(writeConfigFile).toHaveBeenCalledWith({});
|
||||
expect(refreshPluginRegistry).toHaveBeenCalledWith({
|
||||
config: {},
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
refreshPluginRegistry,
|
||||
resetPluginsCliTestState,
|
||||
writeConfigFile,
|
||||
writePersistedPluginInstallLedger,
|
||||
writePersistedInstalledPluginIndexInstallRecords,
|
||||
} from "./plugins-cli-test-helpers.js";
|
||||
|
||||
describe("persistPluginInstall", () => {
|
||||
@@ -46,7 +46,7 @@ describe("persistPluginInstall", () => {
|
||||
});
|
||||
|
||||
expect(next).toEqual(enabledConfig);
|
||||
expect(writePersistedPluginInstallLedger).toHaveBeenCalledWith({
|
||||
expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith({
|
||||
alpha: expect.objectContaining({
|
||||
source: "npm",
|
||||
spec: "alpha@1.0.0",
|
||||
|
||||
@@ -3,12 +3,12 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { type HookInstallUpdate, recordHookInstall } from "../hooks/installs.js";
|
||||
import { enablePluginInConfig } from "../plugins/enable.js";
|
||||
import {
|
||||
loadPluginInstallRecords,
|
||||
loadInstalledPluginIndexInstallRecords,
|
||||
PLUGIN_INSTALLS_CONFIG_PATH,
|
||||
recordPluginInstallInRecords,
|
||||
withoutPluginInstallRecords,
|
||||
writePersistedPluginInstallLedger,
|
||||
} from "../plugins/install-ledger-store.js";
|
||||
writePersistedInstalledPluginIndexInstallRecords,
|
||||
} from "../plugins/installed-plugin-index-records.js";
|
||||
import type { PluginInstallUpdate } from "../plugins/installs.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
@@ -46,14 +46,14 @@ export async function persistPluginInstall(params: {
|
||||
addInstalledPluginToAllowlist(params.config, params.pluginId),
|
||||
params.pluginId,
|
||||
).config;
|
||||
const installRecords = await loadPluginInstallRecords({ config: params.config });
|
||||
const installRecords = await loadInstalledPluginIndexInstallRecords();
|
||||
const nextInstallRecords = recordPluginInstallInRecords(installRecords, {
|
||||
pluginId: params.pluginId,
|
||||
...params.install,
|
||||
});
|
||||
const slotResult = applySlotSelectionForPlugin(next, params.pluginId);
|
||||
next = withoutPluginInstallRecords(slotResult.config);
|
||||
await writePersistedPluginInstallLedger(nextInstallRecords);
|
||||
await writePersistedInstalledPluginIndexInstallRecords(nextInstallRecords);
|
||||
await replaceConfigFile({
|
||||
nextConfig: next,
|
||||
...(params.baseHash !== undefined ? { baseHash: params.baseHash } : {}),
|
||||
@@ -62,6 +62,7 @@ export async function persistPluginInstall(params: {
|
||||
await refreshPluginRegistryAfterConfigMutation({
|
||||
config: next,
|
||||
reason: "source-changed",
|
||||
installRecords: nextInstallRecords,
|
||||
logger: {
|
||||
warn: (message) => defaultRuntime.log(theme.warn(message)),
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { loadInstalledPluginIndexInstallRecords } from "../plugins/installed-plugin-index-records.js";
|
||||
import type { InstalledPluginIndexRefreshReason } from "../plugins/installed-plugin-index.js";
|
||||
import { refreshPluginRegistry } from "../plugins/plugin-registry.js";
|
||||
|
||||
@@ -12,12 +13,17 @@ export async function refreshPluginRegistryAfterConfigMutation(params: {
|
||||
reason: InstalledPluginIndexRefreshReason;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
installRecords?: Awaited<ReturnType<typeof loadInstalledPluginIndexInstallRecords>>;
|
||||
logger?: PluginRegistryRefreshLogger;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const installRecords =
|
||||
params.installRecords ??
|
||||
(await loadInstalledPluginIndexInstallRecords(params.env ? { env: params.env } : {}));
|
||||
await refreshPluginRegistry({
|
||||
config: params.config,
|
||||
reason: params.reason,
|
||||
installRecords,
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { loadConfig, readConfigFileSnapshot, replaceConfigFile } from "../config/config.js";
|
||||
import { updateNpmInstalledHookPacks } from "../hooks/update.js";
|
||||
import {
|
||||
loadPluginInstallRecords,
|
||||
loadInstalledPluginIndexInstallRecords,
|
||||
PLUGIN_INSTALLS_CONFIG_PATH,
|
||||
withoutPluginInstallRecords,
|
||||
writePersistedPluginInstallLedger,
|
||||
writePersistedInstalledPluginIndexInstallRecords,
|
||||
withPluginInstallRecords,
|
||||
} from "../plugins/install-ledger-store.js";
|
||||
} from "../plugins/installed-plugin-index-records.js";
|
||||
import { updateNpmInstalledPlugins } from "../plugins/update.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { theme } from "../terminal/theme.js";
|
||||
@@ -23,7 +23,7 @@ export async function runPluginUpdateCommand(params: {
|
||||
}) {
|
||||
const sourceSnapshotPromise = readConfigFileSnapshot().catch(() => null);
|
||||
const cfg = loadConfig();
|
||||
const pluginInstallRecords = await loadPluginInstallRecords({ config: cfg });
|
||||
const pluginInstallRecords = await loadInstalledPluginIndexInstallRecords();
|
||||
const cfgWithPluginInstallRecords = withPluginInstallRecords(cfg, pluginInstallRecords);
|
||||
const logger = {
|
||||
info: (msg: string) => defaultRuntime.log(msg),
|
||||
@@ -119,18 +119,18 @@ export async function runPluginUpdateCommand(params: {
|
||||
|
||||
if (!params.opts.dryRun && (pluginResult.changed || hookResult.changed)) {
|
||||
const nextPluginInstallRecords = pluginResult.config.plugins?.installs ?? {};
|
||||
const shouldPersistPluginInstallLedger =
|
||||
const shouldPersistPluginInstallIndex =
|
||||
pluginResult.changed || Object.keys(pluginInstallRecords).length > 0;
|
||||
if (shouldPersistPluginInstallLedger) {
|
||||
await writePersistedPluginInstallLedger(nextPluginInstallRecords);
|
||||
if (shouldPersistPluginInstallIndex) {
|
||||
await writePersistedInstalledPluginIndexInstallRecords(nextPluginInstallRecords);
|
||||
}
|
||||
const nextConfig = shouldPersistPluginInstallLedger
|
||||
const nextConfig = shouldPersistPluginInstallIndex
|
||||
? withoutPluginInstallRecords(hookResult.config)
|
||||
: hookResult.config;
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
baseHash: (await sourceSnapshotPromise)?.hash,
|
||||
...(shouldPersistPluginInstallLedger
|
||||
...(shouldPersistPluginInstallIndex
|
||||
? { writeOptions: { unsetPaths: [Array.from(PLUGIN_INSTALLS_CONFIG_PATH)] } }
|
||||
: {}),
|
||||
});
|
||||
@@ -138,6 +138,7 @@ export async function runPluginUpdateCommand(params: {
|
||||
await refreshPluginRegistryAfterConfigMutation({
|
||||
config: nextConfig,
|
||||
reason: "source-changed",
|
||||
installRecords: nextPluginInstallRecords,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,12 +148,15 @@ vi.mock("../plugins/update.js", () => ({
|
||||
updateNpmInstalledPlugins: (...args: unknown[]) => updateNpmInstalledPlugins(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/install-ledger-store.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../plugins/install-ledger-store.js")>();
|
||||
vi.mock("../plugins/installed-plugin-index-records.js", async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import("../plugins/installed-plugin-index-records.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadPluginInstallRecords: vi.fn(async ({ config }) => config?.plugins?.installs ?? {}),
|
||||
writePersistedPluginInstallLedger: vi.fn(async () => undefined),
|
||||
loadInstalledPluginIndexInstallRecords: vi.fn(
|
||||
async ({ config }) => config?.plugins?.installs ?? {},
|
||||
),
|
||||
writePersistedInstalledPluginIndexInstallRecords: vi.fn(async () => undefined),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -43,12 +43,12 @@ import {
|
||||
} from "../../infra/update-global.js";
|
||||
import { runGatewayUpdate, type UpdateRunResult } from "../../infra/update-runner.js";
|
||||
import {
|
||||
loadPluginInstallRecords,
|
||||
loadInstalledPluginIndexInstallRecords,
|
||||
PLUGIN_INSTALLS_CONFIG_PATH,
|
||||
withoutPluginInstallRecords,
|
||||
writePersistedPluginInstallLedger,
|
||||
writePersistedInstalledPluginIndexInstallRecords,
|
||||
withPluginInstallRecords,
|
||||
} from "../../plugins/install-ledger-store.js";
|
||||
} from "../../plugins/installed-plugin-index-records.js";
|
||||
import { syncPluginsForUpdateChannel, updateNpmInstalledPlugins } from "../../plugins/update.js";
|
||||
import { runCommandWithTimeout } from "../../process/exec.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
@@ -584,9 +584,7 @@ async function updatePluginsAfterCoreUpdate(params: {
|
||||
defaultRuntime.log(theme.heading("Updating plugins..."));
|
||||
}
|
||||
|
||||
const pluginInstallRecords = await loadPluginInstallRecords({
|
||||
config: params.configSnapshot.sourceConfig,
|
||||
});
|
||||
const pluginInstallRecords = await loadInstalledPluginIndexInstallRecords();
|
||||
const syncResult = await syncPluginsForUpdateChannel({
|
||||
config: withPluginInstallRecords(params.configSnapshot.sourceConfig, pluginInstallRecords),
|
||||
channel: params.channel,
|
||||
@@ -630,7 +628,7 @@ async function updatePluginsAfterCoreUpdate(params: {
|
||||
pluginConfig = npmResult.config;
|
||||
|
||||
if (syncResult.changed || npmResult.changed) {
|
||||
await writePersistedPluginInstallLedger(pluginConfig.plugins?.installs ?? {});
|
||||
await writePersistedInstalledPluginIndexInstallRecords(pluginConfig.plugins?.installs ?? {});
|
||||
const nextConfig = withoutPluginInstallRecords(pluginConfig);
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
|
||||
Reference in New Issue
Block a user