mirror of
https://github.com/motajs/template.git
synced 2026-09-12 09:48:49 +08:00
fix(03-05): close data package type and cycle boundaries
This commit is contained in:
parent
86a4385261
commit
64b97bc515
@ -1,7 +1,6 @@
|
||||
import {
|
||||
Hookable,
|
||||
HookController,
|
||||
IFacedTileLocator,
|
||||
IHookController,
|
||||
logger
|
||||
} from '@motajs/common';
|
||||
@ -9,6 +8,7 @@ import {
|
||||
FaceDirection,
|
||||
IDataCommon,
|
||||
IFaceHandler,
|
||||
IFacedTileLocator,
|
||||
SaveCompression
|
||||
} from '@user/data-common';
|
||||
import { HeroLocation } from './location';
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import {
|
||||
Hookable,
|
||||
HookController,
|
||||
IFacedTileLocator,
|
||||
IHookController
|
||||
} from '@motajs/common';
|
||||
import { FaceDirection, IDataCommon, IFaceHandler } from '@user/data-common';
|
||||
import {
|
||||
FaceDirection,
|
||||
IDataCommon,
|
||||
IFaceHandler,
|
||||
IFacedTileLocator
|
||||
} from '@user/data-common';
|
||||
import { HeroMover } from './mover';
|
||||
import {
|
||||
IHeroLocation,
|
||||
|
||||
@ -23,12 +23,12 @@ import {
|
||||
FaceDirection,
|
||||
IDataCommon,
|
||||
IFaceHandler,
|
||||
IFacedTileLocator,
|
||||
SaveCompression
|
||||
} from '@user/data-common';
|
||||
import {
|
||||
Hookable,
|
||||
HookController,
|
||||
IFacedTileLocator,
|
||||
IHookController,
|
||||
logger
|
||||
} from '@motajs/common';
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
IFacedTileLocator,
|
||||
IHookBase,
|
||||
IHookable,
|
||||
ITileLocator
|
||||
@ -8,6 +7,7 @@ import {
|
||||
FaceDirection,
|
||||
IDataCommonExtended,
|
||||
IFaceHandler,
|
||||
IFacedTileLocator,
|
||||
IItemRawData,
|
||||
IObjectMovable,
|
||||
IObjectMover,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { IFaceData, IRoleFaceBinder } from '../common';
|
||||
import { IFaceData, IRoleFaceBinder } from './types';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { FaceDirection } from '.';
|
||||
import { FaceDirection } from './types';
|
||||
|
||||
interface FaceInfo {
|
||||
/** 此图块的朝向 */
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { ITileLocator } from '@motajs/common';
|
||||
|
||||
export const enum FaceDirection {
|
||||
Unknown,
|
||||
Left,
|
||||
@ -10,6 +12,11 @@ export const enum FaceDirection {
|
||||
RightDown
|
||||
}
|
||||
|
||||
export interface IFacedTileLocator extends ITileLocator {
|
||||
/** 图块朝向 */
|
||||
direction: FaceDirection;
|
||||
}
|
||||
|
||||
export interface IFaceData {
|
||||
/** 图块数字 */
|
||||
readonly identifier: number;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { IFacedTileLocator } from '@motajs/common';
|
||||
import { IFacedTileLocator } from '../common';
|
||||
import { IReadonlyGameEvent } from '../event';
|
||||
|
||||
//#region tile
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { FaceDirection } from '@user/data-common';
|
||||
|
||||
export interface ISearchable4Dir {
|
||||
/** 获取上侧元素 */
|
||||
up(): ISearchable4Dir | null;
|
||||
@ -144,11 +142,6 @@ export interface ITileLocator {
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface IFacedTileLocator extends ITileLocator {
|
||||
/** 图块朝向 */
|
||||
direction: FaceDirection;
|
||||
}
|
||||
|
||||
export const enum InternalDirectionGroup {
|
||||
/** 上下左右四方向 */
|
||||
Dir4,
|
||||
|
||||
65
script/check-data-circular.ts
Normal file
65
script/check-data-circular.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import madge from 'madge';
|
||||
import { relative, resolve } from 'node:path';
|
||||
|
||||
const root = process.cwd();
|
||||
const entries = [
|
||||
'packages-user/data-common/src/index.ts',
|
||||
'packages-user/data-base/src/index.ts',
|
||||
'packages-user/data-system/src/index.ts',
|
||||
'packages-user/data-state/src/index.ts',
|
||||
'packages/common/src/index.ts'
|
||||
];
|
||||
const dataPrefixes = [
|
||||
'packages-user/data-common/',
|
||||
'packages-user/data-base/',
|
||||
'packages-user/data-system/',
|
||||
'packages-user/data-state/'
|
||||
];
|
||||
|
||||
function toRelativePath(file: string): string {
|
||||
const absolute = resolve(root, file);
|
||||
return relative(root, absolute).replaceAll('\\', '/');
|
||||
}
|
||||
|
||||
function isInScope(cycle: readonly string[]): boolean {
|
||||
return cycle.some(file => {
|
||||
const normalized = file.replaceAll('\\', '/');
|
||||
return (
|
||||
normalized.includes('packages/common/') ||
|
||||
dataPrefixes.some(prefix => normalized.includes(prefix))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const graph = await madge(entries, {
|
||||
baseDir: root,
|
||||
tsConfig: resolve(root, 'tsconfig.json'),
|
||||
fileExtensions: ['ts', 'tsx'],
|
||||
detectiveOptions: { ts: { skipTypeImports: false } }
|
||||
});
|
||||
const cycles = graph.circular() as readonly (readonly string[])[];
|
||||
const inScope = cycles.filter(isInScope);
|
||||
const outsideScope = cycles.filter(cycle => !isInScope(cycle));
|
||||
|
||||
console.log(
|
||||
`Circular diagnostics: ${cycles.length} total, ${inScope.length} in-scope, ${outsideScope.length} outside scope`
|
||||
);
|
||||
for (const [index, cycle] of inScope.entries()) {
|
||||
console.log(`IN-SCOPE CYCLE ${index + 1}:`);
|
||||
console.log(cycle.map(toRelativePath).join(' -> '));
|
||||
}
|
||||
for (const [index, cycle] of outsideScope.entries()) {
|
||||
console.log(`OUTSIDE-SCOPE CYCLE ${index + 1}:`);
|
||||
console.log(cycle.map(toRelativePath).join(' -> '));
|
||||
}
|
||||
|
||||
if (inScope.length > 0) {
|
||||
console.error(
|
||||
'Circular gate failed: four-package, common/data-common, or transitive common cycles remain'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
'Circular gate passed: four data packages and the transitive common boundary are acyclic'
|
||||
);
|
||||
101
script/check-data-type.ts
Normal file
101
script/check-data-type.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { isAbsolute, relative, resolve } from 'node:path';
|
||||
|
||||
const root = process.cwd();
|
||||
const dataPackages = [
|
||||
'packages-user/data-common/',
|
||||
'packages-user/data-base/',
|
||||
'packages-user/data-system/',
|
||||
'packages-user/data-state/'
|
||||
];
|
||||
|
||||
interface ITypeDiagnostic {
|
||||
readonly file: string;
|
||||
readonly line: number;
|
||||
readonly column: number;
|
||||
readonly code: string;
|
||||
readonly message: string;
|
||||
}
|
||||
|
||||
function toRelativePath(file: string): string {
|
||||
const absolute = isAbsolute(file) ? file : resolve(root, file);
|
||||
return relative(root, absolute).replaceAll('\\', '/');
|
||||
}
|
||||
|
||||
function isDataPath(file: string): boolean {
|
||||
const normalized = toRelativePath(file);
|
||||
return dataPackages.some(prefix => normalized.startsWith(prefix));
|
||||
}
|
||||
|
||||
function parseDiagnostics(output: string): {
|
||||
readonly diagnostics: readonly ITypeDiagnostic[];
|
||||
readonly unparsed: readonly string[];
|
||||
} {
|
||||
const diagnostics: ITypeDiagnostic[] = [];
|
||||
const unparsed: string[] = [];
|
||||
const pattern = /^(.*?\.(?:ts|tsx|vue))\((\d+),(\d+)\): error (TS\d+): (.*)$/;
|
||||
|
||||
for (const line of output.split(/\r?\n/)) {
|
||||
if (!line.includes('error TS')) continue;
|
||||
const match = pattern.exec(line);
|
||||
if (!match) {
|
||||
unparsed.push(line);
|
||||
continue;
|
||||
}
|
||||
diagnostics.push({
|
||||
file: toRelativePath(match[1]),
|
||||
line: Number(match[2]),
|
||||
column: Number(match[3]),
|
||||
code: match[4],
|
||||
message: match[5]
|
||||
});
|
||||
}
|
||||
|
||||
return { diagnostics, unparsed };
|
||||
}
|
||||
|
||||
const command = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
||||
const result = spawnSync(
|
||||
command,
|
||||
['exec', 'vue-tsc', '--noEmit', '--pretty', 'false'],
|
||||
{ cwd: root, encoding: 'utf8', shell: process.platform === 'win32' }
|
||||
);
|
||||
|
||||
if (result.error) {
|
||||
console.error(`type gate could not execute vue-tsc: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const output = `${result.stdout ?? ''}\n${result.stderr ?? ''}`;
|
||||
const { diagnostics, unparsed } = parseDiagnostics(output);
|
||||
const inScope = diagnostics.filter(diagnostic => isDataPath(diagnostic.file));
|
||||
const outsideScope = diagnostics.filter(
|
||||
diagnostic => !isDataPath(diagnostic.file)
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Type diagnostics: ${diagnostics.length} total, ${inScope.length} in-scope, ${outsideScope.length} outside scope`
|
||||
);
|
||||
for (const diagnostic of inScope) {
|
||||
console.log(
|
||||
`IN-SCOPE ${diagnostic.file}:${diagnostic.line}:${diagnostic.column} ${diagnostic.code}: ${diagnostic.message}`
|
||||
);
|
||||
}
|
||||
for (const diagnostic of outsideScope) {
|
||||
console.log(
|
||||
`OUTSIDE-SCOPE ${diagnostic.file}:${diagnostic.line}:${diagnostic.column} ${diagnostic.code}: ${diagnostic.message}`
|
||||
);
|
||||
}
|
||||
|
||||
if (unparsed.length > 0) {
|
||||
console.error('Unparsed TypeScript diagnostics; failing closed:');
|
||||
for (const line of unparsed) console.error(line);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (inScope.length > 0) {
|
||||
console.error('Type gate failed on in-scope data-package diagnostics');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('Type gate passed: zero in-scope data-package diagnostics');
|
||||
Loading…
Reference in New Issue
Block a user