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)) {
|
if (!pluginKeyPattern.test(pluginKey)) {
|
||||||
pushIssue(
|
pushIssue(
|
||||||
issues,
|
issues,
|
||||||
'INVALID_PLUGIN_KEY',
|
'INVALID_PLUGIN_KEY',
|
||||||
'pluginKey',
|
pluginKeyPath,
|
||||||
'Plugin key must be lower-case kebab-case.',
|
'Plugin key must be lower-case kebab-case.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const version = getString(manifestLike, 'version') || '';
|
const version = getString(manifestLike, 'version') || '';
|
||||||
const minApiSdkVersion = getString(manifestLike, 'minApiSdkVersion') || '';
|
const minApiSdkVersion =
|
||||||
|
getString(manifestLike, 'minApiSdkVersion') || '1.0.0';
|
||||||
requireSemver(version, 'version', issues);
|
requireSemver(version, 'version', issues);
|
||||||
requireSemver(minApiSdkVersion, 'minApiSdkVersion', issues);
|
requireSemver(minApiSdkVersion, 'minApiSdkVersion', issues);
|
||||||
|
|
||||||
@ -633,6 +636,7 @@ export const parseQqbotPluginManifest = (
|
|||||||
'permissions',
|
'permissions',
|
||||||
issues,
|
issues,
|
||||||
),
|
),
|
||||||
|
key: pluginKey,
|
||||||
pluginKey,
|
pluginKey,
|
||||||
runtime: parseRuntime(manifestLike, issues),
|
runtime: parseRuntime(manifestLike, issues),
|
||||||
tasks: parseTasks(manifestLike, issues),
|
tasks: parseTasks(manifestLike, issues),
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export const QQBOT_PLUGIN_ALLOWED_PERMISSIONS = [
|
|||||||
export const QQBOT_PLUGIN_WORKER_TYPES = [
|
export const QQBOT_PLUGIN_WORKER_TYPES = [
|
||||||
'child-process',
|
'child-process',
|
||||||
'node-worker',
|
'node-worker',
|
||||||
|
'thread',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type QqbotPluginPermission =
|
export type QqbotPluginPermission =
|
||||||
@ -85,6 +86,7 @@ export type QqbotPluginManifest = {
|
|||||||
name: string;
|
name: string;
|
||||||
operations: QqbotPluginOperationManifest[];
|
operations: QqbotPluginOperationManifest[];
|
||||||
permissions: QqbotPluginPermission[];
|
permissions: QqbotPluginPermission[];
|
||||||
|
key: string;
|
||||||
pluginKey: string;
|
pluginKey: string;
|
||||||
runtime: QqbotPluginRuntimeManifest;
|
runtime: QqbotPluginRuntimeManifest;
|
||||||
tasks: QqbotPluginTaskManifest[];
|
tasks: QqbotPluginTaskManifest[];
|
||||||
|
|||||||
@ -2,10 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
||||||
import {
|
import { parseQqbotPluginManifest } from '@/modules/qqbot/plugin-platform/domain/manifest';
|
||||||
parseQqbotPluginManifest,
|
|
||||||
type QqbotPluginManifest,
|
|
||||||
} from '@/modules/qqbot/plugin-platform/domain/manifest';
|
|
||||||
|
|
||||||
import { QqbotPluginPackagePathPolicyService } from './plugin-package-path-policy.service';
|
import { QqbotPluginPackagePathPolicyService } from './plugin-package-path-policy.service';
|
||||||
import type { QqbotPluginPackageDescriptor } from './plugin-package.types';
|
import type { QqbotPluginPackageDescriptor } from './plugin-package.types';
|
||||||
@ -62,16 +59,15 @@ export class QqbotPluginPackageSourceService {
|
|||||||
controlledPackageRoot,
|
controlledPackageRoot,
|
||||||
entry,
|
entry,
|
||||||
);
|
);
|
||||||
const manifest = parseQqbotPluginManifest(
|
const manifest = parseQqbotPluginManifest(manifestLike, {
|
||||||
this.normalizeManifestForCurrentParser(manifestLike),
|
pluginRoot: controlledPackageRoot,
|
||||||
{ pluginRoot: controlledPackageRoot },
|
});
|
||||||
);
|
const pluginKey = manifest.key;
|
||||||
const pluginKey = manifest.pluginKey;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
entry: manifest.entry,
|
entry: manifest.entry,
|
||||||
entryFile,
|
entryFile,
|
||||||
manifest: this.attachPackageKeyAlias(manifest, pluginKey),
|
manifest,
|
||||||
packageRoot: controlledPackageRoot,
|
packageRoot: controlledPackageRoot,
|
||||||
pluginKey,
|
pluginKey,
|
||||||
};
|
};
|
||||||
@ -98,65 +94,6 @@ export class QqbotPluginPackageSourceService {
|
|||||||
return typeof manifestLike.entry === 'string' ? manifestLike.entry : '';
|
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.
|
* Narrows JSON values to object records before reading manifest fields.
|
||||||
* @param value - Parsed JSON value from a QQBot plugin package manifest.
|
* @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> {
|
private isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return !!value && typeof value === 'object' && !Array.isArray(value);
|
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', () => {
|
it('rejects unknown permissions from both plugin and operation scopes', () => {
|
||||||
const manifest = createValidManifest();
|
const manifest = createValidManifest();
|
||||||
manifest.permissions = ['qqbot.send', 'host.env.read'];
|
manifest.permissions = ['qqbot.send', 'host.env.read'];
|
||||||
|
|||||||
@ -51,7 +51,11 @@ describe('QqbotPluginPackageSourceService', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
entry: 'src/index.ts',
|
entry: 'src/index.ts',
|
||||||
entryFile: join(packageRoot, '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,
|
packageRoot,
|
||||||
pluginKey: 'sample',
|
pluginKey: 'sample',
|
||||||
}),
|
}),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user