Skip to content

Commit 63fc7f0

Browse files
committed
fix(plugin): replace fail() with expect.unreachable() for vitest compat
1 parent 86b3811 commit 63fc7f0

4 files changed

Lines changed: 31 additions & 15 deletions

File tree

apps/mcp-server/src/plugin/plugin-manifest.schema.spec.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { describe, it, expect } from 'vitest';
12
import { validatePluginManifest, PluginManifestSchemaError } from './plugin-manifest.schema';
23
import type { PluginManifest } from './plugin.types';
34

@@ -123,6 +124,18 @@ describe('PluginManifestSchema', () => {
123124
expect(() => validatePluginManifest(manifest)).toThrow(PluginManifestSchemaError);
124125
});
125126

127+
it('should reject name starting with a digit', () => {
128+
const manifest = { ...validManifest, name: '0plugin' };
129+
130+
expect(() => validatePluginManifest(manifest)).toThrow(PluginManifestSchemaError);
131+
});
132+
133+
it('should reject name with only hyphens', () => {
134+
const manifest = { ...validManifest, name: '---' };
135+
136+
expect(() => validatePluginManifest(manifest)).toThrow(PluginManifestSchemaError);
137+
});
138+
126139
it('should reject empty name', () => {
127140
const manifest = { ...validManifest, name: '' };
128141

@@ -156,7 +169,7 @@ describe('PluginManifestSchema', () => {
156169

157170
try {
158171
validatePluginManifest(manifest);
159-
fail('Expected PluginManifestSchemaError');
172+
expect.unreachable('Expected PluginManifestSchemaError');
160173
} catch (error) {
161174
expect(error).toBeInstanceOf(PluginManifestSchemaError);
162175
expect((error as PluginManifestSchemaError).message).toContain('name');
@@ -166,7 +179,7 @@ describe('PluginManifestSchema', () => {
166179
it('should provide actionable error for missing fields', () => {
167180
try {
168181
validatePluginManifest({});
169-
fail('Expected PluginManifestSchemaError');
182+
expect.unreachable('Expected PluginManifestSchemaError');
170183
} catch (error) {
171184
expect(error).toBeInstanceOf(PluginManifestSchemaError);
172185
expect((error as PluginManifestSchemaError).message).toContain('Invalid plugin manifest');

apps/mcp-server/src/plugin/plugin-manifest.schema.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88

99
import { z } from 'zod';
10-
import type { PluginManifest } from './plugin.types';
1110

1211
// ============================================================================
1312
// Custom Error
@@ -38,7 +37,10 @@ export const PluginManifestSchema = z.object({
3837
name: z
3938
.string()
4039
.min(1)
41-
.regex(/^[a-z0-9-]+$/, 'Plugin name must be lowercase alphanumeric with hyphens only'),
40+
.regex(
41+
/^[a-z][a-z0-9-]*$/,
42+
'Plugin name must start with a letter and contain only lowercase alphanumeric with hyphens',
43+
),
4244
version: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be in semver format (e.g. 1.0.0)'),
4345
description: z.string().min(1),
4446
author: z.string().min(1),
@@ -47,6 +49,12 @@ export const PluginManifestSchema = z.object({
4749
provides: PluginProvidesSchema,
4850
});
4951

52+
// ============================================================================
53+
// Inferred Type (Single Source of Truth)
54+
// ============================================================================
55+
56+
export type PluginManifest = z.infer<typeof PluginManifestSchema>;
57+
5058
// ============================================================================
5159
// Validation Function
5260
// ============================================================================
@@ -62,7 +70,7 @@ export function validatePluginManifest(json: unknown): PluginManifest {
6270
const result = PluginManifestSchema.safeParse(json);
6371

6472
if (result.success) {
65-
return result.data as PluginManifest;
73+
return result.data;
6674
}
6775

6876
const errorMessage = result.error.issues
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { Module } from '@nestjs/common';
22

3+
// Placeholder: will host PluginRegistryService, PluginInstallService
4+
// once install/uninstall commands land (Phase 1 follow-up).
35
@Module({})
46
export class PluginModule {}
Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/**
22
* Plugin Manifest Types
33
*
4-
* TypeScript types for plugin.json manifest files used by community plugins.
4+
* Re-exports Zod-inferred types as the single source of truth.
5+
* Manual interfaces kept for PluginProvides (used independently).
56
*/
67

78
export interface PluginProvides {
@@ -11,12 +12,4 @@ export interface PluginProvides {
1112
checklists?: string[];
1213
}
1314

14-
export interface PluginManifest {
15-
name: string;
16-
version: string;
17-
description: string;
18-
author: string;
19-
tags?: string[];
20-
compatibility?: string;
21-
provides: PluginProvides;
22-
}
15+
export type { PluginManifest } from './plugin-manifest.schema';

0 commit comments

Comments
 (0)