fix(sdk): close boundary alias bypasses

This commit is contained in:
2026-09-05 08:12:01 +08:00
parent dfaa4f0ef4
commit 88784ad52a
3 changed files with 117 additions and 8 deletions
@@ -6,6 +6,7 @@ const projectDir = resolve(fileURLToPath(new URL('..', import.meta.url)));
const violations = scanImportBoundaries({
frameworkDir: resolve(projectDir, 'YouleNexus/assets/framework'),
gamesDir: resolve(projectDir, 'games'),
tsconfigPath: resolve(projectDir, 'tsconfig.framework.json'),
});
if (violations.length > 0) {
@@ -7,6 +7,12 @@ const FRAMEWORK_INTERNAL = /framework[\\/](?:net|protocol|platform|application|d
const LEGACY_RUNTIME = /framework[\\/](?:net[\\/]net-client|platform[\\/](?:session|startup|room-rpc-bus|readonly|stores[\\/](?:app-store|player-store|room-store|types))|protocol[\\/]room-handlers)\.ts$/;
const CONTRACTS_PATH = /framework[\\/]sdk[\\/]contracts(?:[\\/]|$)/;
const MIGRATION_ONLY_NAMES = new Set(['GameContext', 'IGameModule']);
const SDK_INDEX_MIGRATION_DEPENDENCIES = new Set([
'core/events.ts',
'core/reactive.ts',
'platform/readonly.ts',
'platform/stores/types.ts',
]);
/**
* Recursively scans TypeScript imports for framework/game layering violations.
@@ -15,14 +21,28 @@ const MIGRATION_ONLY_NAMES = new Set(['GameContext', 'IGameModule']);
export function scanImportBoundaries(options) {
const frameworkDir = resolve(options.frameworkDir);
const gamesDir = resolve(options.gamesDir);
const compilerOptions = readCompilerOptions(options.tsconfigPath);
const resolutionBase = options.tsconfigPath
? dirname(resolve(options.tsconfigPath))
: frameworkDir;
const moduleCache = ts.createModuleResolutionCache(
resolutionBase,
(file) => pathKey(canonicalPath(file)),
compilerOptions,
);
const violations = [];
for (const file of [...typescriptFiles(frameworkDir), ...typescriptFiles(gamesDir)]) {
const source = readFileSync(file, 'utf8');
for (const imported of extractImports(source, file)) {
const resolved = imported.specifier.startsWith('.')
? resolve(dirname(file), imported.specifier)
: null;
const resolved = resolveDependency(
imported.specifier,
file,
compilerOptions,
moduleCache,
) ?? (imported.specifier.startsWith('.')
? canonicalPath(resolve(dirname(file), imported.specifier))
: null);
const violation = findViolation({
file,
specifier: imported.specifier,
@@ -233,11 +253,15 @@ function findViolation(context) {
return violation(filePath, specifier, `sdk/contracts may import only sibling contracts; sdk path resolved to ${resolvedPath}`);
}
if (isSdk && isCocosSpecifier(specifier)) {
return violation(filePath, specifier, `sdk cannot import Cocos module ${specifier}`);
}
if (isSdk
&& !isSdkMigrationBarrel(filePath, frameworkDir)
&& resolved
&& isInside(resolved, frameworkDir)
&& !SDK_ALLOWED.test(resolvedPath)) {
&& !SDK_ALLOWED.test(resolvedPath)
&& !isSdkIndexMigrationDependency(filePath, resolved, frameworkDir)) {
return violation(filePath, specifier, `sdk cannot import framework implementation ${resolvedPath}`);
}
@@ -273,8 +297,13 @@ function isLegacyRuntimeImporter(filePath, frameworkDir) {
return filePath === `${normalizedFrameworkDir}/sdk/index.ts` || LEGACY_RUNTIME.test(filePath);
}
function isSdkMigrationBarrel(filePath, frameworkDir) {
return filePath === `${displayPath(frameworkDir)}/sdk/index.ts`;
function isSdkIndexMigrationDependency(filePath, resolved, frameworkDir) {
if (filePath !== `${displayPath(frameworkDir)}/sdk/index.ts`) return false;
return SDK_INDEX_MIGRATION_DEPENDENCIES.has(displayPath(relative(frameworkDir, resolved)));
}
function isCocosSpecifier(specifier) {
return specifier === 'cc' || specifier.startsWith('cc/');
}
/**