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.
|
* @returns Normalized absolute package directory when it is inside a controlled root.
|
||||||
*/
|
*/
|
||||||
assertControlledPackageRoot(packageRoot: string): string {
|
assertControlledPackageRoot(packageRoot: string): string {
|
||||||
const normalizedPackageRoot = resolve(packageRoot);
|
const normalizedPackageRoot =
|
||||||
|
this.resolvePersistedBuiltinPackageRoot(packageRoot) ||
|
||||||
|
resolve(packageRoot);
|
||||||
const isControlled = this.controlledRoots.some(
|
const isControlled = this.controlledRoots.some(
|
||||||
(root) =>
|
(root) =>
|
||||||
normalizedPackageRoot === root ||
|
normalizedPackageRoot === root ||
|
||||||
@ -80,6 +82,35 @@ export class QqbotPluginPackagePathPolicyService {
|
|||||||
return normalizedPackageRoot;
|
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.
|
* Checks whether a candidate package path escapes a controlled root.
|
||||||
* @param root - Absolute controlled root that bounds package discovery.
|
* @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.
|
* Resolves a manifest entry to the file Node can import in the current runtime.
|
||||||
* @param entryFile - Policy-checked entry path declared by `plugin.json`.
|
* @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', () => {
|
it('does not keep platform-side manifest transfer shims', () => {
|
||||||
const source = readFileSync(
|
const source = readFileSync(
|
||||||
join(
|
join(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user