refactor(platform): complete decoupled vertical runtime

This commit is contained in:
2026-09-05 10:21:28 +08:00
parent d177c52c3f
commit 1e3a21b476
10 changed files with 611 additions and 330 deletions
@@ -1,12 +1,14 @@
import { afterEach, test } from 'node:test';
import assert from 'node:assert/strict';
import { existsSync } from 'node:fs';
import { existsSync, readdirSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
findCompatibilityDependency,
LEGACY_RUNTIME,
scanImportBoundaries,
} from '../../scripts/lib/import-boundaries.mjs';
@@ -37,14 +39,6 @@ function scan(root, tsconfigPath) {
}
const frameworkRoot = fileURLToPath(new URL('../../YouleNexus/assets/framework/', import.meta.url));
const modernRuntimeEntries = [
'net/wire-client.ts',
'platform/runtime-session.ts',
'protocol/platform-handlers.ts',
'protocol/router.ts',
// Task 10 creates this entry; the same assertion starts covering it as soon as it exists.
'platform/runtime.ts',
];
const legacyRuntimeFiles = new Set([
'net/net-client.ts',
'platform/session.ts',
@@ -67,11 +61,14 @@ function scanCompatibilityFixture(root, entry = 'framework/protocol/router.ts',
});
}
test('every existing modern runtime entry is transitively isolated from the legacy compatibility island', () => {
const entries = modernRuntimeEntries
.map((file) => resolve(frameworkRoot, file))
.filter((file) => existsSync(file));
assert.ok(entries.some((file) => file.endsWith(`${join('protocol', 'router.ts')}`)));
test('every modern framework file is transitively isolated from the exact legacy compatibility island', () => {
const files = readdirSync(frameworkRoot, { recursive: true })
.filter(file => file.endsWith('.ts'))
.map(file => resolve(frameworkRoot, file));
assert.deepEqual(new Set(files.filter(file => LEGACY_RUNTIME.test(file))), legacyRuntimeFiles);
const entries = files.filter(file => !legacyRuntimeFiles.has(file));
assert.ok(existsSync(resolve(frameworkRoot, 'platform/runtime.ts')));
assert.ok(entries.includes(resolve(frameworkRoot, 'sdk/index.ts')));
for (const entry of entries) {
const dependency = findCompatibilityDependency({
@@ -88,6 +85,22 @@ test('every existing modern runtime entry is transitively isolated from the lega
}
});
test('obsolete SDK identifier inventory stays within explicit negative tests and one legacy comment', () => {
const result = spawnSync('rg', [
'-l', '--glob', '*.ts', '--glob', '*.mjs',
'\\b(GameContext|IGameModule|ActiveGame)\\b',
'YouleNexus/assets/framework', 'framework-tests',
], { cwd: fileURLToPath(new URL('../../', import.meta.url)), encoding: 'utf8' });
assert.ifError(result.error);
assert.ok(result.status === 0 || result.status === 1, result.stderr);
const allowlist = new Set([
'framework-tests/architecture/import-boundaries.test.mjs',
'YouleNexus/assets/framework/protocol/room-handlers.ts',
]);
const matches = result.stdout.trim().split(/\r?\n/).filter(Boolean).map(file => file.replaceAll('\\', '/'));
assert.deepEqual(matches.filter(file => !allowlist.has(file)), []);
});
test('compatibility graph catches static imports, re-exports, and import types through barrels', async () => {
const cases = [
"import '../platform/session.ts'\n",
@@ -427,7 +440,7 @@ test('scanner rejects a CommonJS Cocos import in nested sdk code', async () => {
assert.match(violations[0].message, /sdk.*Cocos/);
});
test('sdk migration barrel exception permits only its four retained legacy dependencies', async () => {
test('sdk public barrel has no exemptions for any former migration dependency', async () => {
const root = await createFixtureRoot();
for (const file of [
'framework/core/events.ts',
@@ -446,10 +459,34 @@ test('sdk migration barrel exception permits only its four retained legacy depen
"import type { Value as Constants } from '../core/constants.ts'; void (0 as unknown as Constants);",
].join('\n'));
const violations = scan(root);
assert.equal(violations.length, 5);
for (const violation of violations) assert.match(violation.message, /sdk.*framework implementation/);
});
test('former active-game file has no exemption from obsolete SDK name checks', async () => {
const root = await createFixtureRoot();
await writeFixture(root, 'framework/protocol/active-game.ts', "import type { IGameModule } from '../sdk/index.ts'\n");
const violations = scan(root);
assert.equal(violations.length, 1);
assert.equal(violations[0].specifier, '../core/constants.ts');
assert.match(violations[0].message, /sdk.*framework implementation/);
assert.match(violations[0].message, /IGameModule.*migration-only/);
});
test('obsolete SDK names are rejected in destructured CommonJS and dynamic imports and named re-exports', async () => {
for (const source of [
"const { GameContext: Context } = require('../sdk/index.ts');",
"const { IGameModule } = require(`../sdk/index.ts`);",
"const { GameContext } = await import('../sdk/index.ts');",
"const Context = require('../sdk/index.ts').GameContext;",
"export type { IGameModule } from '../sdk/index.ts';",
"type Context = import('../sdk/index.ts').GameContext;",
]) {
const root = await createFixtureRoot();
await writeFixture(root, 'framework/application/bad.ts', source);
const violations = scan(root);
assert.equal(violations.length, 1, source);
assert.match(violations[0].message, /migration-only/, source);
}
});
test('scanner permits sdk-internal relatives and ordinary external pure dependencies', async () => {