- 入门指南
- 项目管理
- 项目操作和实用程序
- Test Manager 设置
- ALM 工具集成
- API 集成
- 故障排除
Test Manager 用户指南
.tmh文件是 UiPath Test Manager 使用的项目备份/迁移格式。本文档详细描述其结构、内容和编码要求。
存档结构
<package-name>.tmh ← ZIP archive
├── manifest.json ← Package metadata and object counts
└── objects/
├── requirements/
│ └── requirements-{n}.json
├── testcases/
│ └── testcases-{n}.json
├── teststeps/
│ └── teststeps-{n}.json
├── testsets/
│ └── testsets-{n}.json
├── testsettestcaseassignments/
│ └── testsettestcaseassignments-{n}.json
├── requirementtestcaseassignments/
│ └── requirementtestcaseassignments-{n}.json
├── objectlabels/
│ ├── objectlabels-testcase-{n}.json
│ ├── objectlabels-testset-{n}.json
│ ├── objectlabels-requirement-{n}.json
│ ├── objectlabels-testexecution-{n}.json
│ └── objectlabels-testcaselog-{n}.json
├── customfieldvalues/
│ ├── customfieldvalues-testcase-{n}.json
│ └── customfieldvalues-requirement-{n}.json
├── customfieldlabels/
│ └── customfieldlabels-{n}.json
├── customfielddefinitions/
│ └── customfielddefinitions-{n}.json
├── attachments/
│ ├── attachments-testexecution-{n}.json
│ ├── attachments-requirement-{n}.json
│ ├── attachments-testcaselog-{n}.json
│ ├── attachments-teststeplog-{n}.json
│ └── attachments-project-{n}.json
├── testexecutions/
│ ├── testexecutions-Manual-Finished-{n}.json
│ ├── testexecutions-Automated-Finished-{n}.json
│ └── testexecutions-Mixed-Finished-{n}.json
├── testcaselogs/
│ └── testcaselogs-{n}.json
├── teststeplogs/
│ └── teststeplogs-{n}.json
├── testcaselog-assertions/
│ └── testcaselogassertions-{n}.json
├── testcaselogresultoverrides/
│ └── testcaselogresultoverrides-{n}.json
├── assertion-attachments/
│ └── assertion-attachments-{n}.json
├── defects/
│ └── defects-{n}.json
├── parameters/
│ └── parameters-{n}.json
├── testsetpackages/
│ └── testsetpackages-{n}.json
├── testsettestcaseparameters/
│ └── testsettestcaseparameters-{n}.json
├── testsetschedules/
│ └── testsetschedules-testset-{n}.json
├── objectfilters/
│ └── objectfilters-testset-{n}.json
├── projectauthorization/
│ └── projectauthorization-{n}.json
├── projectsettings/
│ └── projectsettings.json ← singular, no number suffix
└── prompts/
└── prompts-{n}.json
<package-name>.tmh ← ZIP archive
├── manifest.json ← Package metadata and object counts
└── objects/
├── requirements/
│ └── requirements-{n}.json
├── testcases/
│ └── testcases-{n}.json
├── teststeps/
│ └── teststeps-{n}.json
├── testsets/
│ └── testsets-{n}.json
├── testsettestcaseassignments/
│ └── testsettestcaseassignments-{n}.json
├── requirementtestcaseassignments/
│ └── requirementtestcaseassignments-{n}.json
├── objectlabels/
│ ├── objectlabels-testcase-{n}.json
│ ├── objectlabels-testset-{n}.json
│ ├── objectlabels-requirement-{n}.json
│ ├── objectlabels-testexecution-{n}.json
│ └── objectlabels-testcaselog-{n}.json
├── customfieldvalues/
│ ├── customfieldvalues-testcase-{n}.json
│ └── customfieldvalues-requirement-{n}.json
├── customfieldlabels/
│ └── customfieldlabels-{n}.json
├── customfielddefinitions/
│ └── customfielddefinitions-{n}.json
├── attachments/
│ ├── attachments-testexecution-{n}.json
│ ├── attachments-requirement-{n}.json
│ ├── attachments-testcaselog-{n}.json
│ ├── attachments-teststeplog-{n}.json
│ └── attachments-project-{n}.json
├── testexecutions/
│ ├── testexecutions-Manual-Finished-{n}.json
│ ├── testexecutions-Automated-Finished-{n}.json
│ └── testexecutions-Mixed-Finished-{n}.json
├── testcaselogs/
│ └── testcaselogs-{n}.json
├── teststeplogs/
│ └── teststeplogs-{n}.json
├── testcaselog-assertions/
│ └── testcaselogassertions-{n}.json
├── testcaselogresultoverrides/
│ └── testcaselogresultoverrides-{n}.json
├── assertion-attachments/
│ └── assertion-attachments-{n}.json
├── defects/
│ └── defects-{n}.json
├── parameters/
│ └── parameters-{n}.json
├── testsetpackages/
│ └── testsetpackages-{n}.json
├── testsettestcaseparameters/
│ └── testsettestcaseparameters-{n}.json
├── testsetschedules/
│ └── testsetschedules-testset-{n}.json
├── objectfilters/
│ └── objectfilters-testset-{n}.json
├── projectauthorization/
│ └── projectauthorization-{n}.json
├── projectsettings/
│ └── projectsettings.json ← singular, no number suffix
└── prompts/
└── prompts-{n}.json
仅显示实际导出的对象类型。导入期间,系统会静默跳过缺少的文件夹/文件。
文件命名约定
大多数对象类型 — 编号块
对于系统生成的实际导出,文件名为<type>-{n}.json ,其中{n}从0开始。例如:
requirementtestcaseassignments-0.json
testcases-0.json
teststeps-0.json
requirementtestcaseassignments-0.json
testcases-0.json
teststeps-0.json
导入程序将扫描每个文件夹中的所有文件,因此在手动创建文件时,基于0和1的编号都有效。
大型导出可以拆分为多个文件( testcases-0.json 、 testcases-1.json等)— 导入程序会读取文件夹中的所有文件。
为获得最佳性能,请将每个文件最多限制为 500 个对象。
类型范围的文件名
有几种对象类型按其所属的对象类型对其文件进行范围限制。无论限定符如何,导入器都会读取文件夹中的所有文件,但实际导出始终使用以下名称:
| 对象类型 | 文件名称模式 |
|---|---|
| 对象标签 | objectlabels-testcase-{n}.json、 objectlabels-testset-{n}.json 、 objectlabels-requirement-{n}.json 、... |
| 自定义字段值 | customfieldvalues-testcase-{n}.json, customfieldvalues-requirement-{n}.json |
| 附件 | attachments-testexecution-{n}.json、 attachments-requirement-{n}.json 、... |
| 测试执行 | testexecutions-Manual-Finished-{n}.json、 testexecutions-Automated-Finished-{n}.json 、... |
| 对象筛选器 | objectfilters-testset-{n}.json |
| 测试集计划 | testsetschedules-testset-{n}.json |
项目设置 — 无数字后缀
projectsettings/projectsettings.json
projectsettings/projectsettings.json
此文件始终为单数(无块编号)。
编码要求
所有 JSON 文件都必须编码为UTF-8不带 BOM 。
PowerShell 5.1 的Set-Content -Encoding utf8写入 UTF-8 BOM ( EF BB BF )。这会导致浏览器中的JSON.parse在位置 0 处引发SyntaxError: Unexpected token从 PowerShell 创建.tmh文件时,始终使用[System.IO.File]::WriteAllText($path, $content, (New-Object System.Text.UTF8Encoding($false))) 。
架构版本控制
manifest.json包含schemaVersion字段(例如"1.0.16" )。导入器将此版本与每个 DTO 属性上的[IntroducedIn]属性一起使用来确定要读取的字段。反序列化期间将忽略在比包架构版本更新的版本中引入的字段。
当前最新版本: 1.0.16
版本历史记录摘要:
| 版本 | 重要新增内容 |
|---|---|
1.0.0 | 初始:需求、测试用例、测试步骤、测试集、分配、标签、附件、缺陷、自定义字段值 |
1.0.1 | preCondition 测试用例上 |
1.0.2 | 测试执行、测试用例日志结果覆盖、断言 |
1.0.4 | 子集导出( isSubsetExport 、 exportSubsetDetails ) |
1.0.5 | 自定义字段标签 |
1.0.7 | 用户定义的提示 |
1.0.9 | postCondition 测试用例上 |
1.0.10 | 参数 |
1.0.11 | 测试集包、测试集为folderKey / folderName 、测试用例为packageEntryPointUniqueId等 |
1.0.13 | id 附件字段 |
1.0.14 | studioWebFileId/ studioWebProjectId (对测试用例);测试集/测试用例分配为id |
1.0.15 | assigneeEmail 测试集/测试用例分配上 |
1.0.16 | 项目配置(设置、监管、签署人)、自定义字段定义、项目授权 |
Manifest.json
位于存档的根目录(不在objects/中)。
{
"objectCountDetails": {
"testCases": 5,
"testSets": 2,
"requirements": 3,
"objectLabels": 0,
"attachments": 0,
"testExecutions": 0,
"testCaseLogs": 0,
"testCaseResultOverrides": 0,
"testSteps": 12,
"testStepLogs": 0,
"testSetTestCaseAssignments": 6,
"requirementTestCaseAssignments": 4,
"defects": 0,
"customFieldValues": 0,
"customFieldLabels": 0,
"assertions": 0,
"assertionScreenshots": 0,
"testSetLabelFilters": 0,
"userDefinedPrompts": 0,
"parameters": 0,
"testSetPackages": 0,
"testSetTestCaseParameters": 0,
"customFieldDefinitions": 0,
"projectAuthorizations": 0
},
"project": {
"name": "My Project",
"description": "Project description",
"projectPrefix": "MP"
},
"tmPackageId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"schemaVersion": "1.0.16"
}
{
"objectCountDetails": {
"testCases": 5,
"testSets": 2,
"requirements": 3,
"objectLabels": 0,
"attachments": 0,
"testExecutions": 0,
"testCaseLogs": 0,
"testCaseResultOverrides": 0,
"testSteps": 12,
"testStepLogs": 0,
"testSetTestCaseAssignments": 6,
"requirementTestCaseAssignments": 4,
"defects": 0,
"customFieldValues": 0,
"customFieldLabels": 0,
"assertions": 0,
"assertionScreenshots": 0,
"testSetLabelFilters": 0,
"userDefinedPrompts": 0,
"parameters": 0,
"testSetPackages": 0,
"testSetTestCaseParameters": 0,
"customFieldDefinitions": 0,
"projectAuthorizations": 0
},
"project": {
"name": "My Project",
"description": "Project description",
"projectPrefix": "MP"
},
"tmPackageId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"schemaVersion": "1.0.16"
}
真实导出还包含packageName字段(建议的下载文件名)。导入程序会忽略它 — 在手动创建的文件中省略它。
objects/ — JSON 文件架构
每个对象文件都使用一个包装器对象,该包装器对象的单个属性是 JSON 数组。该数组包含单个对象记录。
requirements/{n}.json
{
"requirements": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "User can log in",
"description": "The login form must accept valid credentials.",
"foreignRef": "",
"connectorRequirementId": "00000000-0000-0000-0000-000000000000"
}
]
}
{
"requirements": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "User can log in",
"description": "The login form must accept valid credentials.",
"foreignRef": "",
"connectorRequirementId": "00000000-0000-0000-0000-000000000000"
}
]
}
| 字段 | 类型 | 注意 |
|---|---|---|
id | GUID 字符串 | 每个需求的唯一标识符 |
name | 字符串 | 必填项;最多 255 个字符 |
description | 字符串 | 可以为空字符串 |
foreignRef | 字符串 | 外部系统引用;可以为空 |
connectorRequirementId | GUID 字符串 | 未链接到连接器时,请使用"00000000-0000-0000-0000-000000000000" |
测试用例/{n}.json
{
"testCases": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"version": null,
"name": "Verify login with valid credentials",
"inputParams": null,
"description": "",
"automationId": null,
"automationTestCaseName": null,
"automationProjectName": null,
"foreignRef": "",
"connectorTestCaseId": null,
"preCondition": null,
"postCondition": null,
"packageEntryPointUniqueId": null,
"packageIdentifier": null,
"packageEntryPointName": null,
"feedId": null,
"packageSourceName": null,
"studioWebFileId": null,
"studioWebProjectId": null
}
]
}
{
"testCases": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"version": null,
"name": "Verify login with valid credentials",
"inputParams": null,
"description": "",
"automationId": null,
"automationTestCaseName": null,
"automationProjectName": null,
"foreignRef": "",
"connectorTestCaseId": null,
"preCondition": null,
"postCondition": null,
"packageEntryPointUniqueId": null,
"packageIdentifier": null,
"packageEntryPointName": null,
"feedId": null,
"packageSourceName": null,
"studioWebFileId": null,
"studioWebProjectId": null
}
]
}
| 字段 | 类型 | 注意 |
|---|---|---|
id | GUID 字符串 | 每个测试用例唯一 |
version | 字符串 |空 | null 针对手动测试用例; "1.0.0"适用于链接到包版本的自动化测试用例; ""适用于没有固定版本的自动化测试用例 |
name | 字符串 | 必填项;最多 255 个字符 |
inputParams | 字符串 |空 | JSON 编码的输入参数; null为手动 |
description | 字符串 | 富文本说明;不在时为"" |
automationId | 字符串 |空 | UiPath 自动化标识符; null为手动 |
automationTestCaseName | 字符串 |空 | 自动化显示名称; null为手动 |
automationProjectName | 字符串 |空 | 自动化项目; null为手动 |
foreignRef | 字符串 | 外部系统引用;不在时为"" |
connectorTestCaseId | 字符串 |空 | 连接器系统 ID;不在时为null |
preCondition | 字符串 |空 (v1.0.1+) | 前置条件文本; null不在时);最多 8000 个字符 |
postCondition | 字符串 |空 (v1.0.9 及以上版本) | 后置条件文本; null不在时);最多 8000 个字符 |
packageEntryPointUniqueId | 字符串 |空 (v1.0.11+) | 包入口点 GUID; null为手动 |
packageIdentifier | 字符串 |空 (v1.0.11+) | 包 ID; null为手动 |
packageEntryPointName | 字符串 |空 (v1.0.11+) | 入口点名称; null为手动 |
feedId | 字符串 |空 (v1.0.11+) | 包订阅源 ID; null为手动 |
packageSourceName | 字符串 |空 (v1.0.11+) | 包来源名称; null为手动 |
studioWebFileId | 字符串 | null (v1.0.14+) | Studio Web 文件 ID; null为手动 |
studioWebProjectId | 字符串 | null (v1.0.14+) | Studio Web 项目 ID; null为手动 |
teststeps/{n}.json
{
"testSteps": [
{
"id": "550e8400-e29b-41d4-a716-446655440010",
"testCaseId": "550e8400-e29b-41d4-a716-446655440001",
"orderNo": 0,
"actionType": null,
"description": "Navigate to the login page",
"expectedResult": "Login page is displayed",
"clipboardData": ""
}
]
}
{
"testSteps": [
{
"id": "550e8400-e29b-41d4-a716-446655440010",
"testCaseId": "550e8400-e29b-41d4-a716-446655440001",
"orderNo": 0,
"actionType": null,
"description": "Navigate to the login page",
"expectedResult": "Login page is displayed",
"clipboardData": ""
}
]
}
| 字段 | 类型 | 注意 |
|---|---|---|
id | GUID 字符串 | 每个步骤唯一 |
testCaseId | GUID 字符串 | 必须引用现有测试用例 id |
orderNo | 整数 | 从 0 开始的显示顺序(第一步 = 0 ) |
actionType | 字符串 |空 | null 适用于手动步骤 |
description | 字符串 | 步骤操作文本 |
expectedResult | 字符串 | 预期结果;不在时为"" |
clipboardData | 字符串 | 剪贴板/屏幕截图数据;最多 8000 个字符;通常 "" |
testets/{n}.json
{
"testSets": [
{
"id": "550e8400-e29b-41d4-a716-446655440020",
"version": null,
"name": "Regression Suite",
"description": "",
"source": "TestManager",
"externalTestSetId": null,
"sourceDetails": null,
"folderKey": null,
"folderName": ""
}
]
}
{
"testSets": [
{
"id": "550e8400-e29b-41d4-a716-446655440020",
"version": null,
"name": "Regression Suite",
"description": "",
"source": "TestManager",
"externalTestSetId": null,
"sourceDetails": null,
"folderKey": null,
"folderName": ""
}
]
}
| 字段 | 类型 | 注意 |
|---|---|---|
id | GUID 字符串 | 每个测试集唯一 |
version | 字符串 |空 | null 适用于 Test Manager 托管测试集 |
name | 字符串 | 必填项;最多 255 个字符 |
description | 字符串 | "" 缺少时 |
source | 字符串 | "TestManager" (默认)或"Orchestrator" —这是一个字符串,而不是整数 |
externalTestSetId | 字符串 |空 | 当source为"Orchestrator"时,外部 ID;否则为null |
sourceDetails | 字符串 |空 | 其他源元数据;不在时为null |
folderKey | 字符串 |空 (v1.0.11+) | UiPath Orchestrator 文件夹密钥;如果未链接, null |
folderName | 字符串 (v1.0.11+) | 文件夹显示名称;如果未链接,则使用"" ;未存储在数据库中,仅供参考 |
测试集测试用例分配/{n}.json
{
"testSetTestCaseAssignments": [
{
"id": "550e8400-e29b-41d4-a716-446655440030",
"testSetId": "550e8400-e29b-41d4-a716-446655440020",
"testCaseId": "550e8400-e29b-41d4-a716-446655440001",
"assigneeEmail": null
}
]
}
{
"testSetTestCaseAssignments": [
{
"id": "550e8400-e29b-41d4-a716-446655440030",
"testSetId": "550e8400-e29b-41d4-a716-446655440020",
"testCaseId": "550e8400-e29b-41d4-a716-446655440001",
"assigneeEmail": null
}
]
}
| 字段 | 类型 | 注意 |
|---|---|---|
id | GUID 字符串 (v1.0.14+) | 分配标识符 |
testSetId | GUID 字符串 | 引用测试集 id |
testCaseId | GUID 字符串 | 引用测试用例 id |
assigneeEmail | 字符串 | null (v1.0.15 及以上版本) | 受分配人电子邮箱;如果未分配,则为null |
需求测试用例分配/{n}.json
{
"requirementTestCaseAssignments": [
{
"requirementId": "550e8400-e29b-41d4-a716-446655440000",
"testCaseId": "550e8400-e29b-41d4-a716-446655440001"
}
]
}
{
"requirementTestCaseAssignments": [
{
"requirementId": "550e8400-e29b-41d4-a716-446655440000",
"testCaseId": "550e8400-e29b-41d4-a716-446655440001"
}
]
}
无id字段。与大多数其他对象类型不同,需求–测试用例分配只有requirementId和testCaseId 。
对象标签/对象标签-{objecttype}-{n}.json
文件命名:真实导出使用类型限定名称,例如objectlabels-testcase-1.json 、 objectlabels-testset-1.json 、 objectlabels-requirement-1.json等。导入程序会读取objectlabels/文件夹中的所有文件,因此在手动创建文件时,限定符不是严格要求,但建议您遵循约定。
{
"objectLabels": [
{
"objectId": "550e8400-e29b-41d4-a716-446655440001",
"name": "regression",
"description": "",
"labelType": 0,
"objectType": "TestCase"
}
]
}
{
"objectLabels": [
{
"objectId": "550e8400-e29b-41d4-a716-446655440001",
"name": "regression",
"description": "",
"labelType": 0,
"objectType": "TestCase"
}
]
}
| 字段 | 类型 | 注意 |
|---|---|---|
objectId | GUID 字符串 | 已标记对象的 ID |
name | 字符串 | 标签文本 |
description | 字符串 | 标签说明;不在时为"" |
labelType | 整数 | 1 = 系统标签(例如"manual" 、 "automated" ); 0 = 用户定义的标签 |
objectType | 字符串 | "TestCase"、"TestSet"、"Requirement"、"TestExecution"、"TestCaseLog" |
自定义字段值/{n}.json
{
"customFieldValues": [
{
"objectId": "550e8400-e29b-41d4-a716-446655440001",
"objectType": "TestCase",
"fieldName": "Priority",
"fieldValue": "High"
}
]
}
{
"customFieldValues": [
{
"objectId": "550e8400-e29b-41d4-a716-446655440001",
"objectType": "TestCase",
"fieldName": "Priority",
"fieldValue": "High"
}
]
}
projectsettings/projectsettings.json
在架构版本1.0.16中引入。仅在启用“导出项目配置”时显示。
{
"projectTimeZone": "Europe/Vienna",
"folderKey": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"governanceEnabled": true,
"governedByDefault": false,
"signatories": [
{
"signatoryId": "550e8400-e29b-41d4-a716-446655440099",
"signatoryType": 0,
"signatoryLastKnownDetails": "john.doe@company.com"
}
]
}
{
"projectTimeZone": "Europe/Vienna",
"folderKey": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"governanceEnabled": true,
"governedByDefault": false,
"signatories": [
{
"signatoryId": "550e8400-e29b-41d4-a716-446655440099",
"signatoryType": 0,
"signatoryLastKnownDetails": "john.doe@company.com"
}
]
}
与所有其他对象文件不同, projectsettings.json是直接对象(非包装器数组)。该文件包含单个平面 JSON 对象。
| 字段 | 类型 | 注意 |
|---|---|---|
projectTimeZone | 字符串 |空 | IANA 时区字符串(例如"Europe/Vienna" ) |
folderKey | GUID |空 | 链接的 Orchestrator 文件夹 |
governanceEnabled | 布尔值 |空 | 是否为项目启用了监管 |
governedByDefault | 布尔值 |空 | 是否默认监管新的测试用例 |
signatories | 数组 |空 | 审批人/签名人列表 |
signatories[].signatoryId | GUID | 用户或组 ID |
signatories[].signatoryType | 整数 | 0 = 用户, 1 = 用户组 |
signatories[].signatoryLastKnownDetails | 字符串 | 导出时的显示名称或电子邮件地址 |
系统有意将lastDisabledAt (监管禁用时间戳)从导出中排除——它是一个内部时间戳,对于重新导入的项目没有任何意义。
规则和约束
-
所有 ID 都必须是唯一的有效 GUID。不要在相同或不同类型的对象之间重复使用 ID。
-
必须解析交叉引用。测试步骤或分配中的
testCaseId必须引用同一包中存在的测试用例id。 -
包装器对象为必需。即使集合只有一个项目,也必须将其换行:
{ "testCases": [ {...} ] },而非纯数组或对象。 -
null与""约定:null真正不存在的可选字段(例如automationId、inputParams、preCondition、connectorTestCaseId以及手动测试用例上的所有package*字段)。""仅用于始终以字符串形式呈现但正好为空的字段(例如foreignRef、description、clipboardData)。混合使用这两种方法 — 尤其是在需要""的情况下使用null— 可能会导致静默导入失败。 -
清单文件中的
objectCountDetails应与存在的对象的实际计数相符,但导入程序不会严格验证计数 — 不匹配只会影响导入前摘要的显示。 -
所有文件都必须使用不带 BOM 的 UTF-8 编码。
最小有效 TMH 文件(手动编制)
Test Manager 接受的最小有效.tmh文件仅包含manifest.json :
{
"objectCountDetails": { ... },
"project": { "name": "Test Project", "description": "", "projectPrefix": "TP" },
"tmPackageId": "<new-guid>",
"schemaVersion": "1.0.16"
}
{
"objectCountDetails": { ... },
"project": { "name": "Test Project", "description": "", "projectPrefix": "TP" },
"tmPackageId": "<new-guid>",
"schemaVersion": "1.0.16"
}
所有objects/子目录都是可选的。导入程序会静默跳过存档中未包含的任何对象类型。
- 存档结构
- 文件命名约定
- 大多数对象类型 — 编号块
- 类型范围的文件名
- 项目设置 — 无数字后缀
- 编码要求
- 架构版本控制
- Manifest.json
- objects/ — JSON 文件架构
- requirements/{n}.json
- 测试用例/{n}.json
- teststeps/{n}.json
- testets/{n}.json
- 测试集测试用例分配/{n}.json
- 需求测试用例分配/{n}.json
- 对象标签/对象标签-{objecttype}-{n}.json
- 自定义字段值/{n}.json
- projectsettings/projectsettings.json
- 规则和约束
- 最小有效 TMH 文件(手动编制)