mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-24 07:01:49 +00:00
refactor: dedupe plugin release package scanning
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
import { resolve } from "node:path";
|
||||
import { validateExternalCodePluginPackageJson } from "../../packages/plugin-package-contract/src/index.ts";
|
||||
import { parseReleaseVersion } from "../openclaw-npm-release-check.ts";
|
||||
import {
|
||||
collectExtensionPackageJsonCandidates,
|
||||
collectChangedPathsFromGitRange,
|
||||
collectChangedExtensionIdsFromPaths,
|
||||
collectPublishablePluginPackageErrors,
|
||||
parsePluginReleaseArgs,
|
||||
parsePluginReleaseSelection,
|
||||
parsePluginReleaseSelectionMode,
|
||||
resolvePublishablePluginVersion,
|
||||
resolveGitCommitSha,
|
||||
resolveChangedPublishablePluginPackages,
|
||||
resolveSelectedPublishablePluginPackages,
|
||||
@@ -90,10 +90,6 @@ const CLAWHUB_SHARED_RELEASE_INPUT_PATHS = [
|
||||
"scripts/plugin-clawhub-release-plan.ts",
|
||||
] as const;
|
||||
|
||||
function readPluginPackageJson(path: string): PluginPackageJson {
|
||||
return JSON.parse(readFileSync(path, "utf8")) as PluginPackageJson;
|
||||
}
|
||||
|
||||
function getRegistryBaseUrl(explicit?: string) {
|
||||
return (
|
||||
explicit?.trim() ||
|
||||
@@ -106,63 +102,50 @@ function getRegistryBaseUrl(explicit?: string) {
|
||||
export function collectClawHubPublishablePluginPackages(
|
||||
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;
|
||||
}
|
||||
|
||||
for (const candidate of collectExtensionPackageJsonCandidates<PluginPackageJson>(rootDir)) {
|
||||
const { extensionId, packageDir, packageJson } = candidate;
|
||||
if (packageJson.openclaw?.release?.publishToClawHub !== true) {
|
||||
continue;
|
||||
}
|
||||
if (!SAFE_EXTENSION_ID_RE.test(dir.name)) {
|
||||
if (!SAFE_EXTENSION_ID_RE.test(extensionId)) {
|
||||
validationErrors.push(
|
||||
`${dir.name}: extension directory name must match ^[a-z0-9][a-z0-9._-]*$ for ClawHub publish.`,
|
||||
`${extensionId}: extension directory name must match ^[a-z0-9][a-z0-9._-]*$ for ClawHub publish.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const errors = collectPublishablePluginPackageErrors({
|
||||
extensionId: dir.name,
|
||||
extensionId,
|
||||
packageDir,
|
||||
packageJson,
|
||||
});
|
||||
if (errors.length > 0) {
|
||||
validationErrors.push(...errors.map((error) => `${dir.name}: ${error}`));
|
||||
validationErrors.push(...errors.map((error) => `${extensionId}: ${error}`));
|
||||
continue;
|
||||
}
|
||||
const contractValidation = validateExternalCodePluginPackageJson(packageJson);
|
||||
if (contractValidation.issues.length > 0) {
|
||||
validationErrors.push(
|
||||
...contractValidation.issues.map((issue) => `${dir.name}: ${issue.message}`),
|
||||
...contractValidation.issues.map((issue) => `${extensionId}: ${issue.message}`),
|
||||
);
|
||||
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, YYYY.M.D-N, or YYYY.M.D-beta.N; found "${version}".`,
|
||||
);
|
||||
const resolvedVersion = resolvePublishablePluginVersion({
|
||||
extensionId,
|
||||
packageJson,
|
||||
validationErrors,
|
||||
});
|
||||
if (!resolvedVersion) {
|
||||
continue;
|
||||
}
|
||||
const { version, parsedVersion } = resolvedVersion;
|
||||
|
||||
publishable.push({
|
||||
extensionId: dir.name,
|
||||
extensionId,
|
||||
packageDir,
|
||||
packageName: packageJson.name!.trim(),
|
||||
version,
|
||||
|
||||
@@ -55,14 +55,61 @@ export type ParsedPluginReleaseArgs = {
|
||||
headRef?: string;
|
||||
};
|
||||
|
||||
type PublishablePluginPackageCandidate = {
|
||||
export type PublishablePluginPackageCandidate<
|
||||
TPackageJson extends PluginPackageJson = PluginPackageJson,
|
||||
> = {
|
||||
extensionId: string;
|
||||
packageDir: string;
|
||||
packageJson: PluginPackageJson;
|
||||
packageJson: TPackageJson;
|
||||
};
|
||||
|
||||
function readPluginPackageJson(path: string): PluginPackageJson {
|
||||
return JSON.parse(readFileSync(path, "utf8")) as PluginPackageJson;
|
||||
function readPluginPackageJson<TPackageJson extends PluginPackageJson = PluginPackageJson>(
|
||||
path: string,
|
||||
): TPackageJson {
|
||||
return JSON.parse(readFileSync(path, "utf8")) as TPackageJson;
|
||||
}
|
||||
|
||||
export function collectExtensionPackageJsonCandidates<
|
||||
TPackageJson extends PluginPackageJson = PluginPackageJson,
|
||||
>(rootDir = resolve(".")): PublishablePluginPackageCandidate<TPackageJson>[] {
|
||||
const extensionsDir = join(rootDir, "extensions");
|
||||
const dirs = readdirSync(extensionsDir, { withFileTypes: true }).filter((entry) =>
|
||||
entry.isDirectory(),
|
||||
);
|
||||
|
||||
const candidates: PublishablePluginPackageCandidate<TPackageJson>[] = [];
|
||||
for (const dir of dirs) {
|
||||
const packageDir = join("extensions", dir.name);
|
||||
const absolutePackageDir = join(extensionsDir, dir.name);
|
||||
const packageJsonPath = join(absolutePackageDir, "package.json");
|
||||
try {
|
||||
candidates.push({
|
||||
extensionId: dir.name,
|
||||
packageDir,
|
||||
packageJson: readPluginPackageJson<TPackageJson>(packageJsonPath),
|
||||
});
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
export function resolvePublishablePluginVersion(params: {
|
||||
extensionId: string;
|
||||
packageJson: Pick<PluginPackageJson, "version">;
|
||||
validationErrors: string[];
|
||||
}): { version: string; parsedVersion: NonNullable<ReturnType<typeof parseReleaseVersion>> } | null {
|
||||
const version = params.packageJson.version?.trim() ?? "";
|
||||
const parsedVersion = parseReleaseVersion(version);
|
||||
if (parsedVersion === null) {
|
||||
params.validationErrors.push(
|
||||
`${params.extensionId}: package.json version must match YYYY.M.D, YYYY.M.D-N, or YYYY.M.D-beta.N; found "${version}".`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return { version, parsedVersion };
|
||||
}
|
||||
|
||||
export function normalizeGitDiffPath(path: string): string {
|
||||
@@ -191,51 +238,33 @@ export function collectPublishablePluginPackageErrors(
|
||||
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;
|
||||
}
|
||||
|
||||
for (const candidate of collectExtensionPackageJsonCandidates(rootDir)) {
|
||||
const { extensionId, packageDir, packageJson } = candidate;
|
||||
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}`));
|
||||
validationErrors.push(...errors.map((error) => `${extensionId}: ${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, YYYY.M.D-N, or YYYY.M.D-beta.N; found "${version}".`,
|
||||
);
|
||||
const resolvedVersion = resolvePublishablePluginVersion({
|
||||
extensionId,
|
||||
packageJson,
|
||||
validationErrors,
|
||||
});
|
||||
if (!resolvedVersion) {
|
||||
continue;
|
||||
}
|
||||
const { version, parsedVersion } = resolvedVersion;
|
||||
|
||||
publishable.push({
|
||||
extensionId: dir.name,
|
||||
extensionId,
|
||||
packageDir,
|
||||
packageName: packageJson.name!.trim(),
|
||||
version,
|
||||
|
||||
Reference in New Issue
Block a user