From 30e747bebc8293d79a44599f7ad177ea27fee8ad Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 13 Jun 2026 04:44:51 -0700 Subject: [PATCH] FN-6351: add plugin scaffold dev toolchain dependencies Ensure standalone plugin scaffolds declare the tools required by their generated config and scripts. - Add @types/node, TypeScript, and Vitest dev dependencies to generated standalone plugin package manifests. - Cover scaffold dependency ranges, script/tool alignment, scoped plugin names, and tsconfig types in plugin scaffold tests. - Add a patch changeset documenting the scaffold dependency fix. Files changed: .changeset/FN-6351-plugin-scaffold-devdeps.md | 16 +++++++++++++++ packages/cli/src/__tests__/plugin-scaffold.test.ts | 24 ++++++++++++++++++++-- packages/cli/src/commands/plugin-scaffold.ts | 6 ++++++ 3 files changed, 44 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-6351 Fusion-Task-Lineage: 4134d7cb-b493-49a7-abe1-154a875aec0e --- .changeset/FN-6351-plugin-scaffold-devdeps.md | 16 +++++++++++++ .../cli/src/__tests__/plugin-scaffold.test.ts | 24 +++++++++++++++++-- packages/cli/src/commands/plugin-scaffold.ts | 6 +++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 .changeset/FN-6351-plugin-scaffold-devdeps.md diff --git a/.changeset/FN-6351-plugin-scaffold-devdeps.md b/.changeset/FN-6351-plugin-scaffold-devdeps.md new file mode 100644 index 0000000000..60bba2222c --- /dev/null +++ b/.changeset/FN-6351-plugin-scaffold-devdeps.md @@ -0,0 +1,16 @@ +--- +"@runfusion/fusion": patch +--- + +Standalone plugin scaffolds now declare the dev toolchain they generate scripts and config for: `@types/node`, `vitest`, and `typescript`. This lets projects created with `fn plugin new` install, build, test, and load through `fn plugin dev . --once` via the documented external-author path without relying on transitive or hoisted dependencies. + +Manual spot-check for release validation: + +```sh +npx @runfusion/fusion@latest plugin new proof-point-plugin +cd proof-point-plugin +pnpm install +pnpm build +pnpm test +fn plugin dev . --once +``` diff --git a/packages/cli/src/__tests__/plugin-scaffold.test.ts b/packages/cli/src/__tests__/plugin-scaffold.test.ts index e9ce66de41..3e16a3849e 100644 --- a/packages/cli/src/__tests__/plugin-scaffold.test.ts +++ b/packages/cli/src/__tests__/plugin-scaffold.test.ts @@ -8,6 +8,13 @@ import { runPluginCreate, runPluginNew } from "../commands/plugin-scaffold.js"; describe("plugin-scaffold", () => { const tmpBase = join(tmpdir(), `fn-scaffold-${Date.now()}-${Math.random().toString(36).slice(2)}`); + const standaloneDevDependencyKeys = [ + "@runfusion/fusion", + "@types/node", + "typescript", + "vitest", + ]; + const caretRangePattern = /^\^\d+\.\d+\.\d+$/; beforeEach(() => { mkdirSync(tmpBase, { recursive: true }); @@ -64,6 +71,7 @@ describe("plugin-scaffold", () => { private?: boolean; keywords: string[]; exports: { ".": { types: string; import: string } }; + scripts: { build: string; test: string }; devDependencies: Record; }; @@ -72,8 +80,10 @@ describe("plugin-scaffold", () => { expect(packageJson.private).toBeUndefined(); expect(packageJson.exports["."].types).toBe("./dist/index.d.ts"); expect(packageJson.exports["."].import).toBe("./dist/index.js"); - expect(Object.keys(packageJson.devDependencies)).toEqual(["@runfusion/fusion"]); - expect(packageJson.devDependencies["@runfusion/fusion"]).toMatch(/^\^\d+\.\d+\.\d+$/); + expect(Object.keys(packageJson.devDependencies)).toEqual(standaloneDevDependencyKeys); + for (const dependencyName of standaloneDevDependencyKeys) { + expect(packageJson.devDependencies[dependencyName]).toMatch(caretRangePattern); + } const packageContents = readFileSync(join(outputDir, "package.json"), "utf-8"); const indexContents = readFileSync(join(outputDir, "src/index.ts"), "utf-8"); @@ -87,8 +97,16 @@ describe("plugin-scaffold", () => { const tsconfig = JSON.parse(readFileSync(join(outputDir, "tsconfig.json"), "utf-8")) as { extends?: string; + compilerOptions: { types?: string[] }; }; expect(tsconfig.extends).toBeUndefined(); + for (const typeName of tsconfig.compilerOptions.types ?? []) { + expect(packageJson.devDependencies[`@types/${typeName}`]).toBeDefined(); + } + expect(packageJson.scripts.test.split(/\s+/)[0]).toBe("vitest"); + expect(packageJson.devDependencies.vitest).toBeDefined(); + expect(packageJson.scripts.build.split(/\s+/)[0]).toBe("tsc"); + expect(packageJson.devDependencies.typescript).toBeDefined(); }); it("supports scoped package names", async () => { @@ -96,8 +114,10 @@ describe("plugin-scaffold", () => { await runPluginNew("scoped-plugin", { output: outputDir, scope: "acme" }); const packageJson = JSON.parse(readFileSync(join(outputDir, "package.json"), "utf-8")) as { name: string; + devDependencies: Record; }; expect(packageJson.name).toBe("@acme/fusion-plugin-scoped-plugin"); + expect(Object.keys(packageJson.devDependencies)).toEqual(standaloneDevDependencyKeys); }); it("rejects invalid plugin names", async () => { diff --git a/packages/cli/src/commands/plugin-scaffold.ts b/packages/cli/src/commands/plugin-scaffold.ts index bbd2210fdd..3d102f9c84 100644 --- a/packages/cli/src/commands/plugin-scaffold.ts +++ b/packages/cli/src/commands/plugin-scaffold.ts @@ -12,6 +12,9 @@ import { fileURLToPath } from "node:url"; // Valid plugin name pattern: kebab-case const PLUGIN_NAME_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/; const DEFAULT_RUNFUSION_VERSION = "0.39.0"; +const SCAFFOLD_TYPES_NODE_VERSION = "^22.0.0"; +const SCAFFOLD_VITEST_VERSION = "^4.1.0"; +const SCAFFOLD_TYPESCRIPT_VERSION = "^5.7.0"; /** * Convert a kebab-case string to Title Case @@ -167,6 +170,9 @@ function generateStandalonePackageJson(name: string, scope?: string): string { }, devDependencies: { "@runfusion/fusion": resolveFusionCaretVersion(), + "@types/node": SCAFFOLD_TYPES_NODE_VERSION, + typescript: SCAFFOLD_TYPESCRIPT_VERSION, + vitest: SCAFFOLD_VITEST_VERSION, }, }, null,