diff --git a/packages-user/data-base/src/hero/follower.ts b/packages-user/data-base/src/hero/follower.ts index 203dcbd..ad553f2 100644 --- a/packages-user/data-base/src/hero/follower.ts +++ b/packages-user/data-base/src/hero/follower.ts @@ -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'; diff --git a/packages-user/data-base/src/hero/location.ts b/packages-user/data-base/src/hero/location.ts index c5d3501..df1fc11 100644 --- a/packages-user/data-base/src/hero/location.ts +++ b/packages-user/data-base/src/hero/location.ts @@ -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, diff --git a/packages-user/data-base/src/hero/state.ts b/packages-user/data-base/src/hero/state.ts index 54aede6..4774131 100644 --- a/packages-user/data-base/src/hero/state.ts +++ b/packages-user/data-base/src/hero/state.ts @@ -23,12 +23,12 @@ import { FaceDirection, IDataCommon, IFaceHandler, + IFacedTileLocator, SaveCompression } from '@user/data-common'; import { Hookable, HookController, - IFacedTileLocator, IHookController, logger } from '@motajs/common'; diff --git a/packages-user/data-base/src/hero/types.ts b/packages-user/data-base/src/hero/types.ts index 4b62857..dfb4959 100644 --- a/packages-user/data-base/src/hero/types.ts +++ b/packages-user/data-base/src/hero/types.ts @@ -1,5 +1,4 @@ import { - IFacedTileLocator, IHookBase, IHookable, ITileLocator @@ -8,6 +7,7 @@ import { FaceDirection, IDataCommonExtended, IFaceHandler, + IFacedTileLocator, IItemRawData, IObjectMovable, IObjectMover, diff --git a/packages-user/data-common/src/common/face.ts b/packages-user/data-common/src/common/face.ts index 8654106..c95dab6 100644 --- a/packages-user/data-common/src/common/face.ts +++ b/packages-user/data-common/src/common/face.ts @@ -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 { /** 此图块的朝向 */ diff --git a/packages-user/data-common/src/common/types.ts b/packages-user/data-common/src/common/types.ts index ef726bb..97259e2 100644 --- a/packages-user/data-common/src/common/types.ts +++ b/packages-user/data-common/src/common/types.ts @@ -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; diff --git a/packages-user/data-common/src/store/types.ts b/packages-user/data-common/src/store/types.ts index 7516f5d..8d530fc 100644 --- a/packages-user/data-common/src/store/types.ts +++ b/packages-user/data-common/src/store/types.ts @@ -1,4 +1,4 @@ -import { IFacedTileLocator } from '@motajs/common'; +import { IFacedTileLocator } from '../common'; import { IReadonlyGameEvent } from '../event'; //#region tile diff --git a/packages/common/src/utils/types.ts b/packages/common/src/utils/types.ts index 82b6a31..39784d6 100644 --- a/packages/common/src/utils/types.ts +++ b/packages/common/src/utils/types.ts @@ -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, diff --git a/script/check-data-circular.ts b/script/check-data-circular.ts new file mode 100644 index 0000000..7563f6d --- /dev/null +++ b/script/check-data-circular.ts @@ -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' +); diff --git a/script/check-data-type.ts b/script/check-data-type.ts new file mode 100644 index 0000000..9d67f24 --- /dev/null +++ b/script/check-data-type.ts @@ -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');