fix: 原生支持插件包manifest契约
This commit is contained in:
parent
9314b303fc
commit
3a941d8a6d
@ -597,18 +597,21 @@ export const parseQqbotPluginManifest = (
|
||||
]);
|
||||
}
|
||||
|
||||
const pluginKey = getString(manifestLike, 'pluginKey') || '';
|
||||
const pluginKey =
|
||||
getString(manifestLike, 'key') || getString(manifestLike, 'pluginKey') || '';
|
||||
const pluginKeyPath = getString(manifestLike, 'key') ? 'key' : 'pluginKey';
|
||||
if (!pluginKeyPattern.test(pluginKey)) {
|
||||
pushIssue(
|
||||
issues,
|
||||
'INVALID_PLUGIN_KEY',
|
||||
'pluginKey',
|
||||
pluginKeyPath,
|
||||
'Plugin key must be lower-case kebab-case.',
|
||||
);
|
||||
}
|
||||
|
||||
const version = getString(manifestLike, 'version') || '';
|
||||
const minApiSdkVersion = getString(manifestLike, 'minApiSdkVersion') || '';
|
||||
const minApiSdkVersion =
|
||||
getString(manifestLike, 'minApiSdkVersion') || '1.0.0';
|
||||
requireSemver(version, 'version', issues);
|
||||
requireSemver(minApiSdkVersion, 'minApiSdkVersion', issues);
|
||||
|
||||
@ -633,6 +636,7 @@ export const parseQqbotPluginManifest = (
|
||||
'permissions',
|
||||
issues,
|
||||
),
|
||||
key: pluginKey,
|
||||
pluginKey,
|
||||
runtime: parseRuntime(manifestLike, issues),
|
||||
tasks: parseTasks(manifestLike, issues),
|
||||
|
||||
@ -13,6 +13,7 @@ export const QQBOT_PLUGIN_ALLOWED_PERMISSIONS = [
|
||||
export const QQBOT_PLUGIN_WORKER_TYPES = [
|
||||
'child-process',
|
||||
'node-worker',
|
||||
'thread',
|
||||
] as const;
|
||||
|
||||
export type QqbotPluginPermission =
|
||||
@ -85,6 +86,7 @@ export type QqbotPluginManifest = {
|
||||
name: string;
|
||||
operations: QqbotPluginOperationManifest[];
|
||||
permissions: QqbotPluginPermission[];
|
||||
key: string;
|
||||
pluginKey: string;
|
||||
runtime: QqbotPluginRuntimeManifest;
|
||||
tasks: QqbotPluginTaskManifest[];
|
||||
|
||||
@ -2,10 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import {
|
||||
parseQqbotPluginManifest,
|
||||
type QqbotPluginManifest,
|
||||
} from '@/modules/qqbot/plugin-platform/domain/manifest';
|
||||
import { parseQqbotPluginManifest } from '@/modules/qqbot/plugin-platform/domain/manifest';
|
||||
|
||||
import { QqbotPluginPackagePathPolicyService } from './plugin-package-path-policy.service';
|
||||
import type { QqbotPluginPackageDescriptor } from './plugin-package.types';
|
||||
@ -62,16 +59,15 @@ export class QqbotPluginPackageSourceService {
|
||||
controlledPackageRoot,
|
||||
entry,
|
||||
);
|
||||
const manifest = parseQqbotPluginManifest(
|
||||
this.normalizeManifestForCurrentParser(manifestLike),
|
||||
{ pluginRoot: controlledPackageRoot },
|
||||
);
|
||||
const pluginKey = manifest.pluginKey;
|
||||
const manifest = parseQqbotPluginManifest(manifestLike, {
|
||||
pluginRoot: controlledPackageRoot,
|
||||
});
|
||||
const pluginKey = manifest.key;
|
||||
|
||||
return {
|
||||
entry: manifest.entry,
|
||||
entryFile,
|
||||
manifest: this.attachPackageKeyAlias(manifest, pluginKey),
|
||||
manifest,
|
||||
packageRoot: controlledPackageRoot,
|
||||
pluginKey,
|
||||
};
|
||||
@ -98,65 +94,6 @@ export class QqbotPluginPackageSourceService {
|
||||
return typeof manifestLike.entry === 'string' ? manifestLike.entry : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts planned package manifest aliases to the parser contract currently used by the repo.
|
||||
* @param manifestLike - Parsed `plugin.json` content from a QQBot plugin package.
|
||||
* @returns Parser input with the package key and worker type normalized for this codebase.
|
||||
*/
|
||||
private normalizeManifestForCurrentParser(manifestLike: unknown): unknown {
|
||||
if (!this.isRecord(manifestLike)) return manifestLike;
|
||||
|
||||
const normalized: Record<string, unknown> = {
|
||||
minApiSdkVersion: '1.0.0',
|
||||
...manifestLike,
|
||||
};
|
||||
const packageKey = this.readPackageKey(manifestLike);
|
||||
|
||||
if (!this.readString(normalized.pluginKey) && packageKey) {
|
||||
normalized.pluginKey = packageKey;
|
||||
}
|
||||
if (this.isRecord(manifestLike.runtime)) {
|
||||
normalized.runtime = {
|
||||
...manifestLike.runtime,
|
||||
workerType:
|
||||
manifestLike.runtime.workerType === 'thread'
|
||||
? 'node-worker'
|
||||
: manifestLike.runtime.workerType,
|
||||
};
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the planned `manifest.key` alias while preserving the current manifest parser type.
|
||||
* @param manifest - Parsed manifest whose `pluginKey` identifies the package.
|
||||
* @param pluginKey - Package key exposed to descriptor consumers and future generic workers.
|
||||
* @returns Manifest object with a runtime `key` alias for descriptor compatibility.
|
||||
*/
|
||||
private attachPackageKeyAlias(
|
||||
manifest: QqbotPluginManifest,
|
||||
pluginKey: string,
|
||||
): QqbotPluginManifest {
|
||||
return {
|
||||
...manifest,
|
||||
key: pluginKey,
|
||||
} as QqbotPluginManifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the package key from either the planned `key` field or the current `pluginKey` field.
|
||||
* @param manifestLike - Parsed `plugin.json` object that declares the package identity.
|
||||
* @returns Non-empty package key when declared.
|
||||
*/
|
||||
private readPackageKey(manifestLike: Record<string, unknown>): string {
|
||||
return (
|
||||
this.readString(manifestLike.key) ||
|
||||
this.readString(manifestLike.pluginKey) ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrows JSON values to object records before reading manifest fields.
|
||||
* @param value - Parsed JSON value from a QQBot plugin package manifest.
|
||||
@ -165,15 +102,4 @@ export class QqbotPluginPackageSourceService {
|
||||
private isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes string manifest fields used by package discovery.
|
||||
* @param value - Raw JSON field value read from `plugin.json`.
|
||||
* @returns Trimmed string when the field is non-empty.
|
||||
*/
|
||||
private readString(value: unknown): string | undefined {
|
||||
return typeof value === 'string' && value.trim()
|
||||
? value.trim()
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +176,23 @@ describe('QQBot plugin manifest contract', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('accepts package key and thread worker type without platform transfer shims', () => {
|
||||
const validManifest = createValidManifest();
|
||||
const manifest = parseQqbotPluginManifest({
|
||||
...validManifest,
|
||||
key: 'sample-plugin',
|
||||
pluginKey: undefined,
|
||||
runtime: {
|
||||
...validManifest.runtime,
|
||||
workerType: 'thread',
|
||||
},
|
||||
});
|
||||
|
||||
expect(manifest.key).toBe('sample-plugin');
|
||||
expect(manifest.pluginKey).toBe('sample-plugin');
|
||||
expect(manifest.runtime.workerType).toBe('thread');
|
||||
});
|
||||
|
||||
it('rejects unknown permissions from both plugin and operation scopes', () => {
|
||||
const manifest = createValidManifest();
|
||||
manifest.permissions = ['qqbot.send', 'host.env.read'];
|
||||
|
||||
@ -51,7 +51,11 @@ describe('QqbotPluginPackageSourceService', () => {
|
||||
expect.objectContaining({
|
||||
entry: 'src/index.ts',
|
||||
entryFile: join(packageRoot, 'src', 'index.ts'),
|
||||
manifest: expect.objectContaining({ key: 'sample' }),
|
||||
manifest: expect.objectContaining({
|
||||
key: 'sample',
|
||||
pluginKey: 'sample',
|
||||
runtime: expect.objectContaining({ workerType: 'thread' }),
|
||||
}),
|
||||
packageRoot,
|
||||
pluginKey: 'sample',
|
||||
}),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user