fix: 使用规范化插件入口路径

This commit is contained in:
sunlei 2026-06-18 02:44:59 +08:00
parent 3a941d8a6d
commit 8b50b57f27
2 changed files with 67 additions and 26 deletions

View File

@ -54,14 +54,13 @@ export class QqbotPluginPackageSourceService {
}
const manifestLike = JSON.parse(readFileSync(manifestFile, 'utf8'));
const entry = this.readManifestEntry(manifestLike);
const entryFile = this.pathPolicy.resolveEntryFile(
controlledPackageRoot,
entry,
);
const manifest = parseQqbotPluginManifest(manifestLike, {
pluginRoot: controlledPackageRoot,
});
const entryFile = this.pathPolicy.resolveEntryFile(
controlledPackageRoot,
manifest.entry,
);
const pluginKey = manifest.key;
return {
@ -83,23 +82,4 @@ export class QqbotPluginPackageSourceService {
.filter((entry) => entry.isDirectory())
.map((entry) => join(root, entry.name));
}
/**
* Reads the raw entry string before parser normalization so path policy errors stay explicit.
* @param manifestLike - Parsed `plugin.json` content whose entry path belongs to the package.
* @returns Raw entry path or an empty value that the manifest parser will reject.
*/
private readManifestEntry(manifestLike: unknown): string {
if (!this.isRecord(manifestLike)) return '';
return typeof manifestLike.entry === 'string' ? manifestLike.entry : '';
}
/**
* Narrows JSON values to object records before reading manifest fields.
* @param value - Parsed JSON value from a QQBot plugin package manifest.
* @returns Whether the value is a non-array object record.
*/
private isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
}

View File

@ -1,4 +1,10 @@
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import {
mkdtempSync,
mkdirSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join, sep } from 'node:path';
@ -88,7 +94,62 @@ describe('QqbotPluginPackageSourceService', () => {
);
await expect(source.discoverPackages()).rejects.toThrow(
'Plugin entry must stay inside the package root',
'PATH_OUTSIDE_PLUGIN_ROOT',
);
});
it('resolves entry files from parser-normalized package paths', async () => {
const packageRoot = join(tempRoot, 'windows-entry');
mkdirSync(join(packageRoot, 'src'), { recursive: true });
writeFileSync(
join(packageRoot, 'plugin.json'),
JSON.stringify({
key: 'windows-entry',
name: 'Windows Entry',
version: '1.0.0',
entry: 'src\\\\index.ts',
runtime: {
workerType: 'thread',
timeoutMs: 5000,
memoryMb: 128,
maxConcurrency: 1,
},
operations: [],
}),
'utf8',
);
const pathPolicy = new QqbotPluginPackagePathPolicyService([tempRoot]);
const resolveEntryFileSpy = jest.spyOn(pathPolicy, 'resolveEntryFile');
const source = new QqbotPluginPackageSourceService(pathPolicy);
await expect(source.discoverPackages()).resolves.toEqual([
expect.objectContaining({
entry: 'src/index.ts',
entryFile: join(packageRoot, 'src', 'index.ts'),
pluginKey: 'windows-entry',
}),
]);
expect(resolveEntryFileSpy).toHaveBeenCalledWith(
packageRoot,
'src/index.ts',
);
});
it('does not keep platform-side manifest transfer shims', () => {
const source = readFileSync(
join(
process.cwd(),
'src/modules/qqbot/plugin-platform/infrastructure/integration/package/plugin-package-source.service.ts',
),
'utf8',
);
expect(source).not.toMatch(
/normalizeManifestForCurrentParser|attachPackageKeyAlias|readPackageKey/,
);
expect(source).not.toMatch(
/thread[\s\S]{0,80}node-worker|node-worker[\s\S]{0,80}thread/,
);
});
});