




















@@ -0,0 +1,208 @@
1+import { spawnSync } from "node:child_process";
2+import fs from "node:fs";
3+import path from "node:path";
4+import { pathToFileURL } from "node:url";
5+6+const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH =
7+"src/config/bundled-channel-config-metadata.generated.ts";
8+9+function readJsonFile(filePath) {
10+return JSON.parse(fs.readFileSync(filePath, "utf8"));
11+}
12+13+function writeJsonFile(filePath, value) {
14+fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
15+}
16+17+function resolvePackageDir(repoRoot, packageDir) {
18+return path.isAbsolute(packageDir) ? packageDir : path.resolve(repoRoot, packageDir);
19+}
20+21+function readGeneratedBundledChannelConfigs(repoRoot) {
22+const metadataPath = path.join(repoRoot, GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA_PATH);
23+if (!fs.existsSync(metadataPath)) {
24+return new Map();
25+}
26+const source = fs.readFileSync(metadataPath, "utf8");
27+const match = source.match(
28+/export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = ([\s\S]*?) as const;/u,
29+);
30+if (!match?.[1]) {
31+return new Map();
32+}
33+34+let entries;
35+try {
36+entries = Function(`"use strict"; return (${match[1]});`)();
37+} catch {
38+return new Map();
39+}
40+if (!Array.isArray(entries)) {
41+return new Map();
42+}
43+44+const byPlugin = new Map();
45+for (const entry of entries) {
46+if (
47+!entry ||
48+typeof entry !== "object" ||
49+typeof entry.pluginId !== "string" ||
50+typeof entry.channelId !== "string" ||
51+!entry.schema ||
52+typeof entry.schema !== "object"
53+) {
54+continue;
55+}
56+const pluginConfigs = byPlugin.get(entry.pluginId) ?? {};
57+pluginConfigs[entry.channelId] = {
58+schema: entry.schema,
59+ ...(typeof entry.label === "string" && entry.label ? { label: entry.label } : {}),
60+ ...(typeof entry.description === "string" && entry.description
61+ ? { description: entry.description }
62+ : {}),
63+ ...(entry.uiHints && typeof entry.uiHints === "object" ? { uiHints: entry.uiHints } : {}),
64+};
65+byPlugin.set(entry.pluginId, pluginConfigs);
66+}
67+return byPlugin;
68+}
69+70+function mergeGeneratedChannelConfigs(manifest, generatedChannelConfigs) {
71+if (!generatedChannelConfigs || Object.keys(generatedChannelConfigs).length === 0) {
72+return manifest;
73+}
74+const existingChannelConfigs =
75+manifest.channelConfigs && typeof manifest.channelConfigs === "object"
76+ ? manifest.channelConfigs
77+ : {};
78+const channelConfigs = { ...existingChannelConfigs };
79+for (const [channelId, generated] of Object.entries(generatedChannelConfigs)) {
80+const existing =
81+existingChannelConfigs[channelId] && typeof existingChannelConfigs[channelId] === "object"
82+ ? existingChannelConfigs[channelId]
83+ : {};
84+channelConfigs[channelId] = {
85+ ...generated,
86+ ...existing,
87+schema: generated.schema,
88+ ...(generated.uiHints || existing.uiHints
89+ ? { uiHints: { ...generated.uiHints, ...existing.uiHints } }
90+ : {}),
91+ ...(existing.label || generated.label ? { label: existing.label ?? generated.label } : {}),
92+ ...(existing.description || generated.description
93+ ? { description: existing.description ?? generated.description }
94+ : {}),
95+};
96+}
97+return {
98+ ...manifest,
99+ channelConfigs,
100+};
101+}
102+103+export function resolveAugmentedPluginNpmManifest(params) {
104+const repoRoot = path.resolve(params.repoRoot ?? ".");
105+const packageDir = resolvePackageDir(repoRoot, params.packageDir);
106+const manifestPath = path.join(packageDir, "openclaw.plugin.json");
107+if (!fs.existsSync(manifestPath)) {
108+return {
109+ manifestPath,
110+pluginId: path.basename(packageDir),
111+changed: false,
112+manifest: undefined,
113+reason: "missing-manifest",
114+};
115+}
116+117+const manifest = readJsonFile(manifestPath);
118+const pluginId =
119+typeof manifest.id === "string" && manifest.id ? manifest.id : path.basename(packageDir);
120+const generatedChannelConfigs = readGeneratedBundledChannelConfigs(repoRoot).get(pluginId);
121+const augmentedManifest = mergeGeneratedChannelConfigs(manifest, generatedChannelConfigs);
122+const changed = JSON.stringify(augmentedManifest) !== JSON.stringify(manifest);
123+return {
124+ manifestPath,
125+ pluginId,
126+ changed,
127+manifest: augmentedManifest,
128+reason: changed ? "generated-channel-configs" : "unchanged",
129+};
130+}
131+132+export function withAugmentedPluginNpmManifestForPackage(params, callback) {
133+const repoRoot = path.resolve(params.repoRoot ?? ".");
134+const packageDir = resolvePackageDir(repoRoot, params.packageDir);
135+const resolved = resolveAugmentedPluginNpmManifest({
136+ repoRoot,
137+ packageDir,
138+});
139+140+if (!resolved.changed || !resolved.manifest) {
141+return callback({
142+ ...resolved,
143+ packageDir,
144+ repoRoot,
145+applied: false,
146+});
147+}
148+149+const originalManifest = fs.readFileSync(resolved.manifestPath, "utf8");
150+console.error(
151+`[plugin-npm-publish] overlaying generated channel config metadata for ${resolved.pluginId}`,
152+);
153+writeJsonFile(resolved.manifestPath, resolved.manifest);
154+try {
155+return callback({
156+ ...resolved,
157+ packageDir,
158+ repoRoot,
159+applied: true,
160+});
161+} finally {
162+fs.writeFileSync(resolved.manifestPath, originalManifest, "utf8");
163+}
164+}
165+166+function parseRunArgs(argv) {
167+if (argv[0] !== "--run") {
168+throw new Error(
169+"usage: node scripts/lib/plugin-npm-package-manifest.mjs --run <package-dir> -- <command> [args...]",
170+);
171+}
172+const packageDir = argv[1];
173+const separatorIndex = argv.indexOf("--", 2);
174+if (!packageDir || separatorIndex === -1 || separatorIndex === argv.length - 1) {
175+throw new Error(
176+"usage: node scripts/lib/plugin-npm-package-manifest.mjs --run <package-dir> -- <command> [args...]",
177+);
178+}
179+return {
180+ packageDir,
181+command: argv[separatorIndex + 1],
182+args: argv.slice(separatorIndex + 2),
183+};
184+}
185+186+export function main(argv = process.argv.slice(2)) {
187+const { packageDir, command, args } = parseRunArgs(argv);
188+return withAugmentedPluginNpmManifestForPackage({ packageDir }, ({ packageDir: cwd }) => {
189+const result = spawnSync(command, args, {
190+ cwd,
191+env: process.env,
192+stdio: "inherit",
193+});
194+if (result.error) {
195+throw result.error;
196+}
197+return result.status ?? 1;
198+});
199+}
200+201+if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
202+try {
203+process.exitCode = main();
204+} catch (error) {
205+console.error(error instanceof Error ? error.message : String(error));
206+process.exitCode = 1;
207+}
208+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。