FN-5842: publish plugin SDK subpath export
Expose the plugin SDK as a published CLI subpath while preserving existing workspace behavior. - add a prepack transform helper that injects the ./plugin-sdk and ./package.json exports only in packed manifests - split tsup config into dedicated CLI and plugin-sdk builds so dist/plugin-sdk JS and DTS are emitted with @fusion deps inlined - add coverage for definePlugin/validatePluginManifest behavior and manifest/build export guarantees - document the new published @runfusion/fusion/plugin-sdk surface for plugin authors Files changed: docs/agents.md | 2 + packages/cli/scripts/prepare-publish-manifest.mjs | 61 +++++++++++++++------- packages/cli/src/__tests__/plugin-sdk-export.test.ts| 50 ++++++++++++++++++ packages/cli/tsup.config.ts | 31 +++++++++-- 4 files changed, 121 insertions(+), 23 deletions(-) Fusion-Task-Id: FN-5842 Fusion-Task-Lineage: b6b481ae-aaac-46c0-a6f7-8cc1e9eed1ce
This commit is contained in:
@@ -1,21 +1,9 @@
|
||||
/* global process, URL, console */
|
||||
|
||||
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const mode = process.argv[2];
|
||||
const packageJsonPath = new URL("../package.json", import.meta.url);
|
||||
const backupPath = new URL("../package.json.pack-backup", import.meta.url);
|
||||
|
||||
if (mode === "prepack") {
|
||||
if (existsSync(backupPath)) {
|
||||
// Clean up stale backup from interrupted runs.
|
||||
unlinkSync(backupPath);
|
||||
}
|
||||
|
||||
const original = readFileSync(packageJsonPath, "utf8");
|
||||
writeFileSync(backupPath, original, "utf8");
|
||||
|
||||
const pkg = JSON.parse(original);
|
||||
export function applyPrepackTransform(pkg) {
|
||||
const devDependencies = { ...(pkg.devDependencies || {}) };
|
||||
delete devDependencies["@fusion/core"];
|
||||
delete devDependencies["@fusion/dashboard"];
|
||||
@@ -24,12 +12,42 @@ if (mode === "prepack") {
|
||||
delete devDependencies["@fusion/pi-llama-cpp"];
|
||||
delete devDependencies["@fusion-plugin-examples/roadmap"];
|
||||
|
||||
pkg.devDependencies = devDependencies;
|
||||
writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
|
||||
process.exit(0);
|
||||
return {
|
||||
...pkg,
|
||||
devDependencies,
|
||||
// Inject exports only for the packed manifest so workspace/dev resolution
|
||||
// remains unchanged (postpack restore reverts this file to original state).
|
||||
exports: {
|
||||
...(pkg.exports || {}),
|
||||
"./plugin-sdk": {
|
||||
types: "./dist/plugin-sdk/index.d.ts",
|
||||
import: "./dist/plugin-sdk/index.js",
|
||||
},
|
||||
"./package.json": "./package.json",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (mode === "postpack") {
|
||||
function run() {
|
||||
const mode = process.argv[2];
|
||||
const packageJsonPath = new URL("../package.json", import.meta.url);
|
||||
const backupPath = new URL("../package.json.pack-backup", import.meta.url);
|
||||
|
||||
if (mode === "prepack") {
|
||||
if (existsSync(backupPath)) {
|
||||
unlinkSync(backupPath);
|
||||
}
|
||||
|
||||
const original = readFileSync(packageJsonPath, "utf8");
|
||||
writeFileSync(backupPath, original, "utf8");
|
||||
|
||||
const pkg = JSON.parse(original);
|
||||
const transformed = applyPrepackTransform(pkg);
|
||||
writeFileSync(packageJsonPath, `${JSON.stringify(transformed, null, 2)}\n`, "utf8");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (mode === "postpack") {
|
||||
if (!existsSync(backupPath)) {
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -38,7 +56,12 @@ if (mode === "postpack") {
|
||||
writeFileSync(packageJsonPath, backup, "utf8");
|
||||
unlinkSync(backupPath);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.error("Usage: node ./scripts/prepare-publish-manifest.mjs <prepack|postpack>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.error("Usage: node ./scripts/prepare-publish-manifest.mjs <prepack|postpack>");
|
||||
process.exit(1);
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
run();
|
||||
}
|
||||
|
||||
50
packages/cli/src/__tests__/plugin-sdk-export.test.ts
Normal file
50
packages/cli/src/__tests__/plugin-sdk-export.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { definePlugin, validatePluginManifest } from "@fusion/plugin-sdk";
|
||||
import { applyPrepackTransform } from "../../scripts/prepare-publish-manifest.mjs";
|
||||
|
||||
const workspaceRoot = join(__dirname, "..", "..", "..", "..");
|
||||
|
||||
describe("plugin-sdk export surface", () => {
|
||||
it("keeps definePlugin as identity and validates manifests", () => {
|
||||
const plugin = { manifest: { id: "demo-plugin", name: "Demo", version: "1.0.0" } } as any;
|
||||
expect(definePlugin(plugin)).toBe(plugin);
|
||||
|
||||
expect(validatePluginManifest(plugin.manifest)).toEqual({ valid: true, errors: [] });
|
||||
expect(validatePluginManifest({ id: "Bad_ID", name: "", version: "nope" }).valid).toBe(false);
|
||||
});
|
||||
|
||||
it("injects plugin-sdk subpath export into prepack manifest", () => {
|
||||
const pkgPath = join(workspaceRoot, "packages", "cli", "package.json");
|
||||
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
||||
const transformed = applyPrepackTransform(pkg);
|
||||
|
||||
expect(transformed.exports["./plugin-sdk"]).toEqual({
|
||||
types: "./dist/plugin-sdk/index.d.ts",
|
||||
import: "./dist/plugin-sdk/index.js",
|
||||
});
|
||||
expect(transformed.exports["./package.json"]).toBe("./package.json");
|
||||
expect(transformed.bin).toEqual(pkg.bin);
|
||||
expect(transformed.pi).toEqual(pkg.pi);
|
||||
});
|
||||
|
||||
it("declares plugin-sdk tsup build entry with dts and fusion inlining", () => {
|
||||
const tsupPath = join(workspaceRoot, "packages", "cli", "tsup.config.ts");
|
||||
const tsupRaw = readFileSync(tsupPath, "utf-8");
|
||||
|
||||
expect(tsupRaw).toContain('"plugin-sdk/index"');
|
||||
expect(tsupRaw).toContain('"..", "plugin-sdk", "src", "index.ts"');
|
||||
expect(tsupRaw).toContain("dts:");
|
||||
expect(tsupRaw).toContain("/^@fusion\\//");
|
||||
});
|
||||
|
||||
it("has no @fusion runtime specifiers in built plugin-sdk artifact when present", () => {
|
||||
const distPath = join(workspaceRoot, "packages", "cli", "dist", "plugin-sdk", "index.js");
|
||||
if (!existsSync(distPath)) {
|
||||
return;
|
||||
}
|
||||
const built = readFileSync(distPath, "utf-8");
|
||||
expect(built.includes("@fusion/")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -126,12 +126,14 @@ async function bundlePluginEntry({ pluginId, srcDir, destDir, withMcpAsset = fal
|
||||
console.log(`Bundled plugin ${pluginId} to dist/plugins/${pluginId}/bundled.js`);
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
const pluginSdkEntry = join(__dirname, "..", "plugin-sdk", "src", "index.ts");
|
||||
|
||||
const cliBuildConfig = {
|
||||
entry: ["src/bin.ts", "src/extension.ts"],
|
||||
format: ["esm"],
|
||||
platform: "node",
|
||||
target: "node22",
|
||||
esbuildOptions(options) {
|
||||
esbuildOptions(options: { conditions?: string[] }) {
|
||||
options.conditions = [...(options.conditions || []), "source"];
|
||||
},
|
||||
noExternal: [/^@fusion\//, /^@fusion-plugin-examples\//],
|
||||
@@ -146,7 +148,9 @@ export default defineConfig({
|
||||
"cpu-features",
|
||||
],
|
||||
splitting: false,
|
||||
clean: true,
|
||||
// Keep clean disabled so the dedicated plugin-sdk tsup config can emit into
|
||||
// dist/plugin-sdk without being wiped between config executions.
|
||||
clean: false,
|
||||
removeNodeProtocol: false,
|
||||
banner: {
|
||||
js: 'import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);',
|
||||
@@ -293,4 +297,23 @@ export default defineConfig({
|
||||
`WARNING: Dashboard client assets not found at ${dashboardClientSrc}. Generated minimal stub at ${join(dashboardClientDest, "index.html")}.`,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const pluginSdkBuildConfig = {
|
||||
entry: { "plugin-sdk/index": pluginSdkEntry },
|
||||
format: ["esm"],
|
||||
platform: "node",
|
||||
target: "node22",
|
||||
tsconfig: join(__dirname, "..", "plugin-sdk", "tsconfig.json"),
|
||||
dts: {
|
||||
resolve: true,
|
||||
compilerOptions: {
|
||||
rootDir: join(__dirname, ".."),
|
||||
},
|
||||
},
|
||||
noExternal: [/^@fusion\//],
|
||||
clean: false,
|
||||
outDir: "dist",
|
||||
};
|
||||
|
||||
export default defineConfig([cliBuildConfig, pluginSdkBuildConfig]);
|
||||
|
||||
Reference in New Issue
Block a user