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
This commit is contained in:
16
.changeset/FN-6351-plugin-scaffold-devdeps.md
Normal file
16
.changeset/FN-6351-plugin-scaffold-devdeps.md
Normal file
@@ -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
|
||||
```
|
||||
@@ -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<string, string>;
|
||||
};
|
||||
|
||||
@@ -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<string, string>;
|
||||
};
|
||||
expect(packageJson.name).toBe("@acme/fusion-plugin-scoped-plugin");
|
||||
expect(Object.keys(packageJson.devDependencies)).toEqual(standaloneDevDependencyKeys);
|
||||
});
|
||||
|
||||
it("rejects invalid plugin names", async () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user