AGC 资源 kind 集合改为从生成 union 穷举派生
- assetKind.ts:新增 `GAME_CREATION_APP_ASSET_KIND_MEMBERS: Record<GameCreationAppAssetKind, true>`,生成 union 增删成员时会直接编译报错 - assetKind.ts:`GAME_CREATION_APP_ASSET_KINDS` 由该表派生,不再手写第二份字面量列表 - assetKind.ts:新增 `isGameCreationAppAssetKind` 类型守卫,`parseGameCreationAppAssetKind` 复用它 - view/project-development/index.tsx:资源引用的 `sourceSubtype` 只传正式 kind 成员,任务产物等非 manifest 来源传 `null` - tests/assetKindCanonicalMapping.test.ts:两侧 kind 目录比对前先展开 Rust 常量引用(`ui-design-doc` 是常量),修正此前恒失败的两条;删除测试体里游离的 `'font',` 表达式
This commit is contained in:
@@ -2,36 +2,54 @@ import type { GameCreationAppAssetKind } from './generated/GameCreationAppAssetK
|
||||
|
||||
export type { GameCreationAppAssetKind } from './generated/GameCreationAppAssetKind';
|
||||
|
||||
export const GAME_CREATION_APP_ASSET_KINDS = [
|
||||
'image',
|
||||
'scene',
|
||||
'character',
|
||||
'character-animation',
|
||||
'icon',
|
||||
'icon-spritesheet',
|
||||
'icon-spec',
|
||||
'ui-design',
|
||||
'ui-design-doc',
|
||||
'publication-material',
|
||||
'spec',
|
||||
'video',
|
||||
'audio',
|
||||
'sound-effect',
|
||||
'background-music',
|
||||
'font',
|
||||
'document',
|
||||
'code',
|
||||
'unknown',
|
||||
] as const satisfies readonly GameCreationAppAssetKind[];
|
||||
/**
|
||||
* 穷举生成的 union:这里多写或少写一个成员都会直接报类型错误,
|
||||
* 避免再维护一份与 `GameCreationAppAssetKind.ts` 分叉的手写列表。
|
||||
*/
|
||||
const GAME_CREATION_APP_ASSET_KIND_MEMBERS: Record<
|
||||
GameCreationAppAssetKind,
|
||||
true
|
||||
> = {
|
||||
image: true,
|
||||
scene: true,
|
||||
character: true,
|
||||
'character-animation': true,
|
||||
icon: true,
|
||||
'icon-spritesheet': true,
|
||||
'icon-spec': true,
|
||||
'ui-design': true,
|
||||
'ui-design-doc': true,
|
||||
'publication-material': true,
|
||||
spec: true,
|
||||
video: true,
|
||||
audio: true,
|
||||
'sound-effect': true,
|
||||
'background-music': true,
|
||||
font: true,
|
||||
document: true,
|
||||
code: true,
|
||||
unknown: true,
|
||||
};
|
||||
|
||||
export const GAME_CREATION_APP_ASSET_KINDS: readonly GameCreationAppAssetKind[] =
|
||||
Object.keys(
|
||||
GAME_CREATION_APP_ASSET_KIND_MEMBERS,
|
||||
) as GameCreationAppAssetKind[];
|
||||
|
||||
const KNOWN_ASSET_KINDS = new Set<string>(GAME_CREATION_APP_ASSET_KINDS);
|
||||
|
||||
export function isGameCreationAppAssetKind(
|
||||
raw: unknown,
|
||||
): raw is GameCreationAppAssetKind {
|
||||
return typeof raw === 'string' && KNOWN_ASSET_KINDS.has(raw);
|
||||
}
|
||||
|
||||
export function parseGameCreationAppAssetKind(
|
||||
raw: unknown,
|
||||
context: string,
|
||||
): GameCreationAppAssetKind {
|
||||
if (typeof raw === 'string' && KNOWN_ASSET_KINDS.has(raw)) {
|
||||
return raw as GameCreationAppAssetKind;
|
||||
if (isGameCreationAppAssetKind(raw)) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
const rawValue = typeof raw === 'string' ? raw : String(raw);
|
||||
|
||||
@@ -96,7 +96,10 @@ import {
|
||||
isFloatingOverlayWheelEvent,
|
||||
useImageCanvasFloatingOptionDismiss,
|
||||
} from '../../../../../src/components/image-editor/useImageCanvasFloatingOptionDismiss';
|
||||
import { parseGameCreationAppAssetKind } from '../../contracts/assetKind';
|
||||
import {
|
||||
isGameCreationAppAssetKind,
|
||||
parseGameCreationAppAssetKind,
|
||||
} from '../../contracts/assetKind';
|
||||
import { DesignWorkspacePanel } from '../../features/project-workspace/DesignWorkspacePanel';
|
||||
import {
|
||||
LocalGamePreviewFrame,
|
||||
@@ -6096,7 +6099,11 @@ export default function ProjectDevelopmentView({
|
||||
sourceResourceId: resource.id,
|
||||
sourcePath: resource.path,
|
||||
sourceMediaType: canonicalProjectedResourceMediaType(resource),
|
||||
sourceSubtype: resource.subtype,
|
||||
// sourceSubtype 只接受 manifest 的正式 kind;任务产物等非 manifest
|
||||
// 来源不通过该字段传递。
|
||||
sourceSubtype: isGameCreationAppAssetKind(resource.subtype)
|
||||
? resource.subtype
|
||||
: null,
|
||||
producerTaskId: resource.producerTaskId ?? '',
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -44,7 +44,9 @@ function rustAssetKindAliases(): Map<string, string> {
|
||||
|
||||
/** 从 Rust 侧解析 canonical kind 常量目录。 */
|
||||
function rustCanonicalKinds(): string[] {
|
||||
const source = readFileSync(RUST_CONTRACT_PATH, 'utf8');
|
||||
const source = expandRustAssetKindConsts(
|
||||
readFileSync(RUST_CONTRACT_PATH, 'utf8'),
|
||||
);
|
||||
const start = source.indexOf(
|
||||
'pub const GAME_CREATION_APP_CANONICAL_ASSET_KINDS',
|
||||
);
|
||||
@@ -53,6 +55,27 @@ function rustCanonicalKinds(): string[] {
|
||||
return Array.from(body.matchAll(/"([^"]+)"/g)).map((match) => match[1]!);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 Rust 侧 `pub const ..._ASSET_KIND: &str = "..."` 的常量引用展开成字符串字面量。
|
||||
*
|
||||
* 共享契约里 `ui-design-doc` 等 kind 是常量(如
|
||||
* `GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND`)而不是裸字面量,不展开时下面的正则解析会
|
||||
* 漏掉这些条目,两侧目录比对就会假红。
|
||||
*/
|
||||
function expandRustAssetKindConsts(source: string): string {
|
||||
const consts = new Map<string, string>();
|
||||
for (const decl of source.matchAll(
|
||||
/pub const (\w*ASSET_KIND\w*)\s*:\s*&str\s*=\s*"([^"]+)";/g,
|
||||
)) {
|
||||
consts.set(decl[1]!, decl[2]!);
|
||||
}
|
||||
let expanded = source;
|
||||
for (const [name, value] of consts) {
|
||||
expanded = expanded.replaceAll(name, `"${value}"`);
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
/** Rust 的 `GameCreationAppAssetCategory` 变体名 → kebab-case 线上值。 */
|
||||
function rustCategorySocketName(variant: string) {
|
||||
return variant.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
@@ -60,7 +83,9 @@ function rustCategorySocketName(variant: string) {
|
||||
|
||||
/** 从 Rust 侧 `GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND` 里解析出 kind → 栏目表。 */
|
||||
function rustAssetCategoryByKind(): Map<string, string> {
|
||||
const source = readFileSync(RUST_CONTRACT_PATH, 'utf8');
|
||||
const source = expandRustAssetKindConsts(
|
||||
readFileSync(RUST_CONTRACT_PATH, 'utf8'),
|
||||
);
|
||||
const start = source.indexOf(
|
||||
'pub const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND',
|
||||
);
|
||||
@@ -120,10 +145,9 @@ const OBSERVED_ALIAS_INPUTS = [
|
||||
|
||||
describe('资源 kind 别名表跨语言一致性', () => {
|
||||
test('两侧 canonical kind 目录一致', () => {
|
||||
'font',
|
||||
expect(rustCanonicalKinds()).toEqual([
|
||||
...GAME_CREATION_APP_CANONICAL_ASSET_KINDS,
|
||||
]);
|
||||
expect(rustCanonicalKinds()).toEqual([
|
||||
...GAME_CREATION_APP_CANONICAL_ASSET_KINDS,
|
||||
]);
|
||||
});
|
||||
|
||||
test('两侧别名映射逐条一致', () => {
|
||||
@@ -311,9 +335,9 @@ describe('写侧 kind 字面量 → 分类的端到端口径', () => {
|
||||
'apps/ai-game-creator-shell/src-tauri/src/commands.rs',
|
||||
'utf8',
|
||||
);
|
||||
// 字体上传的写入侧:`register_local_asset_entry(root, path, "font", …)`。
|
||||
// 字体上传的写入侧直接传正式枚举成员:`register_local_asset_entry(root, path, GameCreationAppAssetKind::Font, …)`。
|
||||
expect(commandsSource).toMatch(
|
||||
/register_local_asset_entry\(\s*\n\s*root,\s*\n\s*&relative_path,\s*\n\s*"font",/,
|
||||
/register_local_asset_entry\(\s*\n\s*root,\s*\n\s*&relative_path,\s*\n\s*GameCreationAppAssetKind::Font,/,
|
||||
);
|
||||
expect(gameCreationAppAssetCategoryForKind('font')).toBe('document');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user