diff --git a/packages/plugin-protocol/src/__tests__/manifest.test.ts b/packages/plugin-protocol/src/__tests__/manifest.test.ts index d8c824d..bc9141c 100644 --- a/packages/plugin-protocol/src/__tests__/manifest.test.ts +++ b/packages/plugin-protocol/src/__tests__/manifest.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { + GHOST_MANIFEST_SUMMARY_MAX_CHARS, GHOST_MANIFEST_SCHEMA_VERSION, GHOST_OAUTH_SCOPES_MAX, compareCindyVersions, @@ -64,6 +65,27 @@ describe('Ghost manifest contract', () => { expect(validateGhostManifest({ ...validManifest, schemaVersion: 1 }).ok).toBe(false); }); + it('shares the 300-character limit across description and whenToUse', () => { + expect(GHOST_MANIFEST_SUMMARY_MAX_CHARS).toBe(300); + for (const field of ['description', 'whenToUse'] as const) { + expect( + validateGhostManifest({ + ...validManifest, + [field]: 'x'.repeat(GHOST_MANIFEST_SUMMARY_MAX_CHARS), + }).ok, + ).toBe(true); + expect( + validateGhostManifest({ + ...validManifest, + [field]: 'x'.repeat(GHOST_MANIFEST_SUMMARY_MAX_CHARS + 1), + }), + ).toEqual({ + ok: false, + reason: `${field} 必须是 1–${GHOST_MANIFEST_SUMMARY_MAX_CHARS} 字符的非空字符串`, + }); + } + }); + it('accepts an oidc-token secret without a settings page and normalizes its exact host', () => { const result = validateGhostManifest({ ...validManifest, diff --git a/packages/plugin-protocol/src/manifest.ts b/packages/plugin-protocol/src/manifest.ts index 97251eb..0c7511c 100644 --- a/packages/plugin-protocol/src/manifest.ts +++ b/packages/plugin-protocol/src/manifest.ts @@ -7,6 +7,9 @@ export const CINDY_FILE_EXT = '.cindy'; /** ghost.json 格式版本;与 Plugin HTTP API envelope 版本独立演进。 */ export const GHOST_MANIFEST_SCHEMA_VERSION = 2 as const; +/** ghost.json 的 description / whenToUse 字符上限。 */ +export const GHOST_MANIFEST_SUMMARY_MAX_CHARS = 300; + /** Cindy host locales supported by Plugin manifest resources. */ export const GHOST_LOCALES = ['zh-CN', 'en', 'ja', 'ko'] as const; export type GhostLocale = (typeof GHOST_LOCALES)[number]; @@ -1152,17 +1155,23 @@ export function validateGhostManifest(raw: unknown): ManifestValidation { raw.description !== undefined && (typeof raw.description !== 'string' || raw.description.trim().length === 0 || - raw.description.length > 300) + raw.description.length > GHOST_MANIFEST_SUMMARY_MAX_CHARS) ) { - return { ok: false, reason: 'description 必须是 1–300 字符的非空字符串' }; + return { + ok: false, + reason: `description 必须是 1–${GHOST_MANIFEST_SUMMARY_MAX_CHARS} 字符的非空字符串`, + }; } if ( raw.whenToUse !== undefined && (typeof raw.whenToUse !== 'string' || raw.whenToUse.trim().length === 0 || - raw.whenToUse.length > 300) + raw.whenToUse.length > GHOST_MANIFEST_SUMMARY_MAX_CHARS) ) { - return { ok: false, reason: 'whenToUse 必须是 1–300 字符的非空字符串' }; + return { + ok: false, + reason: `whenToUse 必须是 1–${GHOST_MANIFEST_SUMMARY_MAX_CHARS} 字符的非空字符串`, + }; } if (raw.icon !== undefined) { if (!isSafeGhostRelativePath(raw.icon)) {