mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-05-01 06:36:23 +08:00
refactor: move shared helpers off reserved sdk seams
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
74344f185b3149695443bf8815c9dd784daf9c0b8118ecc54129dc57899e9564 plugin-sdk-api-baseline.json
|
||||
7b84c2f1e5743dac9c764fdee6d3b23e64553516c409f4a24f009a36c40d64e8 plugin-sdk-api-baseline.jsonl
|
||||
491267e919c6bf426f673a9066e703811c7779a32de87edd0ce493147fd4438e plugin-sdk-api-baseline.json
|
||||
590d21aeb520f34b5bf23abb7b17602b204f170547c772d60b604bb34a3940bb plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -138,12 +138,12 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| `plugin-sdk/allow-from` | `formatAllowFromLowercase` |
|
||||
| `plugin-sdk/channel-secret-runtime` | Narrow secret-contract collection helpers for channel/plugin secret surfaces |
|
||||
| `plugin-sdk/secret-ref-runtime` | Narrow `coerceSecretRef` and SecretRef typing helpers for secret-contract/config parsing |
|
||||
| `plugin-sdk/security-runtime` | Shared trust, DM gating, external-content, and secret-collection helpers |
|
||||
| `plugin-sdk/security-runtime` | Shared trust, DM gating, external-content, constant-time secret comparison, and secret-collection helpers |
|
||||
| `plugin-sdk/ssrf-policy` | Host allowlist and private-network SSRF policy helpers |
|
||||
| `plugin-sdk/ssrf-dispatcher` | Narrow pinned-dispatcher helpers without the broad infra runtime surface |
|
||||
| `plugin-sdk/ssrf-runtime` | Pinned-dispatcher, SSRF-guarded fetch, and SSRF policy helpers |
|
||||
| `plugin-sdk/ssrf-runtime` | Pinned-dispatcher, SSRF-guarded fetch, SSRF error, and SSRF policy helpers |
|
||||
| `plugin-sdk/secret-input` | Secret input parsing helpers |
|
||||
| `plugin-sdk/webhook-ingress` | Webhook request/target helpers |
|
||||
| `plugin-sdk/webhook-ingress` | Webhook request/target helpers and raw websocket/body coercion |
|
||||
| `plugin-sdk/webhook-request-guards` | Request body size/timeout helpers |
|
||||
</Accordion>
|
||||
|
||||
@@ -160,7 +160,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| `plugin-sdk/lazy-runtime` | Lazy runtime import/binding helpers such as `createLazyRuntimeModule`, `createLazyRuntimeMethod`, and `createLazyRuntimeSurface` |
|
||||
| `plugin-sdk/process-runtime` | Process exec helpers |
|
||||
| `plugin-sdk/cli-runtime` | CLI formatting, wait, version, argument-invocation, and lazy command-group helpers |
|
||||
| `plugin-sdk/gateway-runtime` | Gateway client and channel-status patch helpers |
|
||||
| `plugin-sdk/gateway-runtime` | Gateway client, gateway CLI RPC, gateway protocol errors, and channel-status patch helpers |
|
||||
| `plugin-sdk/config-runtime` | Config load/write helpers and plugin-config lookup helpers |
|
||||
| `plugin-sdk/telegram-command-config` | Telegram command-name/description normalization and duplicate/conflict checks, even when the bundled Telegram contract surface is unavailable |
|
||||
| `plugin-sdk/text-autolink-runtime` | File-reference autolink detection without the broad text-runtime barrel |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { resolveBlueBubblesEffectiveAllowPrivateNetwork } from "./accounts.js";
|
||||
import { createBlueBubblesDebounceRegistry } from "./monitor-debounce.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type { RuntimeEnv } from "../runtime-api.js";
|
||||
export { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
export { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
export { applyBasicWebhookRequestGuards } from "openclaw/plugin-sdk/webhook-ingress";
|
||||
export {
|
||||
installRequestBodyLimitGuard,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
callGatewayFromCli,
|
||||
ErrorCodes,
|
||||
errorShape,
|
||||
} from "openclaw/plugin-sdk/browser-node-runtime";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { GatewayRequestHandlerOptions } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
type GatewayRequestHandlerOptions,
|
||||
} from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { Type } from "typebox";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { callGatewayFromCli } from "openclaw/plugin-sdk/browser-node-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import type { RuntimeLogger } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import type { GoogleMeetConfig } from "../config.js";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Command } from "commander";
|
||||
import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import { formatZonedTimestamp } from "openclaw/plugin-sdk/matrix-runtime-shared";
|
||||
import type { ChannelSetupInput } from "openclaw/plugin-sdk/setup";
|
||||
import { resolveMatrixAccount, resolveMatrixAccountConfig } from "./matrix/accounts.js";
|
||||
import { listMatrixOwnDevices, pruneMatrixStaleGatewayDevices } from "./matrix/actions/devices.js";
|
||||
@@ -30,6 +29,7 @@ import { isOpenClawManagedMatrixDevice } from "./matrix/device-health.js";
|
||||
import type { MatrixDirectRoomCandidate } from "./matrix/direct-management.js";
|
||||
import { formatMatrixErrorMessage } from "./matrix/errors.js";
|
||||
import { applyMatrixProfileUpdate, type MatrixProfileUpdateResult } from "./profile-update.js";
|
||||
import { formatZonedTimestamp } from "./runtime-api.js";
|
||||
import { getMatrixRuntime } from "./runtime.js";
|
||||
import { matrixSetupAdapter } from "./setup-core.js";
|
||||
import type { CoreConfig } from "./types.js";
|
||||
|
||||
@@ -61,7 +61,7 @@ export { evaluateSenderGroupAccessForPolicy } from "openclaw/plugin-sdk/group-ac
|
||||
export { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
||||
export { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
|
||||
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
||||
export { rawDataToString } from "openclaw/plugin-sdk/browser-node-runtime";
|
||||
export { rawDataToString } from "openclaw/plugin-sdk/webhook-ingress";
|
||||
export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
|
||||
export {
|
||||
DEFAULT_GROUP_HISTORY_LIMIT,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createHmac } from "node:crypto";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedOptionalString,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { rawDataToString } from "openclaw/plugin-sdk/browser-node-runtime";
|
||||
import { formatInboundFromLabel as formatInboundFromLabelShared } from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { createDedupeCache, type OpenClawConfig } from "openclaw/plugin-sdk/core";
|
||||
import { resolveThreadSessionKeys as resolveThreadSessionKeysShared } from "openclaw/plugin-sdk/routing";
|
||||
@@ -6,6 +5,7 @@ import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { rawDataToString } from "openclaw/plugin-sdk/webhook-ingress";
|
||||
|
||||
export { createDedupeCache, rawDataToString };
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import type { ResolvedMattermostAccount } from "../mattermost/accounts.js";
|
||||
import { getMattermostRuntime } from "../runtime.js";
|
||||
|
||||
@@ -10,7 +10,7 @@ const gatewayRpcMock = vi.hoisted(() => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/browser-node-runtime", () => ({
|
||||
vi.mock("openclaw/plugin-sdk/gateway-runtime", () => ({
|
||||
callGatewayFromCli: gatewayRpcMock.callGatewayFromCli,
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { callGatewayFromCli } from "openclaw/plugin-sdk/browser-node-runtime";
|
||||
import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import { formatQaGatewayLogsForError } from "./gateway-log-redaction.js";
|
||||
|
||||
type QaGatewayRpcRequestOptions = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export type { Command } from "commander";
|
||||
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
export { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
export { callGatewayFromCli } from "openclaw/plugin-sdk/browser-node-runtime";
|
||||
export { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
export type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
|
||||
export { defaultQaRuntimeModelForMode } from "./model-selection.runtime.js";
|
||||
export {
|
||||
|
||||
@@ -2,10 +2,8 @@ import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import {
|
||||
formatMemoryDreamingDay,
|
||||
resolveSessionTranscriptsDirForAgent,
|
||||
} from "openclaw/plugin-sdk/memory-core";
|
||||
import { resolveSessionTranscriptsDirForAgent } from "openclaw/plugin-sdk/memory-host-core";
|
||||
import { formatMemoryDreamingDay } from "openclaw/plugin-sdk/memory-host-status";
|
||||
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Security module: token validation, rate limiting, input sanitization, user allowlist.
|
||||
*/
|
||||
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import {
|
||||
createFixedWindowRateLimiter,
|
||||
type FixedWindowRateLimiter,
|
||||
|
||||
@@ -2,11 +2,11 @@ import { createServer } from "node:http";
|
||||
import type { IncomingMessage } from "node:http";
|
||||
import net from "node:net";
|
||||
import * as grammy from "grammy";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { isDiagnosticsEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { defaultRuntime } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import {
|
||||
logWebhookError,
|
||||
|
||||
@@ -14,4 +14,4 @@ export {
|
||||
type LookupFn,
|
||||
type SsrFPolicy,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
export { SsrFBlockedError } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
export { SsrFBlockedError } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SsrFBlockedError } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { SsrFBlockedError } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import type { LookupFn } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { authenticate } from "./auth.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import crypto from "node:crypto";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { TwilioConfig } from "../config.js";
|
||||
import { getHeader } from "../http-headers.js";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from "node:crypto";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { getHeader } from "./http-headers.js";
|
||||
import type { WebhookContext } from "./types.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { z } from "zod";
|
||||
import type { PluginRuntime } from "../api.js";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import type { ResolvedZaloAccount } from "./accounts.js";
|
||||
import type { ZaloFetch, ZaloUpdate } from "./api.js";
|
||||
import type { ZaloRuntimeEnv } from "./monitor.types.js";
|
||||
|
||||
@@ -58,4 +58,4 @@ export {
|
||||
sendPayloadWithChunkedTextAndMedia,
|
||||
type OutboundReplyPayload,
|
||||
} from "openclaw/plugin-sdk/reply-payload";
|
||||
export { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
export { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fsp from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/browser-security-runtime";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
|
||||
export async function writeQrDataUrlToTempFile(
|
||||
qrDataUrl: string,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// Public gateway/client helpers for plugins that talk to the host gateway surface.
|
||||
|
||||
export * from "../gateway/channel-status-patches.js";
|
||||
export { addGatewayClientOptions, callGatewayFromCli } from "../cli/gateway-rpc.js";
|
||||
export type { GatewayRpcOpts } from "../cli/gateway-rpc.js";
|
||||
export { GatewayClient } from "../gateway/client.js";
|
||||
export {
|
||||
createOperatorApprovalsGatewayClient,
|
||||
withOperatorApprovalsGatewayClient,
|
||||
} from "../gateway/operator-approvals-client.js";
|
||||
export { ErrorCodes, errorShape } from "../gateway/protocol/index.js";
|
||||
export type { EventFrame } from "../gateway/protocol/index.js";
|
||||
export type { GatewayRequestHandlerOptions } from "../gateway/server-methods/types.js";
|
||||
|
||||
@@ -9,3 +9,4 @@ export * from "../security/context-visibility.js";
|
||||
export * from "../security/dm-policy-shared.js";
|
||||
export * from "../security/external-content.js";
|
||||
export * from "../security/safe-regex.js";
|
||||
export { safeEqualSecret } from "../security/secret-equal.js";
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
export {
|
||||
closeDispatcher,
|
||||
createPinnedDispatcher,
|
||||
SsrFBlockedError,
|
||||
isBlockedHostnameOrIp,
|
||||
resolvePinnedHostname,
|
||||
resolvePinnedHostnameWithPolicy,
|
||||
|
||||
@@ -43,5 +43,6 @@ export { normalizeWebhookPath, resolveWebhookPath } from "./webhook-path.js";
|
||||
export { resolveRequestClientIp } from "../gateway/net.js";
|
||||
export { createAuthRateLimiter } from "../gateway/auth-rate-limit.js";
|
||||
export type { AuthRateLimiter, RateLimitConfig } from "../gateway/auth-rate-limit.js";
|
||||
export { rawDataToString } from "../infra/ws.js";
|
||||
export { normalizePluginHttpPath } from "../plugins/http-path.js";
|
||||
export { DEFAULT_WEBHOOK_MAX_BODY_BYTES } from "../infra/http-body.js";
|
||||
|
||||
@@ -86,6 +86,12 @@ function collectPluginOwnedSdkEntrypoints(): string[] {
|
||||
.toSorted();
|
||||
}
|
||||
|
||||
function resolvePluginOwnerFromEntrypoint(entrypoint: string): string | undefined {
|
||||
return collectBundledPluginIds().find(
|
||||
(pluginId) => entrypoint === pluginId || entrypoint.startsWith(`${pluginId}-`),
|
||||
);
|
||||
}
|
||||
|
||||
function collectClassificationOverlaps(classifications: Record<string, readonly string[]>) {
|
||||
const seen = new Map<string, string[]>();
|
||||
for (const [classification, entrypoints] of Object.entries(classifications)) {
|
||||
@@ -224,6 +230,47 @@ function collectExtensionCoreImportLeaks(): Array<{ file: string; specifier: str
|
||||
return leaks;
|
||||
}
|
||||
|
||||
function collectCrossOwnerReservedSdkImports(): Array<{
|
||||
file: string;
|
||||
specifier: string;
|
||||
owner?: string;
|
||||
}> {
|
||||
const leaks: Array<{ file: string; specifier: string; owner?: string }> = [];
|
||||
const reserved = new Set<string>(reservedBundledPluginSdkEntrypoints);
|
||||
const importPattern =
|
||||
/\b(?:import|export)\b[\s\S]*?\bfrom\s*["']openclaw\/plugin-sdk\/([a-z0-9][a-z0-9-]*)["']/g;
|
||||
|
||||
for (const file of collectExtensionFiles(resolve(REPO_ROOT, "extensions"))) {
|
||||
const repoRelativePath = relative(REPO_ROOT, file).replaceAll("\\", "/");
|
||||
if (
|
||||
/(?:^|\/)(?:__tests__|tests|test-support)(?:\/|$)/.test(repoRelativePath) ||
|
||||
/(?:^|\/)test-support\.[cm]?tsx?$/.test(repoRelativePath) ||
|
||||
/\.test-support\.[cm]?tsx?$/.test(repoRelativePath) ||
|
||||
/\.test\.[cm]?tsx?$/.test(repoRelativePath)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const pluginId = repoRelativePath.split("/")[1];
|
||||
const source = readFileSync(file, "utf8");
|
||||
for (const match of source.matchAll(importPattern)) {
|
||||
const subpath = match[1];
|
||||
if (!subpath || !reserved.has(subpath)) {
|
||||
continue;
|
||||
}
|
||||
const owner = resolvePluginOwnerFromEntrypoint(subpath);
|
||||
if (owner === pluginId) {
|
||||
continue;
|
||||
}
|
||||
leaks.push({
|
||||
file: repoRelativePath,
|
||||
specifier: `openclaw/plugin-sdk/${subpath}`,
|
||||
owner,
|
||||
});
|
||||
}
|
||||
}
|
||||
return leaks;
|
||||
}
|
||||
|
||||
describe("plugin-sdk package contract guardrails", () => {
|
||||
it("keeps plugin-sdk entrypoint metadata unique", () => {
|
||||
const counts = new Map<string, number>();
|
||||
@@ -350,6 +397,10 @@ describe("plugin-sdk package contract guardrails", () => {
|
||||
expect(collectExtensionCoreImportLeaks()).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps reserved SDK compatibility subpaths inside their owning bundled plugins", () => {
|
||||
expect(collectCrossOwnerReservedSdkImports()).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps generic core poll helpers free of plugin owner names", () => {
|
||||
expect(collectGenericCoreOwnerNameLeaks()).toEqual([]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user