@lcap/nasl 4.4.0-beta.3 → 4.4.0-beta.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/generator/genBundleFiles.d.ts +2 -0
- package/out/generator/genBundleFiles.d.ts.map +1 -1
- package/out/generator/genBundleFiles.js +77 -12
- package/out/generator/genBundleFiles.js.map +1 -1
- package/out/generator/icestark.d.ts +1 -1
- package/out/generator/icestark.d.ts.map +1 -1
- package/out/generator/icestark.js +6 -1
- package/out/generator/icestark.js.map +1 -1
- package/out/generator/qiankun.d.ts +1 -1
- package/out/generator/qiankun.d.ts.map +1 -1
- package/out/generator/qiankun.js +9 -3
- package/out/generator/qiankun.js.map +1 -1
- package/out/generator/release-body/body.d.ts +0 -1
- package/out/generator/release-body/body.d.ts.map +1 -1
- package/out/generator/release-body/body.js +1 -390
- package/out/generator/release-body/body.js.map +1 -1
- package/out/generator/release-body/data.d.ts.map +1 -1
- package/out/generator/release-body/data.js +38 -11
- package/out/generator/release-body/data.js.map +1 -1
- package/out/generator/release-body/internal.d.ts +1 -0
- package/out/generator/release-body/internal.d.ts.map +1 -1
- package/out/generator/release-body/internal.js.map +1 -1
- package/out/generator/release-body/share-content.js +1 -1
- package/out/generator/release-body/share-content.js.map +1 -1
- package/out/generator/release-body/types.d.ts +2 -0
- package/out/generator/release-body/types.d.ts.map +1 -1
- package/out/generator/release-body/utils.d.ts +1 -0
- package/out/generator/release-body/utils.d.ts.map +1 -1
- package/out/generator/release-body/utils.js +2 -1
- package/out/generator/release-body/utils.js.map +1 -1
- package/out/generator/templates/chunk.js +1 -1
- package/out/generator/templates/runtime.js +2 -2
- package/out/generator/wujie.d.ts +1 -1
- package/out/generator/wujie.d.ts.map +1 -1
- package/out/generator/wujie.js +6 -1
- package/out/generator/wujie.js.map +1 -1
- package/out/natural/exports/nasl2Files.d.ts +34 -0
- package/out/natural/exports/nasl2Files.d.ts.map +1 -0
- package/out/natural/exports/nasl2Files.js +237 -0
- package/out/natural/exports/nasl2Files.js.map +1 -0
- package/out/natural/{transform2BatchActions.d.ts → exports/transform2BatchActions.d.ts} +20 -2
- package/out/natural/exports/transform2BatchActions.d.ts.map +1 -0
- package/out/natural/exports/transform2BatchActions.js +1233 -0
- package/out/natural/exports/transform2BatchActions.js.map +1 -0
- package/out/natural/index.d.ts +12 -12
- package/out/natural/index.d.ts.map +1 -1
- package/out/natural/index.js +12 -14
- package/out/natural/index.js.map +1 -1
- package/out/natural/{genNaturalTS.d.ts → old/getContext/genNaturalTS.d.ts} +12 -12
- package/out/natural/old/getContext/genNaturalTS.d.ts.map +1 -0
- package/out/natural/{genNaturalTS.js → old/getContext/genNaturalTS.js} +65 -65
- package/out/natural/old/getContext/genNaturalTS.js.map +1 -0
- package/out/natural/old/getContext/genTextualNASL.d.ts.map +1 -0
- package/out/natural/old/getContext/genTextualNASL.js.map +1 -0
- package/out/natural/{getContext → old/getContext}/getUILib.d.ts +1 -1
- package/out/natural/old/getContext/getUILib.d.ts.map +1 -0
- package/out/natural/{getContext → old/getContext}/getUILib.js +14 -14
- package/out/natural/old/getContext/getUILib.js.map +1 -0
- package/out/natural/{getContext → old/getContext}/index.d.ts +3 -3
- package/out/natural/old/getContext/index.d.ts.map +1 -0
- package/out/natural/{getContext → old/getContext}/index.js +4 -4
- package/out/natural/old/getContext/index.js.map +1 -0
- package/out/natural/old/getContext/naslStdlibMap.d.ts.map +1 -0
- package/out/natural/{getContext → old/getContext}/naslStdlibMap.js +10 -10
- package/out/natural/old/getContext/naslStdlibMap.js.map +1 -0
- package/out/natural/{transformTS2UI.d.ts → old/parser/transformTS2UI.d.ts} +2 -2
- package/out/natural/old/parser/transformTS2UI.d.ts.map +1 -0
- package/out/natural/{transformTS2UI.js → old/parser/transformTS2UI.js} +13 -14
- package/out/natural/old/parser/transformTS2UI.js.map +1 -0
- package/out/natural/{transformTSCode.d.ts → old/parser/transformTSCode.d.ts} +1 -1
- package/out/natural/old/parser/transformTSCode.d.ts.map +1 -0
- package/out/natural/{transformTSCode.js → old/parser/transformTSCode.js} +32 -8
- package/out/natural/old/parser/transformTSCode.js.map +1 -0
- package/out/natural/{tools.d.ts → old/tools.d.ts} +0 -4
- package/out/natural/old/tools.d.ts.map +1 -0
- package/out/natural/{tools.js → old/tools.js} +7 -25
- package/out/natural/old/tools.js.map +1 -0
- package/out/natural/{parseNaturalTS.d.ts → parser/parseNaturalTS.d.ts} +9 -2
- package/out/natural/parser/parseNaturalTS.d.ts.map +1 -0
- package/out/natural/{parseNaturalTS.js → parser/parseNaturalTS.js} +119 -38
- package/out/natural/parser/parseNaturalTS.js.map +1 -0
- package/out/natural/{parseNaturalTSXView.d.ts → parser/parseNaturalTSXView.d.ts} +1 -2
- package/out/natural/parser/parseNaturalTSXView.d.ts.map +1 -0
- package/out/natural/{parseNaturalTSXView.js → parser/parseNaturalTSXView.js} +181 -139
- package/out/natural/parser/parseNaturalTSXView.js.map +1 -0
- package/out/natural/transforms/checker/checkDataQueryCalleeNameOrder.d.ts +7 -0
- package/out/natural/transforms/checker/checkDataQueryCalleeNameOrder.d.ts.map +1 -0
- package/out/natural/transforms/checker/checkDataQueryCalleeNameOrder.js +38 -0
- package/out/natural/transforms/checker/checkDataQueryCalleeNameOrder.js.map +1 -0
- package/out/natural/transforms/checker/checkDefaultValue.d.ts +14 -0
- package/out/natural/transforms/checker/checkDefaultValue.d.ts.map +1 -0
- package/out/natural/transforms/checker/checkDefaultValue.js +39 -0
- package/out/natural/transforms/checker/checkDefaultValue.js.map +1 -0
- package/out/natural/transforms/checker/checkLcapEntity.d.ts +16 -0
- package/out/natural/transforms/checker/checkLcapEntity.d.ts.map +1 -0
- package/out/natural/transforms/checker/checkLcapEntity.js +337 -0
- package/out/natural/transforms/checker/checkLcapEntity.js.map +1 -0
- package/out/natural/transforms/checker/checkValidationCalleeName.d.ts +3 -0
- package/out/natural/transforms/checker/checkValidationCalleeName.d.ts.map +1 -0
- package/out/natural/transforms/checker/checkValidationCalleeName.js +88 -0
- package/out/natural/transforms/checker/checkValidationCalleeName.js.map +1 -0
- package/out/natural/transforms/checker/checkViewElement.d.ts +29 -0
- package/out/natural/transforms/checker/checkViewElement.d.ts.map +1 -0
- package/out/natural/transforms/checker/checkViewElement.js +618 -0
- package/out/natural/transforms/checker/checkViewElement.js.map +1 -0
- package/out/natural/transforms/checker/index.d.ts +19 -0
- package/out/natural/transforms/checker/index.d.ts.map +1 -0
- package/out/natural/transforms/checker/index.js +30 -0
- package/out/natural/transforms/checker/index.js.map +1 -0
- package/out/natural/transforms/transform2Entity.d.ts +13 -1
- package/out/natural/transforms/transform2Entity.d.ts.map +1 -1
- package/out/natural/transforms/transform2Entity.js +172 -17
- package/out/natural/transforms/transform2Entity.js.map +1 -1
- package/out/natural/transforms/transform2Enum.d.ts +1 -1
- package/out/natural/transforms/transform2Enum.d.ts.map +1 -1
- package/out/natural/transforms/transform2Enum.js +5 -1
- package/out/natural/transforms/transform2Enum.js.map +1 -1
- package/out/natural/transforms/transform2GlobalLogicDeclaration.d.ts +2 -2
- package/out/natural/transforms/transform2GlobalLogicDeclaration.d.ts.map +1 -1
- package/out/natural/transforms/transform2GlobalLogicDeclaration.js +18 -4
- package/out/natural/transforms/transform2GlobalLogicDeclaration.js.map +1 -1
- package/out/natural/transforms/transform2Logic.d.ts +4 -4
- package/out/natural/transforms/transform2Logic.d.ts.map +1 -1
- package/out/natural/transforms/transform2Logic.js +58 -17
- package/out/natural/transforms/transform2Logic.js.map +1 -1
- package/out/natural/transforms/transform2LogicItem.d.ts +11 -4
- package/out/natural/transforms/transform2LogicItem.d.ts.map +1 -1
- package/out/natural/transforms/transform2LogicItem.js +279 -178
- package/out/natural/transforms/transform2LogicItem.js.map +1 -1
- package/out/natural/transforms/transform2MetadataType.d.ts +1 -1
- package/out/natural/transforms/transform2MetadataType.d.ts.map +1 -1
- package/out/natural/transforms/transform2MetadataType.js +1 -1
- package/out/natural/transforms/transform2MetadataType.js.map +1 -1
- package/out/natural/transforms/transform2Param.d.ts +4 -0
- package/out/natural/transforms/transform2Param.d.ts.map +1 -0
- package/out/natural/transforms/transform2Param.js +114 -0
- package/out/natural/transforms/transform2Param.js.map +1 -0
- package/out/natural/transforms/transform2Structure.d.ts +1 -1
- package/out/natural/transforms/transform2Structure.d.ts.map +1 -1
- package/out/natural/transforms/transform2Structure.js +13 -2
- package/out/natural/transforms/transform2Structure.js.map +1 -1
- package/out/natural/transforms/transform2TypeAnnotation.d.ts +1 -1
- package/out/natural/transforms/transform2TypeAnnotation.d.ts.map +1 -1
- package/out/natural/transforms/transform2TypeAnnotation.js +3 -3
- package/out/natural/transforms/transform2TypeAnnotation.js.map +1 -1
- package/out/natural/transforms/transform2ValidationRule.d.ts +2 -2
- package/out/natural/transforms/transform2ValidationRule.d.ts.map +1 -1
- package/out/natural/transforms/transform2ValidationRule.js +6 -1
- package/out/natural/transforms/transform2ValidationRule.js.map +1 -1
- package/out/natural/transforms/transform2Variable.d.ts +4 -3
- package/out/natural/transforms/transform2Variable.d.ts.map +1 -1
- package/out/natural/transforms/transform2Variable.js +57 -6
- package/out/natural/transforms/transform2Variable.js.map +1 -1
- package/out/natural/transforms/transformThemeAndStyle.d.ts +5 -2
- package/out/natural/transforms/transformThemeAndStyle.d.ts.map +1 -1
- package/out/natural/transforms/transformThemeAndStyle.js +26 -13
- package/out/natural/transforms/transformThemeAndStyle.js.map +1 -1
- package/out/natural/transforms/utils/createComponentLookupMap.d.ts +32 -0
- package/out/natural/transforms/utils/createComponentLookupMap.d.ts.map +1 -0
- package/out/natural/transforms/utils/createComponentLookupMap.js +57 -0
- package/out/natural/transforms/utils/createComponentLookupMap.js.map +1 -0
- package/out/natural/transforms/utils/iconNames.d.ts +2 -0
- package/out/natural/transforms/utils/iconNames.d.ts.map +1 -0
- package/out/natural/transforms/utils/iconNames.js +299 -0
- package/out/natural/transforms/utils/iconNames.js.map +1 -0
- package/out/natural/transforms/utils/misc.d.ts +16 -0
- package/out/natural/transforms/utils/misc.d.ts.map +1 -0
- package/out/natural/transforms/utils/misc.js +240 -0
- package/out/natural/transforms/utils/misc.js.map +1 -0
- package/out/natural/transforms/utils/normalizeNode.d.ts +3 -0
- package/out/natural/transforms/utils/normalizeNode.d.ts.map +1 -0
- package/out/natural/transforms/utils/normalizeNode.js +15 -0
- package/out/natural/transforms/utils/normalizeNode.js.map +1 -0
- package/out/natural/transforms/utils/parseDecorator.d.ts +74 -0
- package/out/natural/transforms/utils/parseDecorator.d.ts.map +1 -0
- package/out/natural/transforms/utils/parseDecorator.js +252 -0
- package/out/natural/transforms/utils/parseDecorator.js.map +1 -0
- package/out/natural/transforms/utils/transform2Directory.d.ts +59 -0
- package/out/natural/transforms/utils/transform2Directory.d.ts.map +1 -0
- package/out/natural/transforms/utils/transform2Directory.js +229 -0
- package/out/natural/transforms/utils/transform2Directory.js.map +1 -0
- package/out/natural/{transforms/utils.d.ts → utils/index.d.ts} +35 -18
- package/out/natural/utils/index.d.ts.map +1 -0
- package/out/natural/utils/index.js +206 -0
- package/out/natural/utils/index.js.map +1 -0
- package/out/natural/utils/nasl-file-types.d.ts +45 -0
- package/out/natural/utils/nasl-file-types.d.ts.map +1 -0
- package/out/natural/utils/nasl-file-types.js +131 -0
- package/out/natural/utils/nasl-file-types.js.map +1 -0
- package/out/server/OQL/sqlDbTypes.json +717 -0
- package/out/server/createUiTs.d.ts +0 -1
- package/out/server/createUiTs.d.ts.map +1 -1
- package/out/server/createUiTs.js +1 -118
- package/out/server/createUiTs.js.map +1 -1
- package/out/server/extendBaseNode.d.ts.map +1 -1
- package/out/server/extendBaseNode.js +17 -0
- package/out/server/extendBaseNode.js.map +1 -1
- package/out/server/findReference.js +1 -1
- package/out/server/findReference.js.map +1 -1
- package/out/server/naslServer.d.ts +0 -1
- package/out/server/naslServer.d.ts.map +1 -1
- package/out/server/naslServer.js +66 -55
- package/out/server/naslServer.js.map +1 -1
- package/out/server/translator.js.map +1 -1
- package/out/service/autofix/rules/rule-convert-incompatible-assignment-to-union.js.map +1 -1
- package/out/service/creator/add.configs.js +1 -1
- package/out/service/creator/add.configs.js.map +1 -1
- package/out/service/storage/init.d.ts +1 -1
- package/out/service/storage/init.d.ts.map +1 -1
- package/out/service/storage/init.js +26 -9
- package/out/service/storage/init.js.map +1 -1
- package/out/service/storage/specTools.d.ts +20 -8
- package/out/service/storage/specTools.d.ts.map +1 -1
- package/out/service/storage/specTools.js +157 -5
- package/out/service/storage/specTools.js.map +1 -1
- package/out/templator/block2nasl/jsx2nasl/transform-func2nasl.d.ts +4 -0
- package/out/templator/block2nasl/jsx2nasl/transform-func2nasl.d.ts.map +1 -1
- package/out/templator/block2nasl/jsx2nasl/transform-func2nasl.js +20 -1
- package/out/templator/block2nasl/jsx2nasl/transform-func2nasl.js.map +1 -1
- package/out/templator/block2nasl/jsx2nasl/transform-tsx2nasl.d.ts.map +1 -1
- package/out/templator/block2nasl/jsx2nasl/transform-tsx2nasl.js +28 -1
- package/out/templator/block2nasl/jsx2nasl/transform-tsx2nasl.js.map +1 -1
- package/out/templator/block2nasl/transformBlock2Nasl.js +2 -2
- package/out/templator/block2nasl/transformBlock2Nasl.js.map +1 -1
- package/out/templator/block2nasl/viewMergeBlock.js +3 -2
- package/out/templator/block2nasl/viewMergeBlock.js.map +1 -1
- package/out/utils/can-i-use.d.ts.map +1 -1
- package/out/utils/can-i-use.js +1 -0
- package/out/utils/can-i-use.js.map +1 -1
- package/package.json +9 -9
- package/sandbox/stdlib/nasl.core.ts +2 -0
- package/sandbox/stdlib/nasl.io.ts +1 -1
- package/sandbox/stdlib/nasl.oql.ts +2 -0
- package/sandbox/stdlib/nasl.util.ts +1 -1
- package/sandbox/stdlib/nasl.validation.ts +50 -14
- package/out/natural/genNaturalTS.d.ts.map +0 -1
- package/out/natural/genNaturalTS.js.map +0 -1
- package/out/natural/genTextualNASL.d.ts.map +0 -1
- package/out/natural/genTextualNASL.js.map +0 -1
- package/out/natural/getContext/getUILib.d.ts.map +0 -1
- package/out/natural/getContext/getUILib.js.map +0 -1
- package/out/natural/getContext/index.d.ts.map +0 -1
- package/out/natural/getContext/index.js.map +0 -1
- package/out/natural/getContext/nasl2Files.d.ts +0 -15
- package/out/natural/getContext/nasl2Files.d.ts.map +0 -1
- package/out/natural/getContext/nasl2Files.js +0 -75
- package/out/natural/getContext/nasl2Files.js.map +0 -1
- package/out/natural/getContext/naslStdlibMap.d.ts.map +0 -1
- package/out/natural/getContext/naslStdlibMap.js.map +0 -1
- package/out/natural/parseNaturalTS.d.ts.map +0 -1
- package/out/natural/parseNaturalTS.js.map +0 -1
- package/out/natural/parseNaturalTSXView.d.ts.map +0 -1
- package/out/natural/parseNaturalTSXView.js.map +0 -1
- package/out/natural/tools.d.ts.map +0 -1
- package/out/natural/tools.js.map +0 -1
- package/out/natural/transform2BatchActions.d.ts.map +0 -1
- package/out/natural/transform2BatchActions.js +0 -387
- package/out/natural/transform2BatchActions.js.map +0 -1
- package/out/natural/transformTS2UI.d.ts.map +0 -1
- package/out/natural/transformTS2UI.js.map +0 -1
- package/out/natural/transformTSCode.d.ts.map +0 -1
- package/out/natural/transformTSCode.js.map +0 -1
- package/out/natural/transforms/utils.d.ts.map +0 -1
- package/out/natural/transforms/utils.js +0 -407
- package/out/natural/transforms/utils.js.map +0 -1
- /package/out/natural/{genTextualNASL.d.ts → old/getContext/genTextualNASL.d.ts} +0 -0
- /package/out/natural/{genTextualNASL.js → old/getContext/genTextualNASL.js} +0 -0
- /package/out/natural/{getContext → old/getContext}/naslStdlibMap.d.ts +0 -0
|
@@ -0,0 +1,1233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* 这个模块的主要功能是将多个代码文件操作(创建、更新、删除)转换为统一的nasl批量操作格式,
|
|
5
|
+
*
|
|
6
|
+
* 主要处理流程:
|
|
7
|
+
* 1. 解析codeAction 的路径,提取命名空间和项目类型信息
|
|
8
|
+
* 2. 将代码内容包装在适当的命名空间中
|
|
9
|
+
* 3. 使用 parseNaturalTS 解析 TypeScript 代码为 NASL 对象
|
|
10
|
+
* 4. 为每种类型(entity、structure、enum、logic、view)提供统一的操作接口
|
|
11
|
+
* 5. 生成批量操作数组,支持创建、更新和删除操作
|
|
12
|
+
* 6. 处理操作过程中的错误并提供调试信息
|
|
13
|
+
*
|
|
14
|
+
* 目前支持的类型:
|
|
15
|
+
* - entity: 数据实体
|
|
16
|
+
* - structure: 数据结构
|
|
17
|
+
* - enum: 枚举类型
|
|
18
|
+
* - logic: 业务逻辑
|
|
19
|
+
* - view: 视图组件
|
|
20
|
+
* - backendVariables: 后端变量
|
|
21
|
+
* - frontendVariables: 前端类型变量
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.applyBatchActions = exports.transform2BatchActions = exports.generateRouterContainerTemplate = void 0;
|
|
25
|
+
const uuid_1 = require("uuid");
|
|
26
|
+
const concepts_1 = require("../../concepts");
|
|
27
|
+
const index_1 = require("../../index");
|
|
28
|
+
const utils_1 = require("../../utils");
|
|
29
|
+
const nasl_file_types_1 = require("../utils/nasl-file-types");
|
|
30
|
+
/**
|
|
31
|
+
* 检查项目类型是否允许空名称
|
|
32
|
+
*/
|
|
33
|
+
const isNameOptional = (itemType) => {
|
|
34
|
+
return ['backendVariables', 'frontendVariables', 'theme'].includes(itemType);
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* 生成空的路由容器模板
|
|
38
|
+
* 用于为嵌套视图创建父级容器视图
|
|
39
|
+
* @param params - 包含视图名称、标题和命名空间的参数
|
|
40
|
+
* @returns 生成的路由容器模板代码
|
|
41
|
+
*/
|
|
42
|
+
const generateRouterContainerTemplate = (params) => {
|
|
43
|
+
const { viewName, title, namespace } = params;
|
|
44
|
+
return `
|
|
45
|
+
namespace ${namespace} {
|
|
46
|
+
$View({
|
|
47
|
+
title: "${title}",
|
|
48
|
+
crumb: '',
|
|
49
|
+
auth: false,
|
|
50
|
+
isIndex: true,
|
|
51
|
+
})
|
|
52
|
+
export function ${viewName}() {
|
|
53
|
+
return <ElRouterView />
|
|
54
|
+
}
|
|
55
|
+
}`;
|
|
56
|
+
};
|
|
57
|
+
exports.generateRouterContainerTemplate = generateRouterContainerTemplate;
|
|
58
|
+
/**
|
|
59
|
+
* 根据命名空间获取项目类型
|
|
60
|
+
* @param namespace - 命名空间字符串
|
|
61
|
+
* @returns 项目类型,如果无法识别则返回 'unknown'
|
|
62
|
+
*/
|
|
63
|
+
const getItemType = (namespace) => {
|
|
64
|
+
if (namespace.includes('entities'))
|
|
65
|
+
return 'entity';
|
|
66
|
+
if (namespace.includes('structures'))
|
|
67
|
+
return 'structure';
|
|
68
|
+
if (namespace.includes('enums'))
|
|
69
|
+
return 'enum';
|
|
70
|
+
if (namespace.includes('logics'))
|
|
71
|
+
return 'logic';
|
|
72
|
+
if (namespace.includes('views'))
|
|
73
|
+
return 'view';
|
|
74
|
+
if (namespace.includes('extensions'))
|
|
75
|
+
return 'extensions';
|
|
76
|
+
if (namespace.includes('app.backend.variables'))
|
|
77
|
+
return 'backendVariables';
|
|
78
|
+
if (namespace.includes('theme.css'))
|
|
79
|
+
return 'theme';
|
|
80
|
+
if (/app\.frontendTypes\.[^.]+\.frontends\.[^.]+\.variables/.test(namespace))
|
|
81
|
+
return 'frontendVariables';
|
|
82
|
+
return 'unknown';
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* 解析视图路径,提取视图名称和命名空间
|
|
86
|
+
* @param path - 视图路径,例如: 'src/app.frontendTypes.pc.frontends.pc.views.aaa.tsx'
|
|
87
|
+
* @returns 包含视图名称、命名空间和完整路径的对象
|
|
88
|
+
*/
|
|
89
|
+
const parseViewPath = (path) => {
|
|
90
|
+
// 移除 src/ 前缀和 .tsx 后缀
|
|
91
|
+
const cleanPath = path.replace(/^src\//, '').replace(/\.tsx$/, '');
|
|
92
|
+
// 提取最后一个部分作为视图名称
|
|
93
|
+
const lastDotIndex = cleanPath.lastIndexOf('.');
|
|
94
|
+
const viewName = lastDotIndex !== -1 ? cleanPath.substring(lastDotIndex + 1) : cleanPath;
|
|
95
|
+
const namespace = lastDotIndex !== -1 ? cleanPath.substring(0, lastDotIndex) : '';
|
|
96
|
+
return {
|
|
97
|
+
viewName,
|
|
98
|
+
namespace,
|
|
99
|
+
fullPath: path,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* 提取嵌套视图路径的所有父级路径信息
|
|
104
|
+
* @param path - 视图路径,例如: 'src/app.frontendTypes.pc.frontends.pc.views.aaa.views.bbb.views.ccc.tsx'
|
|
105
|
+
* @returns 父级路径信息数组,例如: [{ viewName: 'aaa', namespace: '...', fullPath: '...' }, ...]
|
|
106
|
+
*/
|
|
107
|
+
const extractParentViewPaths = (path) => {
|
|
108
|
+
const parentInfos = [];
|
|
109
|
+
// 检查是否是嵌套视图路径(至少包含两个 .views.)
|
|
110
|
+
const viewsMatches = path.match(/\.views\./g);
|
|
111
|
+
if (!viewsMatches || viewsMatches.length < 2) {
|
|
112
|
+
return parentInfos;
|
|
113
|
+
}
|
|
114
|
+
// 提取 views 部分: src/app.frontendTypes.pc.frontends.pc.views.aaa.views.bbb.views.ccc.tsx
|
|
115
|
+
// -> aaa.views.bbb.views.ccc
|
|
116
|
+
const regex = /src\/app\.frontendTypes\.pc\.frontends\.pc\.views\.(.*?)\.tsx$/;
|
|
117
|
+
const match = path.match(regex);
|
|
118
|
+
if (!match)
|
|
119
|
+
return parentInfos;
|
|
120
|
+
const viewPath = match[1]; // 例如: aaa.views.bbb.views.ccc
|
|
121
|
+
const parts = viewPath.split('.views.'); // ['aaa', 'bbb', 'ccc']
|
|
122
|
+
// 构建所有父级路径信息(不包括当前路径本身)
|
|
123
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
124
|
+
const pathParts = parts.slice(0, i + 1);
|
|
125
|
+
const parentPath = `src/app.frontendTypes.pc.frontends.pc.views.${pathParts.join('.views.')}.tsx`;
|
|
126
|
+
parentInfos.push(parseViewPath(parentPath));
|
|
127
|
+
}
|
|
128
|
+
return parentInfos;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* 构建嵌套视图的路径
|
|
132
|
+
* @param parentViewNames - 父级视图名称数组,例如: ['dashboard', 'teamLeader']
|
|
133
|
+
* @param viewName - 当前视图名称,例如: 'teamLeaveStatistics'
|
|
134
|
+
* @returns 构建的路径,例如: 'app.frontendTypes[name=pc].frontends[name=pc].views[name=dashboard].children[name=teamLeader].children[name=teamLeaveStatistics]'
|
|
135
|
+
*/
|
|
136
|
+
const buildNestedViewPath = (parentViewNames, viewName) => {
|
|
137
|
+
// 基础路径常量
|
|
138
|
+
const basePath = 'app.frontendTypes[name=pc].frontends[name=pc].views';
|
|
139
|
+
if (!parentViewNames || parentViewNames.length === 0) {
|
|
140
|
+
// 如果没有父级,返回顶级视图路径
|
|
141
|
+
return `${basePath}[name=${viewName}]`;
|
|
142
|
+
}
|
|
143
|
+
// 使用数组拼接优化字符串构建性能
|
|
144
|
+
const pathParts = [`${basePath}[name=${parentViewNames[0]}]`];
|
|
145
|
+
// 添加后续的父级视图(使用 children)
|
|
146
|
+
for (let i = 1; i < parentViewNames.length; i++) {
|
|
147
|
+
pathParts.push(`children[name=${parentViewNames[i]}]`);
|
|
148
|
+
}
|
|
149
|
+
// 添加当前视图
|
|
150
|
+
pathParts.push(`children[name=${viewName}]`);
|
|
151
|
+
return pathParts.join('.');
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* 创建视图查找函数
|
|
155
|
+
* 统一处理顶级视图和嵌套视图的查找逻辑
|
|
156
|
+
* @param instance - 应用实例(appInstance 或 app)
|
|
157
|
+
* @param name - 视图名称
|
|
158
|
+
* @param parentViewNames - 可选的父级视图名称数组
|
|
159
|
+
* @returns 找到的视图节点或 undefined
|
|
160
|
+
*/
|
|
161
|
+
const createViewFinder = (instance) => (name, parentViewNames = []) => {
|
|
162
|
+
// if (parentViewNames && parentViewNames.length > 0) {
|
|
163
|
+
// // 对于嵌套视图,使用路径查找
|
|
164
|
+
// }
|
|
165
|
+
const viewPath = buildNestedViewPath(parentViewNames, name);
|
|
166
|
+
return instance?.findNodeByPath(viewPath);
|
|
167
|
+
// 顶级视图使用 findViewByName
|
|
168
|
+
// return instance?.frontendTypes?.[0]?.frontends?.[0]?.findViewByName(name);
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* 类型守卫:判断是否为单节点处理器
|
|
172
|
+
*/
|
|
173
|
+
const isSingleNodeHandler = (handler) => {
|
|
174
|
+
return ['entity', 'structure', 'enum', 'logic'].includes(handler.type);
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* 类型守卫:判断是否为视图处理器
|
|
178
|
+
*/
|
|
179
|
+
const isViewHandler = (handler) => {
|
|
180
|
+
return handler.type === 'view';
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* 类型守卫:判断是否为全局变量处理器
|
|
184
|
+
*/
|
|
185
|
+
const isGlobalVariablesHandler = (handler) => {
|
|
186
|
+
return ['backendVariables', 'frontendVariables'].includes(handler.type);
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* 类型守卫:判断是否为主题处理器
|
|
190
|
+
*/
|
|
191
|
+
const isThemeHandler = (handler) => {
|
|
192
|
+
return handler.type === 'theme';
|
|
193
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* 创建不同类型处理器的工厂函数
|
|
196
|
+
* 为每种类型(entity、structure、enum、logic、view、backendVariables、frontendVariables)提供统一的操作接口
|
|
197
|
+
* @param appInstance - 解析后的应用实例,用于查找新创建的对象
|
|
198
|
+
* @param app - 目前已有的应用实例,用于查找已存在的对象和添加新对象
|
|
199
|
+
* @returns 包含各种类型处理器的对象
|
|
200
|
+
*/
|
|
201
|
+
const createTypeHandlers = (appInstance, app) => ({
|
|
202
|
+
entity: {
|
|
203
|
+
type: 'entity',
|
|
204
|
+
/** 实体在应用中的路径模板 */
|
|
205
|
+
path: 'app.dataSources[name=defaultDS].entities[0]',
|
|
206
|
+
/** 从解析实例中查找实体的方法 */
|
|
207
|
+
findMethod: (name) => appInstance?.dataSources?.[0]?.findEntityByName(name),
|
|
208
|
+
/** 从目标应用中查找已存在实体的方法 */
|
|
209
|
+
findExisting: (name) => app.dataSources?.[0]?.findEntityByName(name),
|
|
210
|
+
},
|
|
211
|
+
structure: {
|
|
212
|
+
type: 'structure',
|
|
213
|
+
/** 结构体在应用中的路径模板 */
|
|
214
|
+
path: 'app.structures[0]',
|
|
215
|
+
/** 从解析实例中查找结构体的方法 */
|
|
216
|
+
findMethod: (name) => appInstance?.findStructureByName(name),
|
|
217
|
+
/** 从目标应用中查找已存在结构体的方法 */
|
|
218
|
+
findExisting: (name) => app.findStructureByName(name),
|
|
219
|
+
},
|
|
220
|
+
enum: {
|
|
221
|
+
type: 'enum',
|
|
222
|
+
/** 枚举在应用中的路径模板 */
|
|
223
|
+
path: 'app.enums[0]',
|
|
224
|
+
/** 从解析实例中查找枚举的方法 */
|
|
225
|
+
findMethod: (name) => appInstance?.findEnumByName(name),
|
|
226
|
+
/** 从目标应用中查找已存在枚举的方法 */
|
|
227
|
+
findExisting: (name) => app.findEnumByName(name),
|
|
228
|
+
},
|
|
229
|
+
logic: {
|
|
230
|
+
type: 'logic',
|
|
231
|
+
/** 逻辑在应用中的路径模板 */
|
|
232
|
+
path: 'app.logics[0]',
|
|
233
|
+
/** 从解析实例中查找逻辑的方法 */
|
|
234
|
+
findMethod: (name) => appInstance?.findLogicByName(name),
|
|
235
|
+
/** 从目标应用中查找已存在逻辑的方法 */
|
|
236
|
+
findExisting: (name) => app.findLogicByName(name),
|
|
237
|
+
},
|
|
238
|
+
view: {
|
|
239
|
+
type: 'view',
|
|
240
|
+
/** 视图在应用中的路径模板 */
|
|
241
|
+
path: 'app.frontendTypes[name=pc].frontends[name=pc].views[0]',
|
|
242
|
+
/** 从解析实例中查找视图的方法 */
|
|
243
|
+
findMethod: createViewFinder(appInstance),
|
|
244
|
+
/** 从目标应用中查找已存在视图的方法 */
|
|
245
|
+
findExisting: createViewFinder(app),
|
|
246
|
+
},
|
|
247
|
+
backendVariables: {
|
|
248
|
+
type: 'backendVariables',
|
|
249
|
+
/** 后端全局变量在应用中的路径 */
|
|
250
|
+
path: 'app.backend.variables[0]',
|
|
251
|
+
/** 从解析实例中查找后端变量 */
|
|
252
|
+
findMethod: (name) => {
|
|
253
|
+
if (!name)
|
|
254
|
+
return appInstance?.backend;
|
|
255
|
+
const path = `app.backend.variables[name=${name}]`;
|
|
256
|
+
return appInstance?.findNodeByPath(path);
|
|
257
|
+
},
|
|
258
|
+
/** 从目标应用中查找已存在的后端变量 */
|
|
259
|
+
findExisting: (name) => {
|
|
260
|
+
if (!name)
|
|
261
|
+
return app.backend;
|
|
262
|
+
const path = `app.backend.variables[name=${name}]`;
|
|
263
|
+
return app.findNodeByPath(path);
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
frontendVariables: {
|
|
267
|
+
type: 'frontendVariables',
|
|
268
|
+
/** 前端全局变量在应用中的路径 */
|
|
269
|
+
path: 'app.frontendTypes[name=pc].frontends[name=pc].variables[0]',
|
|
270
|
+
/** 从解析实例中查找前端变量 */
|
|
271
|
+
findMethod: (name) => {
|
|
272
|
+
if (!name)
|
|
273
|
+
return appInstance?.frontendTypes?.[0]?.frontends?.[0];
|
|
274
|
+
const path = `app.frontendTypes[name=pc].frontends[name=pc].variables[name=${name}]`;
|
|
275
|
+
return appInstance?.findNodeByPath(path);
|
|
276
|
+
},
|
|
277
|
+
/** 从目标应用中查找已存在的前端变量 */
|
|
278
|
+
findExisting: (name) => {
|
|
279
|
+
if (!name)
|
|
280
|
+
return app.frontendTypes?.[0]?.frontends?.[0];
|
|
281
|
+
const path = `app.frontendTypes[name=pc].frontends[name=pc].variables[name=${name}]`;
|
|
282
|
+
return app.findNodeByPath(path);
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
theme: {
|
|
286
|
+
type: 'theme',
|
|
287
|
+
/** 主题在应用中的路径 */
|
|
288
|
+
path: 'app.frontendTypes[name=pc].frontends[name=pc].theme',
|
|
289
|
+
/** 从解析实例中查找主题 */
|
|
290
|
+
findMethod: () => appInstance?.frontendTypes?.[0]?.frontends?.[0]?.theme,
|
|
291
|
+
/** 从目标应用中查找已存在的主题 */
|
|
292
|
+
findExisting: () => app.frontendTypes?.[0]?.frontends?.[0]?.theme,
|
|
293
|
+
},
|
|
294
|
+
// 后续需要扩展 extensions 的
|
|
295
|
+
});
|
|
296
|
+
/**
|
|
297
|
+
* 通用删除操作处理
|
|
298
|
+
* @param params - 删除操作参数
|
|
299
|
+
*/
|
|
300
|
+
const handleDeleteOperation = (params) => {
|
|
301
|
+
const { handler, itemName, itemType, batchActions, errors, parentViewNames, errorMessageTemplate = `要删除的对象不存在: ${itemName}`, } = params;
|
|
302
|
+
// 根据 handler 类型调用不同的 findExisting 方法
|
|
303
|
+
let existingNode;
|
|
304
|
+
if (isViewHandler(handler)) {
|
|
305
|
+
existingNode = handler.findExisting(itemName, parentViewNames);
|
|
306
|
+
}
|
|
307
|
+
else if (isSingleNodeHandler(handler)) {
|
|
308
|
+
existingNode = handler.findExisting(itemName);
|
|
309
|
+
}
|
|
310
|
+
else if (isGlobalVariablesHandler(handler)) {
|
|
311
|
+
existingNode = handler.findExisting(itemName);
|
|
312
|
+
}
|
|
313
|
+
if (existingNode) {
|
|
314
|
+
batchActions.actions.push({
|
|
315
|
+
action: 'delete',
|
|
316
|
+
path: existingNode.nodePath,
|
|
317
|
+
object: null,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
errors.push({
|
|
322
|
+
item: itemName,
|
|
323
|
+
type: itemType,
|
|
324
|
+
error: new Error(errorMessageTemplate),
|
|
325
|
+
level: 1,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* 处理变量创建或更新操作
|
|
331
|
+
*/
|
|
332
|
+
const handleVariableCreateOrUpdate = (handler, variable, variableName, batchActions) => {
|
|
333
|
+
const existingVar = handler.findExisting(variableName);
|
|
334
|
+
const variableObject = variable.toJSON();
|
|
335
|
+
if (existingVar) {
|
|
336
|
+
// 更新操作
|
|
337
|
+
batchActions.actions.push({
|
|
338
|
+
action: 'update',
|
|
339
|
+
path: existingVar.nodePath,
|
|
340
|
+
object: variableObject,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
// 创建操作
|
|
345
|
+
batchActions.actions.push({
|
|
346
|
+
action: 'create',
|
|
347
|
+
path: handler.path,
|
|
348
|
+
object: variableObject,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* 处理全局变量类型(一个文件对应多个节点)
|
|
354
|
+
*/
|
|
355
|
+
const processGlobalVariables = (params) => {
|
|
356
|
+
const { handler, action, itemType, batchActions, errors } = params;
|
|
357
|
+
// 系统保留变量,不允许删除、创建或更新
|
|
358
|
+
const RESERVED_VARIABLES = new Set(['httpRequest', 'httpResponse', 'currentUser']);
|
|
359
|
+
// 获取容器节点(backend 或 frontend)
|
|
360
|
+
const container = handler.findMethod();
|
|
361
|
+
if (!container || !container.variables) {
|
|
362
|
+
errors.push({
|
|
363
|
+
item: itemType,
|
|
364
|
+
type: itemType,
|
|
365
|
+
error: new Error(`未找到${itemType}容器或variables属性`),
|
|
366
|
+
level: 2,
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
// 处理删除操作的特殊情况
|
|
371
|
+
if (action === 'delete') {
|
|
372
|
+
for (const variable of container.variables) {
|
|
373
|
+
const variableName = variable.name;
|
|
374
|
+
if (!variableName || RESERVED_VARIABLES.has(variableName))
|
|
375
|
+
continue;
|
|
376
|
+
try {
|
|
377
|
+
handleDeleteOperation({
|
|
378
|
+
handler,
|
|
379
|
+
itemName: variableName,
|
|
380
|
+
itemType,
|
|
381
|
+
batchActions,
|
|
382
|
+
errors,
|
|
383
|
+
errorMessageTemplate: `要删除的变量不存在: ${variableName}`,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
errors.push({
|
|
388
|
+
item: variableName,
|
|
389
|
+
type: itemType,
|
|
390
|
+
error: error,
|
|
391
|
+
level: 3,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
// 处理创建和更新操作
|
|
398
|
+
// 一次性获取现有容器,避免重复调用
|
|
399
|
+
const existingContainer = handler.findExisting();
|
|
400
|
+
// 构建新变量名的 Set(只在有现有容器时才需要)
|
|
401
|
+
const newVariableNames = existingContainer?.variables ? new Set() : null;
|
|
402
|
+
// 第一遍:处理新变量的创建/更新
|
|
403
|
+
for (const variable of container.variables) {
|
|
404
|
+
const variableName = variable.name;
|
|
405
|
+
if (!variableName || RESERVED_VARIABLES.has(variableName))
|
|
406
|
+
continue;
|
|
407
|
+
// 同时收集变量名(避免额外的 map 操作)
|
|
408
|
+
newVariableNames?.add(variableName);
|
|
409
|
+
try {
|
|
410
|
+
handleVariableCreateOrUpdate(handler, variable, variableName, batchActions);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
errors.push({
|
|
414
|
+
item: variableName,
|
|
415
|
+
type: itemType,
|
|
416
|
+
error: error,
|
|
417
|
+
level: 3,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// 第二遍:处理需要删除的旧变量(app 中有但 appInstance 中没有的,目前以 spec 生成为准,如果后续不需要,这块可以隐藏)
|
|
422
|
+
if (existingContainer?.variables && newVariableNames) {
|
|
423
|
+
for (const existingVariable of existingContainer.variables) {
|
|
424
|
+
const existingVariableName = existingVariable.name;
|
|
425
|
+
if (!existingVariableName ||
|
|
426
|
+
RESERVED_VARIABLES.has(existingVariableName) ||
|
|
427
|
+
newVariableNames.has(existingVariableName)) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
// 变量在 appInstance 中不存在,按 delete 处理
|
|
431
|
+
try {
|
|
432
|
+
handleDeleteOperation({
|
|
433
|
+
handler,
|
|
434
|
+
itemName: existingVariableName,
|
|
435
|
+
itemType,
|
|
436
|
+
batchActions,
|
|
437
|
+
errors,
|
|
438
|
+
errorMessageTemplate: `要删除的变量不存在: ${existingVariableName}`,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
errors.push({
|
|
443
|
+
item: existingVariableName,
|
|
444
|
+
type: itemType,
|
|
445
|
+
error: error,
|
|
446
|
+
level: 3,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
/**
|
|
453
|
+
* 计算 view 类型的路径
|
|
454
|
+
*/
|
|
455
|
+
const calculateViewPath = (naslObject) => {
|
|
456
|
+
let type = 'views';
|
|
457
|
+
if (naslObject.parentNode?.concept === 'View') {
|
|
458
|
+
type = 'children';
|
|
459
|
+
}
|
|
460
|
+
const parentChildren = naslObject.parentNode?.[type] || [];
|
|
461
|
+
const childIndex = parentChildren.findIndex((child) => child === naslObject);
|
|
462
|
+
const index = childIndex !== -1 ? childIndex : 0;
|
|
463
|
+
return `${naslObject.parentNode.nodePath}.${type}[${index}]`;
|
|
464
|
+
};
|
|
465
|
+
/**
|
|
466
|
+
* 处理 view 类型的 children 属性
|
|
467
|
+
*/
|
|
468
|
+
const handleViewChildren = (object, action) => {
|
|
469
|
+
if (action === 'create') {
|
|
470
|
+
// 对于创建操作,需要将 children 数组转换为空数组
|
|
471
|
+
object.children = [];
|
|
472
|
+
}
|
|
473
|
+
else if (action === 'update') {
|
|
474
|
+
// 对于更新操作,删除 children 属性
|
|
475
|
+
if (object.children) {
|
|
476
|
+
delete object.children;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
481
|
+
* 处理单节点创建或更新操作
|
|
482
|
+
*/
|
|
483
|
+
const handleSingleNodeCreateOrUpdate = (handler, itemName, itemType, parentViewNames, batchActions, errors) => {
|
|
484
|
+
// 从解析的实例中获取 NASL 对象
|
|
485
|
+
let naslObject;
|
|
486
|
+
// 根据 handler 类型调用不同的 findMethod
|
|
487
|
+
if (isViewHandler(handler)) {
|
|
488
|
+
naslObject = handler.findMethod(itemName, parentViewNames);
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
naslObject = handler.findMethod(itemName);
|
|
492
|
+
}
|
|
493
|
+
if (!naslObject) {
|
|
494
|
+
errors.push({
|
|
495
|
+
item: itemName,
|
|
496
|
+
type: itemType,
|
|
497
|
+
error: new Error(`未找到对应的 NASL 对象: ${handler.path} - ${itemName}`),
|
|
498
|
+
level: 2,
|
|
499
|
+
});
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
let finalAction = 'create';
|
|
503
|
+
let path = '';
|
|
504
|
+
let object = naslObject.toJSON();
|
|
505
|
+
// 特殊处理:对于 view 类型,计算正确的路径
|
|
506
|
+
if (itemType === 'view' && naslObject.parentNode) {
|
|
507
|
+
path = calculateViewPath(naslObject);
|
|
508
|
+
}
|
|
509
|
+
// 检查目标应用中是否已存在同名对象
|
|
510
|
+
let existingNode;
|
|
511
|
+
if (isViewHandler(handler)) {
|
|
512
|
+
existingNode = handler.findExisting(itemName, parentViewNames);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
existingNode = handler.findExisting(itemName);
|
|
516
|
+
}
|
|
517
|
+
if (existingNode) {
|
|
518
|
+
// 存在同名对象,标记为更新操作
|
|
519
|
+
finalAction = 'update';
|
|
520
|
+
path = existingNode.nodePath;
|
|
521
|
+
}
|
|
522
|
+
// 特殊处理 view 类型的 children 属性
|
|
523
|
+
if (itemType === 'view') {
|
|
524
|
+
handleViewChildren(object, finalAction);
|
|
525
|
+
}
|
|
526
|
+
// 添加创建或更新操作到批量操作列表
|
|
527
|
+
batchActions.actions.push({
|
|
528
|
+
action: finalAction,
|
|
529
|
+
path: path || handler.path,
|
|
530
|
+
object,
|
|
531
|
+
});
|
|
532
|
+
};
|
|
533
|
+
/**
|
|
534
|
+
* 处理单节点类型(一个文件对应一个节点)
|
|
535
|
+
*/
|
|
536
|
+
const processSingleNode = (params) => {
|
|
537
|
+
const { handler, itemName, itemType, action, parentViewNames, batchActions, errors } = params;
|
|
538
|
+
if (action === 'delete') {
|
|
539
|
+
// 删除操作:使用通用删除方法
|
|
540
|
+
handleDeleteOperation({
|
|
541
|
+
handler,
|
|
542
|
+
itemName,
|
|
543
|
+
itemType,
|
|
544
|
+
batchActions,
|
|
545
|
+
errors,
|
|
546
|
+
parentViewNames,
|
|
547
|
+
errorMessageTemplate: `要删除的对象不存在: ${handler.path} - ${itemName}`,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
// 创建或更新操作
|
|
552
|
+
handleSingleNodeCreateOrUpdate(handler, itemName, itemType, parentViewNames, batchActions, errors);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
/**
|
|
556
|
+
* 检测并生成缺失的父级视图模板
|
|
557
|
+
* @param path - 当前项的路径
|
|
558
|
+
* @param codeArray - 代码数组,用于添加生成的模板代码
|
|
559
|
+
* @param processedPaths - 已处理的路径集合,用于去重
|
|
560
|
+
* @param errors - 错误收集数组
|
|
561
|
+
*/
|
|
562
|
+
const processParentViews = (path, codeArray, processedPaths, errors) => {
|
|
563
|
+
// 获取当前项的所有父级路径信息(按层级从浅到深排序)
|
|
564
|
+
const parentInfos = extractParentViewPaths(path);
|
|
565
|
+
// 为缺失的父级视图生成模板并添加到 codeArray
|
|
566
|
+
for (const { viewName, namespace, fullPath } of parentInfos) {
|
|
567
|
+
if (!processedPaths.has(fullPath)) {
|
|
568
|
+
try {
|
|
569
|
+
const template = (0, exports.generateRouterContainerTemplate)({
|
|
570
|
+
viewName,
|
|
571
|
+
title: viewName,
|
|
572
|
+
namespace,
|
|
573
|
+
});
|
|
574
|
+
// 直接将模板代码添加到 codeArray
|
|
575
|
+
codeArray.push(template);
|
|
576
|
+
// 标记为已处理
|
|
577
|
+
processedPaths.add(fullPath);
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
errors.push({
|
|
581
|
+
item: fullPath,
|
|
582
|
+
type: 'generateParentView',
|
|
583
|
+
error: error,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
/**
|
|
590
|
+
* 转换单个codeAction
|
|
591
|
+
* 将codeAction 中的路径和内容转换为可解析的格式
|
|
592
|
+
* @param path - 文件路径
|
|
593
|
+
* @param content - 文件内容
|
|
594
|
+
* @param action - 操作类型
|
|
595
|
+
* @returns 包含转换后代码、项目类型、项目名称、操作类型和父级视图名称数组的对象
|
|
596
|
+
*/
|
|
597
|
+
const transformCodeAction = ({ path, content, action, }) => {
|
|
598
|
+
// 移除路径前缀 'src/',并解析命名空间
|
|
599
|
+
const namespace = path.replace(/^src\//, '');
|
|
600
|
+
const namespaceParts = namespace.split('.');
|
|
601
|
+
// 根据命名空间确定项目类型(需要先确定类型)
|
|
602
|
+
const itemType = getItemType(namespace);
|
|
603
|
+
// 提取项目名称和父命名空间,对于变量类型需要特殊处理
|
|
604
|
+
let itemName;
|
|
605
|
+
let parentNamespace;
|
|
606
|
+
if (itemType === 'backendVariables' || itemType === 'frontendVariables') {
|
|
607
|
+
// 对于变量类型,路径格式为: app.backend.variables.ts 或 app.frontendTypes.pc.frontends.pc.variables.ts
|
|
608
|
+
// itemName 设为空字符串(因为这是整个 variables 文件)
|
|
609
|
+
// parentNamespace 为去掉 .ts 的部分,即保留到 variables
|
|
610
|
+
itemName = '';
|
|
611
|
+
parentNamespace = namespaceParts.slice(0, -1).join('.');
|
|
612
|
+
}
|
|
613
|
+
else if (itemType === 'theme') {
|
|
614
|
+
// 对于主题类型,路径格式为: app.frontendTypes.pc.frontends.pc.theme.css
|
|
615
|
+
itemName = '';
|
|
616
|
+
parentNamespace = namespaceParts.slice(0, -1).join('.');
|
|
617
|
+
}
|
|
618
|
+
else if (itemType === 'extensions') {
|
|
619
|
+
parentNamespace = namespaceParts.slice(0, -1).join('.');
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
// 其他类型使用原有逻辑,即单文件模式(例如一个实体对应一个文件)
|
|
623
|
+
itemName = namespaceParts.length >= 2 ? namespaceParts[namespaceParts.length - 2] : '';
|
|
624
|
+
parentNamespace = namespaceParts.slice(0, -2).join('.');
|
|
625
|
+
}
|
|
626
|
+
// 生成转换后的代码,为非未知类型添加命名空间包装
|
|
627
|
+
let transformedCode = content;
|
|
628
|
+
if (itemType === 'theme') {
|
|
629
|
+
transformedCode = `namespace ${parentNamespace} {\n $theme\`${content}\`;\n}\n`;
|
|
630
|
+
}
|
|
631
|
+
else if (itemType !== 'unknown' && parentNamespace) {
|
|
632
|
+
// 对于视图类型,需要额外的缩进
|
|
633
|
+
const indent = itemType === 'view' ? ' ' : '';
|
|
634
|
+
transformedCode = `namespace ${parentNamespace} {\n${indent}${content}\n}\n`;
|
|
635
|
+
}
|
|
636
|
+
// 如果是 view 类型,提取父级视图名称数组
|
|
637
|
+
// 优化:只在需要时计算 parentViewNames
|
|
638
|
+
const parentViewNames = itemType === 'view' ? extractParentViewPaths(path).map((info) => info.viewName) : undefined;
|
|
639
|
+
return {
|
|
640
|
+
transformedCode,
|
|
641
|
+
itemType,
|
|
642
|
+
itemName,
|
|
643
|
+
action,
|
|
644
|
+
...(parentViewNames && parentViewNames.length > 0 && { parentViewNames }),
|
|
645
|
+
};
|
|
646
|
+
};
|
|
647
|
+
/**
|
|
648
|
+
* 将 codeAction 数组转换为批量操作
|
|
649
|
+
* 这是整个模块的核心函数,负责将多个codeAction 转换成统一的批量操作格式
|
|
650
|
+
*
|
|
651
|
+
* 处理流程:
|
|
652
|
+
* 1. 解析每个 codeAction ,提取类型和名称信息
|
|
653
|
+
* 2. 将需要解析的代码合并并通过 parseNaturalTS 解析
|
|
654
|
+
* 3. 为每个操作生成对应的 NASL 对象
|
|
655
|
+
* 4. 构建批量操作数组,支持创建、更新和删除操作
|
|
656
|
+
* 5. 处理错误并返回结果
|
|
657
|
+
*
|
|
658
|
+
* @param codeActions - codeAction 数组,每个元素包含 path、content、action
|
|
659
|
+
* @param app - NASL 应用实例,用于操作目标应用
|
|
660
|
+
* @param sessionId - 会话 ID,用于生成唯一标识
|
|
661
|
+
* @param type - 操作类型,'normal' 为正常模式,'debug' 为调试模式(会返回更多信息)
|
|
662
|
+
* @param files - 可选的完整文件列表,包含现有文件和新文件
|
|
663
|
+
* @returns 包含批量操作和可选调试信息的对象
|
|
664
|
+
*/
|
|
665
|
+
const transform2BatchActions = ({ codeActions, app, sessionId, type = 'normal', }) => {
|
|
666
|
+
// 开始性能计时
|
|
667
|
+
const startTime = performance.now();
|
|
668
|
+
// 存储转换后的代码片段(用于解析)
|
|
669
|
+
const codeArray = [];
|
|
670
|
+
// 存储每个项目的类型、名称、操作信息和父级视图名称
|
|
671
|
+
const itemTypes = [];
|
|
672
|
+
// 存储处理过程中的错误
|
|
673
|
+
const errors = [];
|
|
674
|
+
// 记录已处理的路径,避免重复生成父级视图
|
|
675
|
+
const processedPaths = new Set();
|
|
676
|
+
// 第一阶段:转换所有codeAction
|
|
677
|
+
for (const item of codeActions) {
|
|
678
|
+
try {
|
|
679
|
+
// 检查文件后缀,目前只处理 .ts、.tsx 和 .css 文件
|
|
680
|
+
if (!item.path.endsWith('.ts') &&
|
|
681
|
+
!item.path.endsWith('.tsx') &&
|
|
682
|
+
!item.path.endsWith('.css')) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (!(0, nasl_file_types_1.isKnownFileType)(item.path)) {
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
// 检测并生成缺失的父级视图
|
|
689
|
+
if (item.path?.includes('app.frontendTypes.pc.frontends.')) {
|
|
690
|
+
processParentViews(item.path, codeArray, processedPaths, errors);
|
|
691
|
+
}
|
|
692
|
+
// 处理当前项
|
|
693
|
+
const { transformedCode, itemType, itemName, action, parentViewNames } = transformCodeAction(item);
|
|
694
|
+
// 跳过未知类型的 codeAction/暂时禁用 extensions
|
|
695
|
+
if (itemType === 'unknown' || itemType === 'extensions')
|
|
696
|
+
continue;
|
|
697
|
+
// 只有非删除操作才需要累积代码用于解析,因为删除操作不需要解析新的 NASL 对象
|
|
698
|
+
if (action !== 'delete') {
|
|
699
|
+
codeArray.push(transformedCode);
|
|
700
|
+
}
|
|
701
|
+
if (action !== 'exist') {
|
|
702
|
+
itemTypes.push({ type: itemType, name: itemName, action, parentViewNames });
|
|
703
|
+
}
|
|
704
|
+
processedPaths.add(item.path);
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
errors.push({
|
|
708
|
+
item: item.path,
|
|
709
|
+
type: 'transformCodeAction',
|
|
710
|
+
error: error,
|
|
711
|
+
level: 0,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// 第二阶段:解析累积的代码
|
|
716
|
+
// 将所有需要解析的代码合并为一个字符串(性能优化)
|
|
717
|
+
const accumulatedTsCode = codeArray.join('');
|
|
718
|
+
// 只有当有需要解析的代码时才调用 parseNaturalTS
|
|
719
|
+
let appInstance = null;
|
|
720
|
+
let parseJSON = null;
|
|
721
|
+
if (accumulatedTsCode) {
|
|
722
|
+
// 解析 TypeScript 代码为 NASL 对象
|
|
723
|
+
try {
|
|
724
|
+
parseJSON = (0, index_1.parseNaturalTS)(accumulatedTsCode, {});
|
|
725
|
+
if (!parseJSON)
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
console.error('转换失败:', error);
|
|
730
|
+
console.error('原始数据:', codeActions);
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
// 生成 AI 唯一标识并创建应用实例
|
|
734
|
+
const { node: nodes } = (0, index_1.genAIUID)(parseJSON.toJSON(), sessionId);
|
|
735
|
+
appInstance = new concepts_1.App(nodes);
|
|
736
|
+
}
|
|
737
|
+
// 初始化批量操作对象
|
|
738
|
+
const batchActions = {
|
|
739
|
+
uuid: (0, uuid_1.v4)().replace(/-/g, ''), // 生成无连字符的 UUID
|
|
740
|
+
actions: [],
|
|
741
|
+
actionMsg: '批量操作数据模型和视图',
|
|
742
|
+
sourceType: 'spec_driven',
|
|
743
|
+
};
|
|
744
|
+
// 第三阶段:生成批量操作
|
|
745
|
+
// 获取不同类型的处理器
|
|
746
|
+
const typeHandlers = createTypeHandlers(appInstance, app);
|
|
747
|
+
// 收集所有要删除的视图名称,用于跳过冗余的子视图删除操作
|
|
748
|
+
const deletedViewNames = new Set(itemTypes
|
|
749
|
+
.filter(({ type, action }) => type === 'view' && action === 'delete')
|
|
750
|
+
.map(({ name }) => name));
|
|
751
|
+
// 遍历每个项目,生成对应的批量操作
|
|
752
|
+
for (const { type: itemType, name: itemName, action, parentViewNames } of itemTypes) {
|
|
753
|
+
// 跳过未知类型,或者非可选名称但无名称的项目
|
|
754
|
+
if (itemType === 'unknown' || (!isNameOptional(itemType) && !itemName))
|
|
755
|
+
continue;
|
|
756
|
+
// 如果父级视图已在删除列表中,子视图无需单独删除(父视图删除时会级联删除子视图)
|
|
757
|
+
if (itemType === 'view' && action === 'delete' && parentViewNames?.some((p) => deletedViewNames.has(p))) {
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
const handler = typeHandlers[itemType];
|
|
762
|
+
if (!handler)
|
|
763
|
+
continue;
|
|
764
|
+
// 使用类型守卫进行精确的类型判断和处理
|
|
765
|
+
if (isThemeHandler(handler)) {
|
|
766
|
+
// 主题类型:无需参数
|
|
767
|
+
const naslObject = handler.findMethod();
|
|
768
|
+
if (naslObject) {
|
|
769
|
+
const existingNode = handler.findExisting();
|
|
770
|
+
batchActions.actions.push({
|
|
771
|
+
action: 'update',
|
|
772
|
+
path: existingNode?.nodePath || handler.path,
|
|
773
|
+
object: {
|
|
774
|
+
// 暂时只支持 scopeVariableMap + cssRules
|
|
775
|
+
...existingNode.toJSON(),
|
|
776
|
+
scopeVariableMap: naslObject.scopeVariableMap,
|
|
777
|
+
cssRules: naslObject.cssRules,
|
|
778
|
+
},
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
else if (isGlobalVariablesHandler(handler)) {
|
|
783
|
+
// 全局变量类型:一个文件对应多个节点
|
|
784
|
+
processGlobalVariables({
|
|
785
|
+
handler,
|
|
786
|
+
action,
|
|
787
|
+
itemType,
|
|
788
|
+
batchActions,
|
|
789
|
+
errors,
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
else if (isViewHandler(handler)) {
|
|
793
|
+
// 视图类型:一个文件对应一个节点,支持嵌套视图
|
|
794
|
+
processSingleNode({
|
|
795
|
+
handler,
|
|
796
|
+
itemName,
|
|
797
|
+
itemType,
|
|
798
|
+
action,
|
|
799
|
+
parentViewNames,
|
|
800
|
+
batchActions,
|
|
801
|
+
errors,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
else if (isSingleNodeHandler(handler)) {
|
|
805
|
+
// 单节点类型:entity, structure, enum, logic
|
|
806
|
+
processSingleNode({
|
|
807
|
+
handler,
|
|
808
|
+
itemName,
|
|
809
|
+
itemType,
|
|
810
|
+
action,
|
|
811
|
+
parentViewNames,
|
|
812
|
+
batchActions,
|
|
813
|
+
errors,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
// 记录处理过程中的错误
|
|
819
|
+
errors.push({
|
|
820
|
+
item: itemName,
|
|
821
|
+
type: itemType,
|
|
822
|
+
error: error,
|
|
823
|
+
level: 3,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
// 第五阶段:同步 directories(只处理 appInstance 中已存在的)
|
|
828
|
+
if (appInstance && app) {
|
|
829
|
+
handleAppDirectories(appInstance, app, batchActions, errors);
|
|
830
|
+
}
|
|
831
|
+
// 第四阶段:错误处理和结果返回
|
|
832
|
+
// 如果有错误发生,在控制台输出错误信息(但不中断执行)
|
|
833
|
+
if (errors.length > 0) {
|
|
834
|
+
console.error('批量处理过程中发现错误:', errors);
|
|
835
|
+
}
|
|
836
|
+
// 构建返回结果,基础结果包含批量操作
|
|
837
|
+
let result = {
|
|
838
|
+
batchActions,
|
|
839
|
+
accumulatedTsCode, // 累积的 TypeScript 代码
|
|
840
|
+
};
|
|
841
|
+
// 调试模式下返回额外的调试信息
|
|
842
|
+
if (type === 'debug') {
|
|
843
|
+
result = {
|
|
844
|
+
...result,
|
|
845
|
+
parseJSON, // 解析后的 JSON 对象
|
|
846
|
+
errors, // 错误列表
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
// 计算并输出执行时间
|
|
850
|
+
const endTime = performance.now();
|
|
851
|
+
console.log(`transform2BatchActions 执行时间: ${endTime - startTime}ms`);
|
|
852
|
+
return result;
|
|
853
|
+
};
|
|
854
|
+
exports.transform2BatchActions = transform2BatchActions;
|
|
855
|
+
/**
|
|
856
|
+
* 构建全局 definition 索引
|
|
857
|
+
* 一次性构建,避免重复遍历
|
|
858
|
+
* @param allDirs - 所有目录数组
|
|
859
|
+
* @returns 全局索引对象
|
|
860
|
+
*/
|
|
861
|
+
const buildGlobalDefIndex = (allDirs) => {
|
|
862
|
+
const defMap = new Map();
|
|
863
|
+
const dirDefMap = new Map();
|
|
864
|
+
for (const dir of allDirs) {
|
|
865
|
+
if (!dir?.definitions || !dir.name)
|
|
866
|
+
continue;
|
|
867
|
+
const defKeys = new Set();
|
|
868
|
+
for (const def of dir.definitions) {
|
|
869
|
+
if (!def)
|
|
870
|
+
continue;
|
|
871
|
+
const key = `${def.namespace}:${def.name}`;
|
|
872
|
+
defMap.set(key, {
|
|
873
|
+
def,
|
|
874
|
+
dirName: dir.name,
|
|
875
|
+
dirPath: dir.nodePath,
|
|
876
|
+
});
|
|
877
|
+
defKeys.add(key);
|
|
878
|
+
}
|
|
879
|
+
if (defKeys.size > 0) {
|
|
880
|
+
dirDefMap.set(dir.name, defKeys);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return { defMap, dirDefMap };
|
|
884
|
+
};
|
|
885
|
+
/**
|
|
886
|
+
* 深度比较两个 definition 是否需要更新
|
|
887
|
+
* 只比较关键属性,避免不必要的序列化
|
|
888
|
+
* @param appDef - app 中的 definition
|
|
889
|
+
* @param instanceDef - appInstance 中的 definition
|
|
890
|
+
* @returns 是否需要更新
|
|
891
|
+
*/
|
|
892
|
+
const needsDefinitionUpdate = (appDef, instanceDef) => {
|
|
893
|
+
// 快速检查:引用相等
|
|
894
|
+
if (appDef === instanceDef)
|
|
895
|
+
return false;
|
|
896
|
+
// 关键属性比较
|
|
897
|
+
return (appDef.referredConcept !== instanceDef.referredConcept ||
|
|
898
|
+
appDef.namespace !== instanceDef.namespace ||
|
|
899
|
+
appDef.name !== instanceDef.name);
|
|
900
|
+
};
|
|
901
|
+
/**
|
|
902
|
+
* 同步目录的 definitions
|
|
903
|
+
* 使用 Map 替代数组查找,使用全局索引避免重复遍历
|
|
904
|
+
* @param instanceDir - appInstance 中的目录
|
|
905
|
+
* @param appDir - app 中对应的目录
|
|
906
|
+
* @param globalDefIndex - 全局 definition 索引
|
|
907
|
+
* @param pendingActions - 待添加的批量操作数组
|
|
908
|
+
* @param errors - 错误收集数组
|
|
909
|
+
*/
|
|
910
|
+
const syncDirectoryDefinitions = (instanceDir, appDir, globalDefIndex, pendingActions, errors) => {
|
|
911
|
+
try {
|
|
912
|
+
// 快速检查
|
|
913
|
+
if (!instanceDir?.definitions || instanceDir.definitions.length === 0) {
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
// 构建当前目录的 definition Map
|
|
917
|
+
const appDefMap = new Map((appDir?.definitions || []).map((def) => [`${def.namespace}:${def.name}`, def]));
|
|
918
|
+
for (const instanceDef of instanceDir.definitions) {
|
|
919
|
+
if (!instanceDef)
|
|
920
|
+
continue;
|
|
921
|
+
const key = `${instanceDef.namespace}:${instanceDef.name}`;
|
|
922
|
+
const defObject = instanceDef.toJSON();
|
|
923
|
+
// 第一步:在当前目录查找
|
|
924
|
+
const appDefInCurrentDir = appDefMap.get(key);
|
|
925
|
+
if (appDefInCurrentDir) {
|
|
926
|
+
// 在当前目录找到了,检查是否需要更新
|
|
927
|
+
if (needsDefinitionUpdate(appDefInCurrentDir, instanceDef)) {
|
|
928
|
+
// 内容不同,更新(先删除再创建)
|
|
929
|
+
pendingActions.push({
|
|
930
|
+
action: 'delete',
|
|
931
|
+
path: appDefInCurrentDir.nodePath,
|
|
932
|
+
object: null,
|
|
933
|
+
}, {
|
|
934
|
+
action: 'create',
|
|
935
|
+
path: `${appDir.nodePath}.definitions[0]`,
|
|
936
|
+
object: defObject,
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
// 内容相同则跳过
|
|
940
|
+
}
|
|
941
|
+
else {
|
|
942
|
+
// 第二步:使用全局索引查找其他目录
|
|
943
|
+
const defInOtherDir = globalDefIndex.defMap.get(key);
|
|
944
|
+
if (defInOtherDir && defInOtherDir.dirName !== appDir.name) {
|
|
945
|
+
// 在其他文件夹找到了 → 文件移动场景
|
|
946
|
+
// 先删除旧位置的引用
|
|
947
|
+
pendingActions.push({
|
|
948
|
+
action: 'delete',
|
|
949
|
+
path: defInOtherDir.def.nodePath,
|
|
950
|
+
object: null,
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
// 然后在当前目录创建新引用(无论是移动还是新建)
|
|
954
|
+
pendingActions.push({
|
|
955
|
+
action: 'create',
|
|
956
|
+
path: `${appDir.nodePath}.definitions[0]`,
|
|
957
|
+
object: defObject,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
catch (error) {
|
|
963
|
+
errors.push({
|
|
964
|
+
item: instanceDir?.name || 'unknown',
|
|
965
|
+
type: 'definitionSync',
|
|
966
|
+
error: error,
|
|
967
|
+
level: 4,
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
/**
|
|
972
|
+
* 删除新目录中的 definitions 在其他目录中的旧引用
|
|
973
|
+
* 使用全局索引,避免重复构建 Map
|
|
974
|
+
* @param instanceDir - 新目录
|
|
975
|
+
* @param globalDefIndex - 全局 definition 索引
|
|
976
|
+
* @param pendingActions - 待添加的批量操作数组
|
|
977
|
+
*/
|
|
978
|
+
const deleteOldDefinitionReferences = (instanceDir, globalDefIndex, pendingActions) => {
|
|
979
|
+
if (!instanceDir?.definitions || instanceDir.definitions.length === 0) {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
// 直接使用全局索引查找
|
|
983
|
+
for (const instanceDef of instanceDir.definitions) {
|
|
984
|
+
if (!instanceDef)
|
|
985
|
+
continue;
|
|
986
|
+
const key = `${instanceDef.namespace}:${instanceDef.name}`;
|
|
987
|
+
const oldDef = globalDefIndex.defMap.get(key);
|
|
988
|
+
if (oldDef) {
|
|
989
|
+
// 找到了旧引用,删除它
|
|
990
|
+
pendingActions.push({
|
|
991
|
+
action: 'delete',
|
|
992
|
+
path: oldDef.def.nodePath,
|
|
993
|
+
object: null,
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
/**
|
|
999
|
+
* 同步单个目录
|
|
1000
|
+
* 通用的目录同步逻辑,适用于所有类型的目录
|
|
1001
|
+
* @param instanceDir - appInstance 中的目录
|
|
1002
|
+
* @param appDir - app 中对应的目录(可能不存在)
|
|
1003
|
+
* @param globalDefIndex - 全局 definition 索引
|
|
1004
|
+
* @param parentPath - 父级路径
|
|
1005
|
+
* @param pendingActions - 待添加的批量操作数组
|
|
1006
|
+
* @param errors - 错误收集数组
|
|
1007
|
+
* @param errorType - 错误类型标识
|
|
1008
|
+
*/
|
|
1009
|
+
const syncSingleDirectory = (instanceDir, appDir, globalDefIndex, parentPath, pendingActions, errors, errorType) => {
|
|
1010
|
+
try {
|
|
1011
|
+
if (!appDir) {
|
|
1012
|
+
// 目录不存在,需要创建
|
|
1013
|
+
// 先删除该目录中 definitions 在其他目录的旧引用
|
|
1014
|
+
deleteOldDefinitionReferences(instanceDir, globalDefIndex, pendingActions);
|
|
1015
|
+
// 然后创建完整的新目录
|
|
1016
|
+
const dirObject = instanceDir.toJSON();
|
|
1017
|
+
pendingActions.push({
|
|
1018
|
+
action: 'create',
|
|
1019
|
+
path: parentPath,
|
|
1020
|
+
object: dirObject,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
// 目录存在,同步 definitions
|
|
1025
|
+
syncDirectoryDefinitions(instanceDir, appDir, globalDefIndex, pendingActions, errors);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
catch (error) {
|
|
1029
|
+
errors.push({
|
|
1030
|
+
item: instanceDir?.name || 'unknown',
|
|
1031
|
+
type: errorType,
|
|
1032
|
+
error: error,
|
|
1033
|
+
level: 4,
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
/**
|
|
1038
|
+
* 处理 app directories 同步
|
|
1039
|
+
* 处理四种 directories: 'LOGICS', 'ENUMS', 'STRUCTURES' 以及 appInstance.dataSources[0] 的 directories
|
|
1040
|
+
* 比对 directories 及引用 definitions,最小粒度是 definition,唯一标识是 namespace+name
|
|
1041
|
+
* 文件夹只做新增更新,definition 如果在同一领域的文件夹下,需要先删除原引用,再创建新引用
|
|
1042
|
+
*
|
|
1043
|
+
* 性能优化:
|
|
1044
|
+
* 1. 全局 definition 索引缓存 - 避免重复构建
|
|
1045
|
+
* 3. 批量操作收集 - 减少数组扩容
|
|
1046
|
+
* 4. 提前验证和快速退出 - 避免无效处理
|
|
1047
|
+
* 5. 优化的 definition 比较 - 只比较关键属性
|
|
1048
|
+
*/
|
|
1049
|
+
const handleAppDirectories = (appInstance, app, batchActions, errors) => {
|
|
1050
|
+
// 性能计时开始
|
|
1051
|
+
const startTime = performance.now();
|
|
1052
|
+
// 1. 边界检查和快速退出
|
|
1053
|
+
if (!appInstance || !app) {
|
|
1054
|
+
errors.push({
|
|
1055
|
+
item: 'handleAppDirectories',
|
|
1056
|
+
type: 'invalidParams',
|
|
1057
|
+
error: new Error('appInstance 或 app 参数无效'),
|
|
1058
|
+
level: 5,
|
|
1059
|
+
});
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
try {
|
|
1063
|
+
// 2. 快速检查是否有需要处理的目录
|
|
1064
|
+
const hasInstanceDirs = appInstance.directories?.some((dir) => ['LOGICS', 'ENUMS', 'STRUCTURES'].includes(dir?.name) && dir.children?.length > 0);
|
|
1065
|
+
const hasDataSourceDirs = appInstance.dataSources?.[0]?.directories?.length > 0;
|
|
1066
|
+
if (!hasInstanceDirs && !hasDataSourceDirs) {
|
|
1067
|
+
console.log('[handleAppDirectories] 无需处理目录同步');
|
|
1068
|
+
return; // 无需处理
|
|
1069
|
+
}
|
|
1070
|
+
// 3. 批量操作收集器(减少数组扩容)
|
|
1071
|
+
const pendingActions = [];
|
|
1072
|
+
// 4. 处理根目录类型 (LOGICS, ENUMS, STRUCTURES)
|
|
1073
|
+
const rootDirectoryTypes = ['LOGICS', 'ENUMS', 'STRUCTURES'];
|
|
1074
|
+
for (const rootType of rootDirectoryTypes) {
|
|
1075
|
+
try {
|
|
1076
|
+
const instanceRootDir = appInstance.directories?.find((dir) => dir?.name === rootType);
|
|
1077
|
+
if (!instanceRootDir?.children?.length) {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
let appRootDir = app.directories?.find((dir) => dir?.name === rootType);
|
|
1081
|
+
if (!appRootDir) {
|
|
1082
|
+
// 根目录不存在,直接创建
|
|
1083
|
+
pendingActions.push({
|
|
1084
|
+
action: 'create',
|
|
1085
|
+
path: 'app.directories[0]',
|
|
1086
|
+
object: instanceRootDir.toJSON(),
|
|
1087
|
+
});
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
// 5. 构建子目录 Map(O(1) 查找,替代数组遍历)
|
|
1091
|
+
const allAppChildDirs = appRootDir.children || [];
|
|
1092
|
+
const appChildDirMap = new Map(allAppChildDirs.filter((dir) => dir?.name).map((dir) => [dir.name, dir]));
|
|
1093
|
+
// 6. 构建全局 definition 索引(一次性构建)
|
|
1094
|
+
const globalDefIndex = buildGlobalDefIndex(allAppChildDirs);
|
|
1095
|
+
// 7. 遍历子目录
|
|
1096
|
+
for (const instanceChildDir of instanceRootDir.children) {
|
|
1097
|
+
if (!instanceChildDir?.name)
|
|
1098
|
+
continue;
|
|
1099
|
+
const appChildDir = appChildDirMap.get(instanceChildDir.name);
|
|
1100
|
+
syncSingleDirectory(instanceChildDir, appChildDir, globalDefIndex, `${appRootDir.nodePath}.children[0]`, pendingActions, errors, 'directorySync');
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
catch (error) {
|
|
1104
|
+
errors.push({
|
|
1105
|
+
item: rootType,
|
|
1106
|
+
type: 'rootDirectorySync',
|
|
1107
|
+
error: error,
|
|
1108
|
+
level: 4,
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
// 8. 处理数据源 (Entity) directories
|
|
1113
|
+
if (appInstance.dataSources?.[0]?.directories && app.dataSources?.[0]) {
|
|
1114
|
+
try {
|
|
1115
|
+
const instanceDataSource = appInstance.dataSources[0];
|
|
1116
|
+
const appDataSource = app.dataSources[0];
|
|
1117
|
+
const allAppEntityDirs = appDataSource.directories || [];
|
|
1118
|
+
// 构建目录 Map(O(1) 查找)
|
|
1119
|
+
const entityDirMap = new Map(allAppEntityDirs.filter((dir) => dir?.name).map((dir) => [dir.name, dir]));
|
|
1120
|
+
// 构建全局 definition 索引
|
|
1121
|
+
const globalDefIndex = buildGlobalDefIndex(allAppEntityDirs);
|
|
1122
|
+
for (const instanceDir of instanceDataSource.directories) {
|
|
1123
|
+
if (!instanceDir?.name)
|
|
1124
|
+
continue;
|
|
1125
|
+
const appDir = entityDirMap.get(instanceDir.name);
|
|
1126
|
+
syncSingleDirectory(instanceDir, appDir, globalDefIndex, `${appDataSource.nodePath}.directories[0]`, pendingActions, errors, 'entityDirectorySync');
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
catch (error) {
|
|
1130
|
+
errors.push({
|
|
1131
|
+
item: 'dataSources',
|
|
1132
|
+
type: 'dataSourceDirectorySync',
|
|
1133
|
+
error: error,
|
|
1134
|
+
level: 4,
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
// 9. 批量添加所有操作(一次性 push,减少数组扩容)
|
|
1139
|
+
if (pendingActions.length > 0) {
|
|
1140
|
+
batchActions.actions.push(...pendingActions);
|
|
1141
|
+
console.log(`[handleAppDirectories] 生成 ${pendingActions.length} 个批量操作`);
|
|
1142
|
+
}
|
|
1143
|
+
// 性能计时结束
|
|
1144
|
+
const endTime = performance.now();
|
|
1145
|
+
console.log(`[handleAppDirectories] 执行时间: ${(endTime - startTime).toFixed(2)}ms`);
|
|
1146
|
+
}
|
|
1147
|
+
catch (error) {
|
|
1148
|
+
errors.push({
|
|
1149
|
+
item: 'handleAppDirectories',
|
|
1150
|
+
type: 'directorySync',
|
|
1151
|
+
error: error,
|
|
1152
|
+
level: 4,
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
/**
|
|
1157
|
+
* 辅助函数:从路径项中提取索引
|
|
1158
|
+
* @param pathItem - 路径项,如 'entities[0]'
|
|
1159
|
+
* @returns 索引数字,如果没有则返回 undefined
|
|
1160
|
+
*/
|
|
1161
|
+
const extractIndex = (pathItem) => {
|
|
1162
|
+
const match = pathItem.match(/\[(\d+)\]$/);
|
|
1163
|
+
return match ? parseInt(match[1], 10) : undefined;
|
|
1164
|
+
};
|
|
1165
|
+
/**
|
|
1166
|
+
* 辅助函数:从路径项中提取父级键名
|
|
1167
|
+
* @param pathItem - 路径片段,如 'entities[0]' 或 'dataSources[name=defaultDS]'
|
|
1168
|
+
* @returns 父级键名,如 'entities' 或 'dataSources'
|
|
1169
|
+
*/
|
|
1170
|
+
const getParentKey = (pathItem) => {
|
|
1171
|
+
return pathItem.replace(/\[.*\]$/, '');
|
|
1172
|
+
};
|
|
1173
|
+
/**
|
|
1174
|
+
* 将批量操作应用到 app 实例
|
|
1175
|
+
* 处理批量操作(create/update/delete),并将其应用到 app 中
|
|
1176
|
+
* @param app - NASL 应用实例,需传入 Vue 劫持后的响应式 app
|
|
1177
|
+
* @param batchActions - 需要应用的批量操作
|
|
1178
|
+
*/
|
|
1179
|
+
const applyBatchActions = (app, batchActions) => {
|
|
1180
|
+
const { actionMsg, sourceType, actions } = batchActions;
|
|
1181
|
+
app.emit('collect:start', {
|
|
1182
|
+
actionMsg,
|
|
1183
|
+
sourceType,
|
|
1184
|
+
});
|
|
1185
|
+
for (const event of actions) {
|
|
1186
|
+
const { action, path, object } = event;
|
|
1187
|
+
try {
|
|
1188
|
+
switch (action) {
|
|
1189
|
+
case 'create': {
|
|
1190
|
+
const pathArr = (0, utils_1.splitPath)(path) || [];
|
|
1191
|
+
const lastPathItem = pathArr?.pop();
|
|
1192
|
+
if (!lastPathItem)
|
|
1193
|
+
break;
|
|
1194
|
+
const parentPath = pathArr.join('.');
|
|
1195
|
+
const parentKey = getParentKey(lastPathItem);
|
|
1196
|
+
const index = extractIndex(lastPathItem);
|
|
1197
|
+
const parentNode = app.findNodeByPath(parentPath);
|
|
1198
|
+
if (!parentNode)
|
|
1199
|
+
break;
|
|
1200
|
+
const node = concepts_1.BaseNode.from(object);
|
|
1201
|
+
node.createNode({
|
|
1202
|
+
parentNode,
|
|
1203
|
+
parentKey,
|
|
1204
|
+
index,
|
|
1205
|
+
});
|
|
1206
|
+
break;
|
|
1207
|
+
}
|
|
1208
|
+
case 'delete': {
|
|
1209
|
+
const node = app.findNodeByPath(path);
|
|
1210
|
+
node?.delete();
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
case 'update': {
|
|
1214
|
+
const node = app.findNodeByPath(path);
|
|
1215
|
+
node?.update(object);
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
catch (error) {
|
|
1221
|
+
console.error(`Error applying ${action} operation for path ${path}:`, error);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
app.emit('collect:end');
|
|
1225
|
+
// return new Promise((resolve, reject) => {
|
|
1226
|
+
// app.on('save-nasl-success', resolve);
|
|
1227
|
+
// app.on('save-nasl-error', (error: any) => {
|
|
1228
|
+
// reject(error);
|
|
1229
|
+
// });
|
|
1230
|
+
// });
|
|
1231
|
+
};
|
|
1232
|
+
exports.applyBatchActions = applyBatchActions;
|
|
1233
|
+
//# sourceMappingURL=transform2BatchActions.js.map
|