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
@@ -28,10 +28,11 @@ async function writeFixture(root, relativePath, source) {
await writeFile(file, source, 'utf8');
}
function scan(root) {
function scan(root, tsconfigPath) {
return scanImportBoundaries({
frameworkDir: join(root, 'framework'),
gamesDir: join(root, 'games'),
tsconfigPath: tsconfigPath ? join(root, tsconfigPath) : undefined,
});
}
@@ -217,6 +218,84 @@ test('scanner rejects every sdk runtime dependency on framework implementation l
}
});
test('scanner resolves and rejects an sdk runtime path alias into framework implementation', async () => {
const root = await createFixtureRoot();
await writeFixture(root, 'framework/core/internal.ts', 'export const value = true\n');
await writeFixture(
root,
'framework/sdk/runtime/bad.ts',
"import { value } from '@framework/core/internal'; void value\n",
);
await writeFixture(root, 'tsconfig.json', JSON.stringify({
compilerOptions: {
baseUrl: '.',
paths: { '@framework/*': ['framework/*'] },
module: 'ESNext',
moduleResolution: 'Bundler',
allowImportingTsExtensions: true,
},
}));
const violations = scan(root, 'tsconfig.json');
assert.equal(violations.length, 1);
assert.equal(violations[0].specifier, '@framework/core/internal');
assert.match(violations[0].message, /sdk.*framework implementation.*framework\/core/);
});
test('scanner rejects Cocos imports throughout sdk runtime', async () => {
for (const specifier of ['cc', 'cc/env']) {
const root = await createFixtureRoot();
await writeFixture(
root,
'framework/sdk/runtime/bad.ts',
`import { Node } from '${specifier}'; void Node\n`,
);
const violations = scan(root);
assert.equal(violations.length, 1, specifier);
assert.equal(violations[0].specifier, specifier);
assert.match(violations[0].message, /sdk.*Cocos/);
}
});
test('sdk migration barrel exception permits only its four retained legacy dependencies', async () => {
const root = await createFixtureRoot();
for (const file of [
'framework/core/events.ts',
'framework/core/reactive.ts',
'framework/platform/readonly.ts',
'framework/platform/stores/types.ts',
'framework/core/constants.ts',
]) {
await writeFixture(root, file, 'export interface Value {}\n');
}
await writeFixture(root, 'framework/sdk/index.ts', [
"import type { Value as Events } from '../core/events.ts'; void (0 as unknown as Events);",
"import type { Value as Reactive } from '../core/reactive.ts'; void (0 as unknown as Reactive);",
"import type { Value as ReadonlyStore } from '../platform/readonly.ts'; void (0 as unknown as ReadonlyStore);",
"import type { Value as StoreTypes } from '../platform/stores/types.ts'; void (0 as unknown as StoreTypes);",
"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, 1);
assert.equal(violations[0].specifier, '../core/constants.ts');
assert.match(violations[0].message, /sdk.*framework implementation/);
});
test('scanner permits sdk-internal relatives and ordinary external pure dependencies', async () => {
const root = await createFixtureRoot();
await writeFixture(root, 'framework/sdk/contracts/index.ts', 'export interface Contract {}\n');
await writeFixture(root, 'framework/sdk/runtime/helper.ts', 'export const helper = true\n');
await writeFixture(root, 'framework/sdk/runtime/allowed.ts', [
"import type { Contract } from '../contracts/index.ts'; void (0 as unknown as Contract);",
"import { helper } from './helper.ts'; void helper;",
"import type { PureValue } from 'pure-external'; void (0 as unknown as PureValue);",
].join('\n'));
assert.deepEqual(scan(root), []);
});
test('scanner rejects game imports of framework net internals', async () => {
const root = await createFixtureRoot();
await writeFixture(root, 'games/a/assets/game/bad.ts', "import '../../../framework/net/net-client.ts'\n");