mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 19:17:28 +00:00
refactor(plugins): name shared catalog and manifest schema parts (#30612)
* plugins: name the shared catalog and manifest schema parts Pure refactor. Marketplace entry/header fields and the duplicate-category and duplicate-id checks become named pieces, the bounded catalog JSON read is split from schema parsing, and the v1 manifest object is named apart from its refinement, so a second catalog file and manifest version can reuse them. * plugins: share the plugin tree-identity check Pure refactor. Install integrity and instructional-content checks each inlined the full-hash-or-legacy-prefix comparison; both now call pluginTreeHashMatches. The tree limits are exported for reuse.
This commit is contained in:
@@ -73,62 +73,61 @@ const eventContributionSchema = z.object({
|
||||
on: z.enum(PLUGIN_EVENT_NAMES)
|
||||
})
|
||||
|
||||
export const pluginManifestSchema = z
|
||||
.object({
|
||||
manifestVersion: z.literal(1),
|
||||
id: pluginIdSchema,
|
||||
/** Publisher slug; canonical identity is `<publisher>.<id>` — bare-id
|
||||
* global uniqueness is unverifiable without a registry. */
|
||||
publisher: pluginIdSchema,
|
||||
name: z.string().min(1).max(256),
|
||||
version: z.string().regex(SEMVER_RE, 'must be semver'),
|
||||
description: z.string().max(4096).optional(),
|
||||
author: z
|
||||
.object({ name: z.string().min(1).max(256), url: z.string().max(2048).optional() })
|
||||
.optional(),
|
||||
repository: z.string().max(2048).optional(),
|
||||
icon: pluginRelativePathSchema.optional(),
|
||||
/** Minimum host version gate; the host refuses to load below it. */
|
||||
engines: z.object({ orca: orcaEngineRangeSchema }),
|
||||
/** Host-API major version this plugin targets. */
|
||||
pluginApi: z.literal(1),
|
||||
/** Node entry executed inside the out-of-process plugin worker. */
|
||||
main: pluginRelativePathSchema.optional(),
|
||||
contributes: z
|
||||
.object({
|
||||
panels: z.array(panelContributionSchema).max(PLUGIN_PANEL_LIMIT).default([]),
|
||||
commands: z.array(commandContributionSchema).max(PLUGIN_COMMAND_LIMIT).default([]),
|
||||
events: z.array(eventContributionSchema).max(PLUGIN_EVENT_SUBSCRIPTION_LIMIT).default([]),
|
||||
languagePacks: z
|
||||
.array(pluginLanguagePackContributionSchema)
|
||||
.max(PLUGIN_LANGUAGE_PACK_LIMIT)
|
||||
.default([]),
|
||||
keybindings: z
|
||||
.array(pluginKeybindingContributionSchema)
|
||||
.max(PLUGIN_KEYBINDING_LIMIT)
|
||||
.default([]),
|
||||
vmRecipes: z
|
||||
.array(pluginVmRecipeContributionSchema)
|
||||
.max(PLUGIN_VM_RECIPE_LIMIT)
|
||||
.default([]),
|
||||
agents: z
|
||||
.array(pluginAgentProfileContributionSchema)
|
||||
.max(PLUGIN_AGENT_PROFILE_LIMIT)
|
||||
.default([])
|
||||
})
|
||||
.strict()
|
||||
.default(() => ({
|
||||
panels: [],
|
||||
commands: [],
|
||||
events: [],
|
||||
languagePacks: [],
|
||||
keybindings: [],
|
||||
vmRecipes: [],
|
||||
agents: []
|
||||
})),
|
||||
capabilities: z.array(pluginCapabilitySchema).max(32).default([])
|
||||
})
|
||||
.superRefine(validatePluginManifestContributions)
|
||||
const pluginManifestV1Object = z.object({
|
||||
manifestVersion: z.literal(1),
|
||||
id: pluginIdSchema,
|
||||
/** Publisher slug; canonical identity is `<publisher>.<id>` — bare-id
|
||||
* global uniqueness is unverifiable without a registry. */
|
||||
publisher: pluginIdSchema,
|
||||
name: z.string().min(1).max(256),
|
||||
version: z.string().regex(SEMVER_RE, 'must be semver'),
|
||||
description: z.string().max(4096).optional(),
|
||||
author: z
|
||||
.object({ name: z.string().min(1).max(256), url: z.string().max(2048).optional() })
|
||||
.optional(),
|
||||
repository: z.string().max(2048).optional(),
|
||||
icon: pluginRelativePathSchema.optional(),
|
||||
/** Minimum host version gate; the host refuses to load below it. */
|
||||
engines: z.object({ orca: orcaEngineRangeSchema }),
|
||||
/** Host-API major version this plugin targets. */
|
||||
pluginApi: z.literal(1),
|
||||
/** Node entry executed inside the out-of-process plugin worker. */
|
||||
main: pluginRelativePathSchema.optional(),
|
||||
contributes: z
|
||||
.object({
|
||||
panels: z.array(panelContributionSchema).max(PLUGIN_PANEL_LIMIT).default([]),
|
||||
commands: z.array(commandContributionSchema).max(PLUGIN_COMMAND_LIMIT).default([]),
|
||||
events: z.array(eventContributionSchema).max(PLUGIN_EVENT_SUBSCRIPTION_LIMIT).default([]),
|
||||
languagePacks: z
|
||||
.array(pluginLanguagePackContributionSchema)
|
||||
.max(PLUGIN_LANGUAGE_PACK_LIMIT)
|
||||
.default([]),
|
||||
keybindings: z
|
||||
.array(pluginKeybindingContributionSchema)
|
||||
.max(PLUGIN_KEYBINDING_LIMIT)
|
||||
.default([]),
|
||||
vmRecipes: z.array(pluginVmRecipeContributionSchema).max(PLUGIN_VM_RECIPE_LIMIT).default([]),
|
||||
agents: z
|
||||
.array(pluginAgentProfileContributionSchema)
|
||||
.max(PLUGIN_AGENT_PROFILE_LIMIT)
|
||||
.default([])
|
||||
})
|
||||
.strict()
|
||||
.default(() => ({
|
||||
panels: [],
|
||||
commands: [],
|
||||
events: [],
|
||||
languagePacks: [],
|
||||
keybindings: [],
|
||||
vmRecipes: [],
|
||||
agents: []
|
||||
})),
|
||||
capabilities: z.array(pluginCapabilitySchema).max(32).default([])
|
||||
})
|
||||
|
||||
export const pluginManifestSchema = pluginManifestV1Object.superRefine(
|
||||
validatePluginManifestContributions
|
||||
)
|
||||
|
||||
export type PluginManifest = z.infer<typeof pluginManifestSchema>
|
||||
export type PluginPanelContribution = z.infer<typeof panelContributionSchema>
|
||||
|
||||
@@ -53,50 +53,63 @@ export const pluginMarketplaceGitSourceSchema = z.strictObject({
|
||||
ref: z.string().trim().min(1).max(4_096)
|
||||
})
|
||||
|
||||
export const pluginMarketplaceEntrySchema = z
|
||||
.strictObject({
|
||||
/** Canonical `<publisher>.<id>` identity expected in the source manifest. */
|
||||
id: z.string().refine(isQualifiedPluginKey, 'invalid qualified plugin key'),
|
||||
source: pluginMarketplaceGitSourceSchema,
|
||||
description: z.string().min(1).max(4_096).optional(),
|
||||
categories: z
|
||||
.array(marketplaceCategorySchema)
|
||||
.max(PLUGIN_MARKETPLACE_CATEGORY_LIMIT)
|
||||
.default([])
|
||||
})
|
||||
.superRefine((entry, context) => {
|
||||
const seen = new Set<string>()
|
||||
for (const [index, category] of entry.categories.entries()) {
|
||||
if (seen.has(category)) {
|
||||
context.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['categories', index],
|
||||
message: `duplicate category: ${category}`
|
||||
})
|
||||
}
|
||||
seen.add(category)
|
||||
const marketplaceEntryFields = {
|
||||
/** Canonical `<publisher>.<id>` identity expected in the source manifest. */
|
||||
id: z.string().refine(isQualifiedPluginKey, 'invalid qualified plugin key'),
|
||||
source: pluginMarketplaceGitSourceSchema,
|
||||
description: z.string().min(1).max(4_096).optional(),
|
||||
categories: z.array(marketplaceCategorySchema).max(PLUGIN_MARKETPLACE_CATEGORY_LIMIT).default([])
|
||||
}
|
||||
|
||||
function rejectDuplicateCategories(
|
||||
entry: { categories: string[] },
|
||||
context: z.RefinementCtx
|
||||
): void {
|
||||
const seen = new Set<string>()
|
||||
for (const [index, category] of entry.categories.entries()) {
|
||||
if (seen.has(category)) {
|
||||
context.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['categories', index],
|
||||
message: `duplicate category: ${category}`
|
||||
})
|
||||
}
|
||||
})
|
||||
seen.add(category)
|
||||
}
|
||||
}
|
||||
|
||||
function rejectDuplicatePluginIds(
|
||||
marketplace: { plugins: { id: string }[] },
|
||||
context: z.RefinementCtx
|
||||
): void {
|
||||
const seen = new Set<string>()
|
||||
for (const [index, plugin] of marketplace.plugins.entries()) {
|
||||
if (seen.has(plugin.id)) {
|
||||
context.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['plugins', index, 'id'],
|
||||
message: `duplicate plugin id: ${plugin.id}`
|
||||
})
|
||||
}
|
||||
seen.add(plugin.id)
|
||||
}
|
||||
}
|
||||
|
||||
export const pluginMarketplaceEntrySchema = z
|
||||
.strictObject(marketplaceEntryFields)
|
||||
.superRefine(rejectDuplicateCategories)
|
||||
|
||||
const marketplaceHeaderFields = {
|
||||
name: z.string().min(1).max(256),
|
||||
owner: marketplaceOwnerSchema
|
||||
}
|
||||
|
||||
export const pluginMarketplaceSchema = z
|
||||
.strictObject({
|
||||
name: z.string().min(1).max(256),
|
||||
owner: marketplaceOwnerSchema,
|
||||
...marketplaceHeaderFields,
|
||||
plugins: z.array(pluginMarketplaceEntrySchema).max(PLUGIN_MARKETPLACE_ENTRY_LIMIT)
|
||||
})
|
||||
.superRefine((marketplace, context) => {
|
||||
const seen = new Set<string>()
|
||||
for (const [index, plugin] of marketplace.plugins.entries()) {
|
||||
if (seen.has(plugin.id)) {
|
||||
context.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['plugins', index, 'id'],
|
||||
message: `duplicate plugin id: ${plugin.id}`
|
||||
})
|
||||
}
|
||||
seen.add(plugin.id)
|
||||
}
|
||||
})
|
||||
.superRefine(rejectDuplicatePluginIds)
|
||||
|
||||
/** Host-derived trust metadata. Marketplace JSON cannot self-award either bit. */
|
||||
export const pluginMarketplaceTrustMetadataSchema = z
|
||||
|
||||
Reference in New Issue
Block a user