mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-23 22:55:24 +00:00
Release: add plugin npm publish workflow (#47678)
* Release: add plugin npm publish workflow * Release: make plugin publish scope explicit
This commit is contained in:
394
scripts/lib/plugin-npm-release.ts
Normal file
394
scripts/lib/plugin-npm-release.ts
Normal file
@@ -0,0 +1,394 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { parseReleaseVersion } from "../openclaw-npm-release-check.ts";
|
||||
|
||||
export type PluginPackageJson = {
|
||||
name?: string;
|
||||
version?: string;
|
||||
private?: boolean;
|
||||
openclaw?: {
|
||||
extensions?: string[];
|
||||
install?: {
|
||||
npmSpec?: string;
|
||||
};
|
||||
release?: {
|
||||
publishToNpm?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type PublishablePluginPackage = {
|
||||
extensionId: string;
|
||||
packageDir: string;
|
||||
packageName: string;
|
||||
version: string;
|
||||
channel: "stable" | "beta";
|
||||
publishTag: "latest" | "beta";
|
||||
installNpmSpec?: string;
|
||||
};
|
||||
|
||||
export type PluginReleasePlanItem = PublishablePluginPackage & {
|
||||
alreadyPublished: boolean;
|
||||
};
|
||||
|
||||
export type PluginReleasePlan = {
|
||||
all: PluginReleasePlanItem[];
|
||||
candidates: PluginReleasePlanItem[];
|
||||
skippedPublished: PluginReleasePlanItem[];
|
||||
};
|
||||
|
||||
export type PluginReleaseSelectionMode = "selected" | "all-publishable";
|
||||
|
||||
export type GitRangeSelection = {
|
||||
baseRef: string;
|
||||
headRef: string;
|
||||
};
|
||||
|
||||
export type ParsedPluginReleaseArgs = {
|
||||
selection: string[];
|
||||
selectionMode?: PluginReleaseSelectionMode;
|
||||
pluginsFlagProvided: boolean;
|
||||
baseRef?: string;
|
||||
headRef?: string;
|
||||
};
|
||||
|
||||
type PublishablePluginPackageCandidate = {
|
||||
extensionId: string;
|
||||
packageDir: string;
|
||||
packageJson: PluginPackageJson;
|
||||
};
|
||||
|
||||
function readPluginPackageJson(path: string): PluginPackageJson {
|
||||
return JSON.parse(readFileSync(path, "utf8")) as PluginPackageJson;
|
||||
}
|
||||
|
||||
export function parsePluginReleaseSelection(value: string | undefined): string[] {
|
||||
if (!value?.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
...new Set(
|
||||
value
|
||||
.split(/[,\s]+/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
].toSorted();
|
||||
}
|
||||
|
||||
export function parsePluginReleaseSelectionMode(
|
||||
value: string | undefined,
|
||||
): PluginReleaseSelectionMode {
|
||||
if (value === "selected" || value === "all-publishable") {
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Unknown selection mode: ${value ?? "<missing>"}. Expected "selected" or "all-publishable".`,
|
||||
);
|
||||
}
|
||||
|
||||
export function parsePluginReleaseArgs(argv: string[]): ParsedPluginReleaseArgs {
|
||||
let selection: string[] = [];
|
||||
let selectionMode: PluginReleaseSelectionMode | undefined;
|
||||
let pluginsFlagProvided = false;
|
||||
let baseRef: string | undefined;
|
||||
let headRef: string | undefined;
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--") {
|
||||
continue;
|
||||
}
|
||||
if (arg === "--plugins") {
|
||||
selection = parsePluginReleaseSelection(argv[index + 1]);
|
||||
pluginsFlagProvided = true;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--selection-mode") {
|
||||
selectionMode = parsePluginReleaseSelectionMode(argv[index + 1]);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--base-ref") {
|
||||
baseRef = argv[index + 1];
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--head-ref") {
|
||||
headRef = argv[index + 1];
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
|
||||
if (pluginsFlagProvided && selection.length === 0) {
|
||||
throw new Error("`--plugins` must include at least one package name.");
|
||||
}
|
||||
if (selectionMode === "selected" && !pluginsFlagProvided) {
|
||||
throw new Error("`--selection-mode selected` requires `--plugins`.");
|
||||
}
|
||||
if (selectionMode === "all-publishable" && pluginsFlagProvided) {
|
||||
throw new Error("`--selection-mode all-publishable` must not be combined with `--plugins`.");
|
||||
}
|
||||
if (selection.length > 0 && (baseRef || headRef)) {
|
||||
throw new Error("Use either --plugins or --base-ref/--head-ref, not both.");
|
||||
}
|
||||
if (selectionMode && (baseRef || headRef)) {
|
||||
throw new Error("Use either --selection-mode or --base-ref/--head-ref, not both.");
|
||||
}
|
||||
if ((baseRef && !headRef) || (!baseRef && headRef)) {
|
||||
throw new Error("Both --base-ref and --head-ref are required together.");
|
||||
}
|
||||
|
||||
return { selection, selectionMode, pluginsFlagProvided, baseRef, headRef };
|
||||
}
|
||||
|
||||
export function collectPublishablePluginPackageErrors(
|
||||
candidate: PublishablePluginPackageCandidate,
|
||||
): string[] {
|
||||
const { packageJson } = candidate;
|
||||
const errors: string[] = [];
|
||||
const packageName = packageJson.name?.trim() ?? "";
|
||||
const packageVersion = packageJson.version?.trim() ?? "";
|
||||
const extensions = packageJson.openclaw?.extensions ?? [];
|
||||
|
||||
if (!packageName.startsWith("@openclaw/")) {
|
||||
errors.push(
|
||||
`package name must start with "@openclaw/"; found "${packageName || "<missing>"}".`,
|
||||
);
|
||||
}
|
||||
if (packageJson.private === true) {
|
||||
errors.push("package.json private must not be true.");
|
||||
}
|
||||
if (!packageVersion) {
|
||||
errors.push("package.json version must be non-empty.");
|
||||
} else if (parseReleaseVersion(packageVersion) === null) {
|
||||
errors.push(
|
||||
`package.json version must match YYYY.M.D or YYYY.M.D-beta.N; found "${packageVersion}".`,
|
||||
);
|
||||
}
|
||||
if (!Array.isArray(extensions) || extensions.length === 0) {
|
||||
errors.push("openclaw.extensions must contain at least one entry.");
|
||||
}
|
||||
if (extensions.some((entry) => typeof entry !== "string" || !entry.trim())) {
|
||||
errors.push("openclaw.extensions must contain only non-empty strings.");
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function collectPublishablePluginPackages(
|
||||
rootDir = resolve("."),
|
||||
): PublishablePluginPackage[] {
|
||||
const extensionsDir = join(rootDir, "extensions");
|
||||
const dirs = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) =>
|
||||
entry.isDirectory(),
|
||||
);
|
||||
|
||||
const publishable: PublishablePluginPackage[] = [];
|
||||
const validationErrors: string[] = [];
|
||||
|
||||
for (const dir of dirs) {
|
||||
const packageDir = join("extensions", dir.name);
|
||||
const absolutePackageDir = join(extensionsDir, dir.name);
|
||||
const packageJsonPath = join(absolutePackageDir, "package.json");
|
||||
let packageJson: PluginPackageJson;
|
||||
try {
|
||||
packageJson = readPluginPackageJson(packageJsonPath);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packageJson.openclaw?.release?.publishToNpm !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const candidate = {
|
||||
extensionId: dir.name,
|
||||
packageDir,
|
||||
packageJson,
|
||||
} satisfies PublishablePluginPackageCandidate;
|
||||
const errors = collectPublishablePluginPackageErrors(candidate);
|
||||
if (errors.length > 0) {
|
||||
validationErrors.push(...errors.map((error) => `${dir.name}: ${error}`));
|
||||
continue;
|
||||
}
|
||||
|
||||
const version = packageJson.version!.trim();
|
||||
const parsedVersion = parseReleaseVersion(version);
|
||||
if (parsedVersion === null) {
|
||||
validationErrors.push(
|
||||
`${dir.name}: package.json version must match YYYY.M.D or YYYY.M.D-beta.N; found "${version}".`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
publishable.push({
|
||||
extensionId: dir.name,
|
||||
packageDir,
|
||||
packageName: packageJson.name!.trim(),
|
||||
version,
|
||||
channel: parsedVersion.channel,
|
||||
publishTag: parsedVersion.channel === "beta" ? "beta" : "latest",
|
||||
installNpmSpec: packageJson.openclaw?.install?.npmSpec?.trim() || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
throw new Error(
|
||||
`Publishable plugin metadata validation failed:\n${validationErrors.map((error) => `- ${error}`).join("\n")}`,
|
||||
);
|
||||
}
|
||||
|
||||
return publishable.toSorted((left, right) => left.packageName.localeCompare(right.packageName));
|
||||
}
|
||||
|
||||
export function resolveSelectedPublishablePluginPackages(params: {
|
||||
plugins: PublishablePluginPackage[];
|
||||
selection: string[];
|
||||
}): PublishablePluginPackage[] {
|
||||
if (params.selection.length === 0) {
|
||||
return params.plugins;
|
||||
}
|
||||
|
||||
const byName = new Map(params.plugins.map((plugin) => [plugin.packageName, plugin]));
|
||||
const selected: PublishablePluginPackage[] = [];
|
||||
const missing: string[] = [];
|
||||
|
||||
for (const packageName of params.selection) {
|
||||
const plugin = byName.get(packageName);
|
||||
if (!plugin) {
|
||||
missing.push(packageName);
|
||||
continue;
|
||||
}
|
||||
selected.push(plugin);
|
||||
}
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Unknown or non-publishable plugin package selection: ${missing.join(", ")}.`);
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
export function collectChangedExtensionIdsFromPaths(paths: readonly string[]): string[] {
|
||||
const extensionIds = new Set<string>();
|
||||
|
||||
for (const path of paths) {
|
||||
const normalized = path.trim().replaceAll("\\", "/");
|
||||
const match = /^extensions\/([^/]+)\//.exec(normalized);
|
||||
if (match?.[1]) {
|
||||
extensionIds.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return [...extensionIds].toSorted();
|
||||
}
|
||||
|
||||
function isNullGitRef(ref: string | undefined): boolean {
|
||||
return !ref || /^0+$/.test(ref);
|
||||
}
|
||||
|
||||
export function collectChangedExtensionIdsFromGitRange(params: {
|
||||
rootDir?: string;
|
||||
gitRange: GitRangeSelection;
|
||||
}): string[] {
|
||||
const rootDir = params.rootDir ?? resolve(".");
|
||||
const { baseRef, headRef } = params.gitRange;
|
||||
|
||||
if (isNullGitRef(baseRef) || isNullGitRef(headRef)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const changedPaths = execFileSync(
|
||||
"git",
|
||||
["diff", "--name-only", "--diff-filter=ACMR", baseRef, headRef, "--", "extensions"],
|
||||
{
|
||||
cwd: rootDir,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
},
|
||||
)
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
return collectChangedExtensionIdsFromPaths(changedPaths);
|
||||
}
|
||||
|
||||
export function resolveChangedPublishablePluginPackages(params: {
|
||||
plugins: PublishablePluginPackage[];
|
||||
changedExtensionIds: readonly string[];
|
||||
}): PublishablePluginPackage[] {
|
||||
if (params.changedExtensionIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const changed = new Set(params.changedExtensionIds);
|
||||
return params.plugins.filter((plugin) => changed.has(plugin.extensionId));
|
||||
}
|
||||
|
||||
export function isPluginVersionPublished(packageName: string, version: string): boolean {
|
||||
const tempDir = mkdtempSync(join(tmpdir(), "openclaw-plugin-npm-view-"));
|
||||
const userconfigPath = join(tempDir, "npmrc");
|
||||
writeFileSync(userconfigPath, "");
|
||||
|
||||
try {
|
||||
execFileSync(
|
||||
"npm",
|
||||
["view", `${packageName}@${version}`, "version", "--userconfig", userconfigPath],
|
||||
{
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
},
|
||||
);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function collectPluginReleasePlan(params?: {
|
||||
rootDir?: string;
|
||||
selection?: string[];
|
||||
selectionMode?: PluginReleaseSelectionMode;
|
||||
gitRange?: GitRangeSelection;
|
||||
}): PluginReleasePlan {
|
||||
const allPublishable = collectPublishablePluginPackages(params?.rootDir);
|
||||
const selectedPublishable =
|
||||
params?.selectionMode === "all-publishable"
|
||||
? allPublishable
|
||||
: params?.selection && params.selection.length > 0
|
||||
? resolveSelectedPublishablePluginPackages({
|
||||
plugins: allPublishable,
|
||||
selection: params.selection,
|
||||
})
|
||||
: params?.gitRange
|
||||
? resolveChangedPublishablePluginPackages({
|
||||
plugins: allPublishable,
|
||||
changedExtensionIds: collectChangedExtensionIdsFromGitRange({
|
||||
rootDir: params.rootDir,
|
||||
gitRange: params.gitRange,
|
||||
}),
|
||||
})
|
||||
: allPublishable;
|
||||
|
||||
const all = selectedPublishable.map((plugin) => ({
|
||||
...plugin,
|
||||
alreadyPublished: isPluginVersionPublished(plugin.packageName, plugin.version),
|
||||
}));
|
||||
|
||||
return {
|
||||
all,
|
||||
candidates: all.filter((plugin) => !plugin.alreadyPublished),
|
||||
skippedPublished: all.filter((plugin) => plugin.alreadyPublished),
|
||||
};
|
||||
}
|
||||
45
scripts/plugin-npm-publish.sh
Normal file
45
scripts/plugin-npm-publish.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mode="${1:-}"
|
||||
package_dir="${2:-}"
|
||||
|
||||
if [[ "${mode}" != "--dry-run" && "${mode}" != "--publish" ]]; then
|
||||
echo "usage: bash scripts/plugin-npm-publish.sh [--dry-run|--publish] <package-dir>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z "${package_dir}" ]]; then
|
||||
echo "missing package dir" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
package_name="$(node -e 'const pkg = require(require("node:path").resolve(process.argv[1], "package.json")); console.log(pkg.name)' "${package_dir}")"
|
||||
package_version="$(node -e 'const pkg = require(require("node:path").resolve(process.argv[1], "package.json")); console.log(pkg.version)' "${package_dir}")"
|
||||
publish_cmd=(npm publish --access public --provenance)
|
||||
release_channel="stable"
|
||||
|
||||
if [[ "${package_version}" == *-beta.* ]]; then
|
||||
publish_cmd=(npm publish --access public --tag beta --provenance)
|
||||
release_channel="beta"
|
||||
fi
|
||||
|
||||
echo "Resolved package dir: ${package_dir}"
|
||||
echo "Resolved package name: ${package_name}"
|
||||
echo "Resolved package version: ${package_version}"
|
||||
echo "Resolved release channel: ${release_channel}"
|
||||
echo "Publish auth: GitHub OIDC trusted publishing"
|
||||
|
||||
printf 'Publish command:'
|
||||
printf ' %q' "${publish_cmd[@]}"
|
||||
printf '\n'
|
||||
|
||||
if [[ "${mode}" == "--dry-run" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
(
|
||||
cd "${package_dir}"
|
||||
"${publish_cmd[@]}"
|
||||
)
|
||||
47
scripts/plugin-npm-release-check.ts
Normal file
47
scripts/plugin-npm-release-check.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
collectChangedExtensionIdsFromGitRange,
|
||||
collectPublishablePluginPackages,
|
||||
parsePluginReleaseArgs,
|
||||
resolveChangedPublishablePluginPackages,
|
||||
resolveSelectedPublishablePluginPackages,
|
||||
} from "./lib/plugin-npm-release.ts";
|
||||
|
||||
export function runPluginNpmReleaseCheck(argv: string[]) {
|
||||
const { selection, selectionMode, baseRef, headRef } = parsePluginReleaseArgs(argv);
|
||||
const publishable = collectPublishablePluginPackages();
|
||||
const selected =
|
||||
selectionMode === "all-publishable"
|
||||
? publishable
|
||||
: selection.length > 0
|
||||
? resolveSelectedPublishablePluginPackages({
|
||||
plugins: publishable,
|
||||
selection,
|
||||
})
|
||||
: baseRef && headRef
|
||||
? resolveChangedPublishablePluginPackages({
|
||||
plugins: publishable,
|
||||
changedExtensionIds: collectChangedExtensionIdsFromGitRange({
|
||||
gitRange: { baseRef, headRef },
|
||||
}),
|
||||
})
|
||||
: publishable;
|
||||
|
||||
console.log("plugin-npm-release-check: publishable plugin metadata looks OK.");
|
||||
if (baseRef && headRef && selected.length === 0) {
|
||||
console.log(
|
||||
` - no publishable plugin package changes detected between ${baseRef} and ${headRef}`,
|
||||
);
|
||||
}
|
||||
for (const plugin of selected) {
|
||||
console.log(
|
||||
` - ${plugin.packageName}@${plugin.version} (${plugin.channel}, ${plugin.extensionId})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
runPluginNpmReleaseCheck(process.argv.slice(2));
|
||||
}
|
||||
18
scripts/plugin-npm-release-plan.ts
Normal file
18
scripts/plugin-npm-release-plan.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { collectPluginReleasePlan, parsePluginReleaseArgs } from "./lib/plugin-npm-release.ts";
|
||||
|
||||
export function collectPluginNpmReleasePlan(argv: string[]) {
|
||||
const { selection, selectionMode, baseRef, headRef } = parsePluginReleaseArgs(argv);
|
||||
return collectPluginReleasePlan({
|
||||
selection,
|
||||
selectionMode,
|
||||
gitRange: baseRef && headRef ? { baseRef, headRef } : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
const plan = collectPluginNpmReleasePlan(process.argv.slice(2));
|
||||
console.log(JSON.stringify(plan, null, 2));
|
||||
}
|
||||
@@ -34,15 +34,6 @@ const appcastPath = resolve("appcast.xml");
|
||||
const laneBuildMin = 1_000_000_000;
|
||||
const laneFloorAdoptionDateKey = 20260227;
|
||||
|
||||
function normalizePluginSyncVersion(version: string): string {
|
||||
const normalized = version.trim().replace(/^v/, "");
|
||||
const base = /^([0-9]+\.[0-9]+\.[0-9]+)/.exec(normalized)?.[1];
|
||||
if (base) {
|
||||
return base;
|
||||
}
|
||||
return normalized.replace(/[-+].*$/, "");
|
||||
}
|
||||
|
||||
export function collectBundledExtensionRootDependencyGapErrors(params: {
|
||||
rootPackage: PackageJson;
|
||||
extensions: BundledExtension[];
|
||||
@@ -190,54 +181,6 @@ export function collectPackUnpackedSizeErrors(results: Iterable<PackResult>): st
|
||||
return errors;
|
||||
}
|
||||
|
||||
function checkPluginVersions() {
|
||||
const rootPackagePath = resolve("package.json");
|
||||
const rootPackage = JSON.parse(readFileSync(rootPackagePath, "utf8")) as PackageJson;
|
||||
const targetVersion = rootPackage.version;
|
||||
const targetBaseVersion = targetVersion ? normalizePluginSyncVersion(targetVersion) : null;
|
||||
|
||||
if (!targetVersion || !targetBaseVersion) {
|
||||
console.error("release-check: root package.json missing version.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const extensionsDir = resolve("extensions");
|
||||
const entries = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) =>
|
||||
entry.isDirectory(),
|
||||
);
|
||||
|
||||
const mismatches: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const packagePath = join(extensionsDir, entry.name, "package.json");
|
||||
let pkg: PackageJson;
|
||||
try {
|
||||
pkg = JSON.parse(readFileSync(packagePath, "utf8")) as PackageJson;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pkg.name || !pkg.version) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalizePluginSyncVersion(pkg.version) !== targetBaseVersion) {
|
||||
mismatches.push(`${pkg.name} (${pkg.version})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatches.length > 0) {
|
||||
console.error(
|
||||
`release-check: plugin versions must match release base ${targetBaseVersion} (root ${targetVersion}):`,
|
||||
);
|
||||
for (const item of mismatches) {
|
||||
console.error(` - ${item}`);
|
||||
}
|
||||
console.error("release-check: run `pnpm plugins:sync` to align plugin versions.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function extractTag(item: string, tag: string): string | null {
|
||||
const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regex = new RegExp(`<${escapedTag}>([^<]+)</${escapedTag}>`);
|
||||
@@ -393,7 +336,6 @@ async function checkPluginSdkExports() {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
checkPluginVersions();
|
||||
checkAppcastSparkleVersions();
|
||||
await checkPluginSdkExports();
|
||||
checkBundledExtensionRootDependencyMirrors();
|
||||
|
||||
Reference in New Issue
Block a user