mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-20 00:31:37 +00:00
fix(msteams): emit native cjs runtime entrypoints (#102366)
Co-authored-by: Galin Iliev <Galin.Iliev@microsoft.com>
This commit is contained in:
@@ -59,7 +59,8 @@
|
||||
"pluginApi": ">=2026.7.2"
|
||||
},
|
||||
"build": {
|
||||
"openclawVersion": "2026.7.2"
|
||||
"openclawVersion": "2026.7.2",
|
||||
"runtimeFormat": "cjs"
|
||||
},
|
||||
"release": {
|
||||
"publishToClawHub": true,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
export function listPublishablePluginPackageDirs(params?: Record<string, unknown>): string[];
|
||||
/** List package-local runtime output files expected from a runtime build plan. */
|
||||
export function listPluginNpmRuntimeBuildOutputs(plan: unknown): string[];
|
||||
/** Resolve the package-local runtime build plan for one publishable plugin package. */
|
||||
export function resolvePluginNpmRuntimeBuildPlan(params: unknown): {
|
||||
export type PluginNpmRuntimeBuildFormat = "esm" | "cjs";
|
||||
export type PluginNpmRuntimeBuildPlan = {
|
||||
runtimeBuildOutputs: string[];
|
||||
packageFiles: string[];
|
||||
packagePeerMetadata: {
|
||||
@@ -27,34 +27,20 @@ export function resolvePluginNpmRuntimeBuildPlan(params: unknown): {
|
||||
[k: string]: string;
|
||||
};
|
||||
outDir: string;
|
||||
runtimeFormat: PluginNpmRuntimeBuildFormat;
|
||||
runtimeExtensions: string[];
|
||||
runtimeSetupEntry: string | undefined;
|
||||
} | null;
|
||||
};
|
||||
/** Resolve the package-local runtime build plan for one publishable plugin package. */
|
||||
export function resolvePluginNpmRuntimeBuildPlan(params: unknown): PluginNpmRuntimeBuildPlan | null;
|
||||
/** Build package-local runtime files and static assets for one plugin package. */
|
||||
export function buildPluginNpmRuntime(params: unknown): Promise<{
|
||||
assetBuildCommand: string | null;
|
||||
copiedStaticAssets: string[];
|
||||
runtimeBuildOutputs: string[];
|
||||
packageFiles: unknown[];
|
||||
packagePeerMetadata: {
|
||||
peerDependencies: {
|
||||
openclaw: string;
|
||||
};
|
||||
peerDependenciesMeta: unknown;
|
||||
};
|
||||
repoRoot: string;
|
||||
packageDir: unknown;
|
||||
pluginDir: string;
|
||||
packageJson: unknown;
|
||||
rootPackageJson: unknown;
|
||||
sourceEntries: unknown[];
|
||||
entry: {
|
||||
[k: string]: string;
|
||||
};
|
||||
outDir: string;
|
||||
runtimeExtensions: unknown;
|
||||
runtimeSetupEntry: string | undefined;
|
||||
} | null>;
|
||||
export function buildPluginNpmRuntime(params: unknown): Promise<
|
||||
| (PluginNpmRuntimeBuildPlan & {
|
||||
assetBuildCommand: string | null;
|
||||
copiedStaticAssets: string[];
|
||||
})
|
||||
| null
|
||||
>;
|
||||
export function parseArgs(argv: unknown):
|
||||
| {
|
||||
help: boolean;
|
||||
|
||||
@@ -37,9 +37,17 @@ function isTypeScriptEntry(entry) {
|
||||
return /\.(?:c|m)?ts$/u.test(entry);
|
||||
}
|
||||
|
||||
function toPackageRuntimeEntry(entry) {
|
||||
function resolveRuntimeBuildFormat(packageJson) {
|
||||
return packageJson.openclaw?.build?.runtimeFormat === "cjs" ? "cjs" : "esm";
|
||||
}
|
||||
|
||||
function runtimeBuildExtension(runtimeFormat) {
|
||||
return runtimeFormat === "cjs" ? ".cjs" : ".js";
|
||||
}
|
||||
|
||||
function toPackageRuntimeEntry(entry, runtimeFormat = "esm") {
|
||||
const normalized = normalizePackageEntry(entry).replace(/^\.\//u, "");
|
||||
return `./dist/${normalized.replace(/\.[^.]+$/u, ".js")}`;
|
||||
return `./dist/${normalized.replace(/\.[^.]+$/u, runtimeBuildExtension(runtimeFormat))}`;
|
||||
}
|
||||
|
||||
function collectExternalDependencyNames(packageJson) {
|
||||
@@ -115,11 +123,41 @@ export function listPublishablePluginPackageDirs(params = {}) {
|
||||
|
||||
/** List package-local runtime output files expected from a runtime build plan. */
|
||||
export function listPluginNpmRuntimeBuildOutputs(plan) {
|
||||
const extension = runtimeBuildExtension(plan.runtimeFormat);
|
||||
return Object.keys(plan.entry)
|
||||
.map((entryKey) => `./dist/${entryKey}.js`)
|
||||
.map((entryKey) => `./dist/${entryKey}${extension}`)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function rewriteCommonJsRuntimeSpecifiers(plan) {
|
||||
if (plan.runtimeFormat !== "cjs") {
|
||||
return;
|
||||
}
|
||||
const specifierRewrites = new Map(
|
||||
plan.runtimeBuildOutputs.map((output) => {
|
||||
const cjsSpecifier = output.replace(/^\.\/dist\//u, "./");
|
||||
return [cjsSpecifier.replace(/\.cjs$/u, ".js"), cjsSpecifier];
|
||||
}),
|
||||
);
|
||||
|
||||
for (const output of plan.runtimeBuildOutputs) {
|
||||
const outputPath = path.join(plan.packageDir, output.replace(/^\.\//u, ""));
|
||||
let text = fs.readFileSync(outputPath, "utf8");
|
||||
const original = text;
|
||||
// Source entries stay .js for the root bundled build; package-local CJS
|
||||
// artifacts must point at their generated .cjs sidecars instead.
|
||||
for (const [fromSpecifier, toSpecifier] of specifierRewrites) {
|
||||
text = text.replaceAll(
|
||||
`specifier: ${JSON.stringify(fromSpecifier)}`,
|
||||
`specifier: ${JSON.stringify(toSpecifier)}`,
|
||||
);
|
||||
}
|
||||
if (text !== original) {
|
||||
fs.writeFileSync(outputPath, text, "utf8");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve package `files` entries needed for runtime build outputs and plugin metadata. */
|
||||
function resolvePluginNpmRuntimePackageFiles(plan) {
|
||||
const merged = new Set(
|
||||
@@ -209,6 +247,7 @@ export function resolvePluginNpmRuntimeBuildPlan(params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const runtimeFormat = resolveRuntimeBuildFormat(packageJson);
|
||||
const packageEntries = collectPluginSourceEntries(packageJson).map(normalizePackageEntry);
|
||||
const requiresRuntimeBuild = packageEntries.some(isTypeScriptEntry);
|
||||
if (!requiresRuntimeBuild) {
|
||||
@@ -238,15 +277,16 @@ export function resolvePluginNpmRuntimeBuildPlan(params) {
|
||||
sourceEntries,
|
||||
entry,
|
||||
outDir: path.join(packageDir, "dist"),
|
||||
runtimeFormat,
|
||||
runtimeExtensions: (Array.isArray(packageJson.openclaw?.extensions)
|
||||
? packageJson.openclaw.extensions
|
||||
: []
|
||||
)
|
||||
.map(normalizePackageEntry)
|
||||
.filter(Boolean)
|
||||
.map(toPackageRuntimeEntry),
|
||||
.map((runtimeEntry) => toPackageRuntimeEntry(runtimeEntry, runtimeFormat)),
|
||||
runtimeSetupEntry: normalizePackageEntry(packageJson.openclaw?.setupEntry)
|
||||
? toPackageRuntimeEntry(packageJson.openclaw.setupEntry)
|
||||
? toPackageRuntimeEntry(packageJson.openclaw.setupEntry, runtimeFormat)
|
||||
: undefined,
|
||||
};
|
||||
return {
|
||||
@@ -274,11 +314,13 @@ export async function buildPluginNpmRuntime(params) {
|
||||
},
|
||||
entry: plan.entry,
|
||||
env,
|
||||
fixedExtension: false,
|
||||
fixedExtension: plan.runtimeFormat === "cjs",
|
||||
format: plan.runtimeFormat,
|
||||
logLevel: params.logLevel ?? "info",
|
||||
outDir: plan.outDir,
|
||||
platform: "node",
|
||||
});
|
||||
rewriteCommonJsRuntimeSpecifiers(plan);
|
||||
const assetBuildCommand = runPackageAssetBuild(plan);
|
||||
const missingStaticAssets = listMissingPackageStaticAssetSources(plan);
|
||||
if (missingStaticAssets.length > 0) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Plugin npm runtime build tests validate plugin runtime package builds.
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildPluginNpmRuntime,
|
||||
listPublishablePluginPackageDirs,
|
||||
resolvePluginNpmRuntimeBuildPlan,
|
||||
} from "../scripts/lib/plugin-npm-runtime-build.mjs";
|
||||
@@ -101,11 +103,82 @@ describe("plugin npm runtime build planning", () => {
|
||||
expect(plan.entry["doctor-contract-api"]).toBe(
|
||||
path.join(repoRoot, "extensions", pluginDir, "doctor-contract-api.ts"),
|
||||
);
|
||||
expect(plan.runtimeBuildOutputs).toContain("./dist/doctor-contract-api.js");
|
||||
const extension = plan.runtimeFormat === "cjs" ? ".cjs" : ".js";
|
||||
expect(plan.runtimeBuildOutputs).toContain(`./dist/doctor-contract-api${extension}`);
|
||||
expect(plan.packageFiles).toContain("dist/**");
|
||||
}
|
||||
});
|
||||
|
||||
it("plans msteams startup runtime surfaces as native CommonJS entrypoints", () => {
|
||||
const plan = expectPluginNpmRuntimeBuildPlan(
|
||||
resolvePluginNpmRuntimeBuildPlan({
|
||||
repoRoot,
|
||||
packageDir: path.join(repoRoot, "extensions", "msteams"),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(plan.runtimeFormat).toBe("cjs");
|
||||
expect(plan.runtimeExtensions).toEqual(["./dist/index.cjs"]);
|
||||
expect(plan.runtimeSetupEntry).toBe("./dist/setup-entry.cjs");
|
||||
expect(plan.runtimeBuildOutputs).toEqual(
|
||||
expect.arrayContaining([
|
||||
"./dist/channel-plugin-api.cjs",
|
||||
"./dist/doctor-contract-api.cjs",
|
||||
"./dist/index.cjs",
|
||||
"./dist/runtime-api.cjs",
|
||||
"./dist/secret-contract-api.cjs",
|
||||
"./dist/setup-entry.cjs",
|
||||
"./dist/setup-plugin-api.cjs",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("builds msteams startup runtime surfaces as CommonJS files", async () => {
|
||||
const result = await buildPluginNpmRuntime({
|
||||
repoRoot,
|
||||
packageDir: "extensions/msteams",
|
||||
logLevel: "silent",
|
||||
});
|
||||
const plan = expectPluginNpmRuntimeBuildPlan(result);
|
||||
|
||||
expect(plan.runtimeFormat).toBe("cjs");
|
||||
expect(plan.runtimeExtensions).toEqual(["./dist/index.cjs"]);
|
||||
expect(plan.runtimeSetupEntry).toBe("./dist/setup-entry.cjs");
|
||||
|
||||
const entrypoints = [
|
||||
"dist/index.cjs",
|
||||
"dist/channel-plugin-api.cjs",
|
||||
"dist/runtime-api.cjs",
|
||||
"dist/setup-plugin-api.cjs",
|
||||
"dist/secret-contract-api.cjs",
|
||||
];
|
||||
const missing = entrypoints.filter(
|
||||
(relativePath) => !existsSync(path.join(repoRoot, "extensions/msteams", relativePath)),
|
||||
);
|
||||
expect(missing).toEqual([]);
|
||||
|
||||
for (const relativePath of entrypoints) {
|
||||
const text = readFileSync(path.join(repoRoot, "extensions/msteams", relativePath), "utf8");
|
||||
expect(text).not.toMatch(/^import\s/u);
|
||||
expect(text).toMatch(/(?:require\(|exports\.)/u);
|
||||
}
|
||||
|
||||
const indexText = readFileSync(
|
||||
path.join(repoRoot, "extensions/msteams/dist/index.cjs"),
|
||||
"utf8",
|
||||
);
|
||||
expect(indexText).toContain('specifier: "./channel-plugin-api.cjs"');
|
||||
expect(indexText).toContain('specifier: "./secret-contract-api.cjs"');
|
||||
expect(indexText).toContain('specifier: "./runtime-api.cjs"');
|
||||
|
||||
const setupEntryText = readFileSync(
|
||||
path.join(repoRoot, "extensions/msteams/dist/setup-entry.cjs"),
|
||||
"utf8",
|
||||
);
|
||||
expect(setupEntryText).toContain('specifier: "./setup-plugin-api.cjs"');
|
||||
expect(setupEntryText).toContain('specifier: "./secret-contract-api.cjs"');
|
||||
});
|
||||
|
||||
it("builds Tencent setup metadata for installed-package migrations", () => {
|
||||
const plan = expectPluginNpmRuntimeBuildPlan(
|
||||
resolvePluginNpmRuntimeBuildPlan({
|
||||
|
||||
Reference in New Issue
Block a user