fix: 修复内置插件生产路径解析
This commit is contained in:
parent
30f8c2bbc3
commit
9fc11975c1
@ -66,7 +66,9 @@ export class QqbotPluginPackagePathPolicyService {
|
||||
* @returns Normalized absolute package directory when it is inside a controlled root.
|
||||
*/
|
||||
assertControlledPackageRoot(packageRoot: string): string {
|
||||
const normalizedPackageRoot = resolve(packageRoot);
|
||||
const normalizedPackageRoot =
|
||||
this.resolvePersistedBuiltinPackageRoot(packageRoot) ||
|
||||
resolve(packageRoot);
|
||||
const isControlled = this.controlledRoots.some(
|
||||
(root) =>
|
||||
normalizedPackageRoot === root ||
|
||||
@ -80,6 +82,35 @@ export class QqbotPluginPackagePathPolicyService {
|
||||
return normalizedPackageRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps persisted built-in source package paths to the controlled root used by the current runtime.
|
||||
* @param packageRoot - Package root persisted by seed SQL or install records; built-ins may store source-relative paths.
|
||||
* @returns Runtime package directory under the active controlled root, or null for ordinary paths.
|
||||
*/
|
||||
private resolvePersistedBuiltinPackageRoot(
|
||||
packageRoot: string,
|
||||
): string | null {
|
||||
if (isAbsolute(packageRoot)) return null;
|
||||
|
||||
const packageSegments = this.toPathSegments(packageRoot);
|
||||
const builtinPrefix = DEFAULT_BUILTIN_PACKAGE_ROOT_SEGMENTS.find(
|
||||
(segments) => this.startsWithSegments(packageSegments, segments),
|
||||
);
|
||||
if (!builtinPrefix || packageSegments.length !== builtinPrefix.length + 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const packageName = packageSegments[packageSegments.length - 1];
|
||||
if (!packageName || packageName === '..') return null;
|
||||
|
||||
const controlledRoot = this.controlledRoots.find((root) =>
|
||||
DEFAULT_BUILTIN_PACKAGE_ROOT_SEGMENTS.some((segments) =>
|
||||
this.endsWithSegments(this.toPathSegments(root), segments),
|
||||
),
|
||||
);
|
||||
return controlledRoot ? resolve(controlledRoot, packageName) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a candidate package path escapes a controlled root.
|
||||
* @param root - Absolute controlled root that bounds package discovery.
|
||||
@ -95,6 +126,42 @@ export class QqbotPluginPackagePathPolicyService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a path with either Windows or POSIX separators for suffix/prefix policy checks.
|
||||
* @param pathValue - Raw absolute or relative filesystem path.
|
||||
* @returns Non-empty path segments excluding current-directory markers.
|
||||
*/
|
||||
private toPathSegments(pathValue: string): string[] {
|
||||
return pathValue
|
||||
.replace(/\\/g, '/')
|
||||
.split('/')
|
||||
.filter((segment) => segment && segment !== '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a path segment list starts with an expected built-in root prefix.
|
||||
* @param candidate - Candidate package root split into path segments.
|
||||
* @param expected - Built-in source or dist root segment sequence.
|
||||
* @returns Whether candidate begins with the expected sequence.
|
||||
*/
|
||||
private startsWithSegments(candidate: string[], expected: string[]): boolean {
|
||||
return expected.every((segment, index) => candidate[index] === segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a controlled root ends with an expected built-in root segment sequence.
|
||||
* @param candidate - Controlled root split into path segments.
|
||||
* @param expected - Built-in source or dist root segment sequence.
|
||||
* @returns Whether candidate ends with the expected sequence.
|
||||
*/
|
||||
private endsWithSegments(candidate: string[], expected: string[]): boolean {
|
||||
const offset = candidate.length - expected.length;
|
||||
if (offset < 0) return false;
|
||||
return expected.every(
|
||||
(segment, index) => candidate[offset + index] === segment,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a manifest entry to the file Node can import in the current runtime.
|
||||
* @param entryFile - Policy-checked entry path declared by `plugin.json`.
|
||||
|
||||
@ -204,6 +204,51 @@ describe('QqbotPluginPackageSourceService', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('resolves persisted source package paths to the current dist controlled root', () => {
|
||||
const distPluginRoot = join(
|
||||
tempRoot,
|
||||
'production-app',
|
||||
'dist',
|
||||
'modules',
|
||||
'qqbot',
|
||||
'plugins',
|
||||
);
|
||||
const packageRoot = join(distPluginRoot, 'bilibili-card');
|
||||
mkdirSync(join(packageRoot, 'src'), { recursive: true });
|
||||
writeFileSync(
|
||||
join(packageRoot, 'src', 'index.js'),
|
||||
'module.exports = { createPlugin() {} };',
|
||||
'utf8',
|
||||
);
|
||||
|
||||
const source = new QqbotPluginPackageSourceService(
|
||||
new QqbotPluginPackagePathPolicyService([distPluginRoot]),
|
||||
);
|
||||
|
||||
expect(
|
||||
source.resolveDescriptor('src/modules/qqbot/plugins/bilibili-card', {
|
||||
key: 'bilibili-card',
|
||||
name: 'Bilibili Card',
|
||||
version: '1.0.0',
|
||||
entry: 'src/index.ts',
|
||||
runtime: {
|
||||
workerType: 'thread',
|
||||
timeoutMs: 10000,
|
||||
memoryMb: 128,
|
||||
maxConcurrency: 1,
|
||||
},
|
||||
operations: [],
|
||||
events: [],
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
entryFile: join(packageRoot, 'src', 'index.js'),
|
||||
packageRoot,
|
||||
pluginKey: 'bilibili-card',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not keep platform-side manifest transfer shims', () => {
|
||||
const source = readFileSync(
|
||||
join(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user