mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
1da74794b6
* fix(release): define immutable release plan contract * fix(release): harden immutable plan authority * fix(release): tighten release plan authority * fix(release): share plugin publication authority * fix(release): verify plan authority remotely * fix(release): track ClawHub publication authorities * fix(release): trust remote tooling tag identity * fix(release): close plugin publication authority * fix(release): align npm authority selection * fix(release): bind plans to validation intent * fix(release): require qualification cadence * fix(release): reject lossy canonical values * fix(release): narrow qualification cadence * fix(release): bind plan parser dependency * fix(release): add tagless diagnostic plans * fix(release): attest release plan parser tree * fix(release): isolate verified plan parser snapshot * fix(release): verify plan tooling before execution
128 lines
4.3 KiB
TypeScript
128 lines
4.3 KiB
TypeScript
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
import { join, resolve } from "node:path";
|
|
import type {
|
|
PluginPackageJson,
|
|
PublishablePluginPackageCandidate,
|
|
} from "./plugin-publication-collector.ts";
|
|
|
|
// Any change here can alter the package inventory for both registries. Range
|
|
// selectors and workflow triggers must keep this closure in sync.
|
|
export const PLUGIN_PUBLICATION_SHARED_AUTHORITY_PATHS = [
|
|
"package.json",
|
|
"pnpm-lock.yaml",
|
|
"packages/normalization-core/src",
|
|
"packages/plugin-package-contract/src",
|
|
"scripts/lib/npm-publish-plan.mjs",
|
|
"scripts/lib/plugin-publication-candidates.ts",
|
|
"scripts/lib/plugin-publication-collector.ts",
|
|
"scripts/lib/release-version.mjs",
|
|
] as const;
|
|
|
|
export const PLUGIN_NPM_RELEASE_AUTHORITY_PATHS = [
|
|
...PLUGIN_PUBLICATION_SHARED_AUTHORITY_PATHS,
|
|
".github/actions/setup-node-env",
|
|
".github/workflows/plugin-npm-release.yml",
|
|
"scripts/generate-npm-package-lock.mjs",
|
|
"scripts/generate-npm-package-lock.mts",
|
|
"scripts/lib/actions-artifact-archive.mjs",
|
|
"scripts/lib/npm-json-output.mts",
|
|
"scripts/lib/plugin-npm-package-manifest.mjs",
|
|
"scripts/lib/plugin-npm-package-manifest.mts",
|
|
"scripts/lib/plugin-npm-release.ts",
|
|
"scripts/lib/tsx-cli-shim.mjs",
|
|
"scripts/plugin-npm-publish.sh",
|
|
"scripts/plugin-npm-release-check.ts",
|
|
"scripts/plugin-npm-release-plan.ts",
|
|
"scripts/plugin-publication-artifact.mjs",
|
|
"scripts/release-tooling-identity.d.mts",
|
|
"scripts/release-tooling-identity.mjs",
|
|
"scripts/verify-plugin-npm-published-runtime.mts",
|
|
] as const;
|
|
|
|
function hasAuthorityPathChanges(
|
|
paths: readonly string[],
|
|
authorityPaths: readonly string[],
|
|
): boolean {
|
|
return paths.some((path) =>
|
|
authorityPaths.some(
|
|
(authorityPath) => path === authorityPath || path.startsWith(`${authorityPath}/`),
|
|
),
|
|
);
|
|
}
|
|
|
|
export function hasPluginPublicationSharedAuthorityChanges(paths: readonly string[]): boolean {
|
|
return hasAuthorityPathChanges(paths, PLUGIN_PUBLICATION_SHARED_AUTHORITY_PATHS);
|
|
}
|
|
|
|
export function hasPluginNpmReleaseAuthorityChanges(paths: readonly string[]): boolean {
|
|
return hasAuthorityPathChanges(paths, PLUGIN_NPM_RELEASE_AUTHORITY_PATHS);
|
|
}
|
|
|
|
function readPluginPackageJson(absolutePath: string, repoPath: string): PluginPackageJson {
|
|
let raw: string;
|
|
try {
|
|
raw = readFileSync(absolutePath, "utf8");
|
|
} catch (error) {
|
|
throw new Error(`plugin candidate manifest is unreadable: ${repoPath}`, { cause: error });
|
|
}
|
|
try {
|
|
return JSON.parse(raw) as PluginPackageJson;
|
|
} catch (error) {
|
|
throw new Error(`plugin candidate manifest is malformed JSON: ${repoPath}`, { cause: error });
|
|
}
|
|
}
|
|
|
|
function pluginPackageJsonExists(absolutePath: string, repoPath: string): boolean {
|
|
try {
|
|
statSync(absolutePath);
|
|
return true;
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
return false;
|
|
}
|
|
throw new Error(`plugin candidate manifest is unreadable: ${repoPath}`, { cause: error });
|
|
}
|
|
}
|
|
|
|
function readOptionalPluginReadme(absolutePath: string, repoPath: string): string | undefined {
|
|
try {
|
|
return readFileSync(absolutePath, "utf8");
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
return undefined;
|
|
}
|
|
throw new Error(`plugin candidate README is unreadable: ${repoPath}`, { cause: error });
|
|
}
|
|
}
|
|
|
|
export function collectExtensionPackageJsonCandidates<
|
|
TPackageJson extends PluginPackageJson = PluginPackageJson,
|
|
>(rootDir = resolve(".")): PublishablePluginPackageCandidate<TPackageJson>[] {
|
|
const extensionsDir = join(rootDir, "extensions");
|
|
return readdirSync(extensionsDir, { withFileTypes: true }).flatMap((entry) => {
|
|
if (!entry.isDirectory()) {
|
|
return [];
|
|
}
|
|
const packageDir = `extensions/${entry.name}`;
|
|
const absolutePackageDir = join(extensionsDir, entry.name);
|
|
const packageJsonPath = join(absolutePackageDir, "package.json");
|
|
if (!pluginPackageJsonExists(packageJsonPath, `${packageDir}/package.json`)) {
|
|
return [];
|
|
}
|
|
return [
|
|
{
|
|
extensionId: entry.name,
|
|
packageDir,
|
|
packageJson: readPluginPackageJson(
|
|
packageJsonPath,
|
|
`${packageDir}/package.json`,
|
|
) as TPackageJson,
|
|
readmeText: readOptionalPluginReadme(
|
|
join(absolutePackageDir, "README.md"),
|
|
`${packageDir}/README.md`,
|
|
),
|
|
},
|
|
];
|
|
});
|
|
}
|