@nasl/cli 0.1.18 → 0.2.0

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/dist/index.mjs CHANGED
@@ -61,6 +61,56 @@ function createSorter() {
61
61
  };
62
62
  }
63
63
  const sorter = createSorter();
64
+ /**
65
+ * 验证 variables 文件
66
+ */
67
+ function validateVariablesFile(file, errors) {
68
+ const matchArr = Array.from(file.content.matchAll(/^(export\s+)(declare\s+)?(const|let)\s+(\w+)/gm));
69
+ if (matchArr.length === 0) {
70
+ errors.push(`${file.path} (variables文件) 必须包含至少一个 export const 或 export let 声明`);
71
+ }
72
+ for (const match of matchArr) {
73
+ const [, isExport] = match;
74
+ if (!isExport)
75
+ errors.push(`${file.path} 所有声明必须使用 export,错误代码:${match[0]}`);
76
+ }
77
+ }
78
+ /**
79
+ * 验证 extensions 文件
80
+ */
81
+ function validateExtensionsFile(file, errors) {
82
+ const matchArr = Array.from(file.content.matchAll(/^(export\s+)(namespace)\s+(\w+)/gm));
83
+ if (matchArr.length === 0) {
84
+ errors.push(`${file.path} (extensions文件) 必须包含至少一个 export namespace 声明`);
85
+ }
86
+ for (const match of matchArr) {
87
+ const [, isExport] = match;
88
+ if (!isExport)
89
+ errors.push(`${file.path} 所有 namespace 必须使用 export,错误代码:${match[0]}`);
90
+ }
91
+ }
92
+ /**
93
+ * 验证普通文件(枚举、实体、数据结构、逻辑、页面)
94
+ */
95
+ function validateNormalFile(file, nameFromPath, namespace, errors) {
96
+ const matchArr = Array.from(file.content.matchAll(/^(export\s+)?(declare\s+)?(function|class|interface)\s+(\w+)/gm));
97
+ if (matchArr.length === 0)
98
+ errors.push(`${file.path} 必须有一个函数或类,错误代码:${file.content}`);
99
+ else if (matchArr.length > 1)
100
+ errors.push(`${file.path} 只能有一个函数或类,错误代码:${matchArr.map((match) => match[0]).join('\n')}
101
+ 你可以将该文件中所有函数合并,或者将其他函数拆到多个文件中。`);
102
+ for (const match of matchArr) {
103
+ const [, isExport, isDeclare, type, name] = match;
104
+ if (!isExport)
105
+ errors.push(`${file.path} 必须使用 export,错误代码:${match[0]}`);
106
+ if (name !== nameFromPath)
107
+ errors.push(`${file.path} 的函数或类名必须与文件名一致,错误代码:${match[0]}`);
108
+ if (/\.(entities|enums|structures)/.test(namespace) && type !== 'class')
109
+ errors.push(`${file.path} 实体、数据结构和枚举只能使用 class 定义,错误代码:${match[0]}`);
110
+ if (/\.(logics|views)/.test(namespace) && type !== 'function')
111
+ errors.push(`${file.path} 逻辑和页面只能使用 function 定义,错误代码:${match[0]}`);
112
+ }
113
+ }
64
114
  function composeToString(files) {
65
115
  files.sort((a, b) => sorter(a.path, b.path));
66
116
  const errors = [];
@@ -68,28 +118,31 @@ function composeToString(files) {
68
118
  const arr = file.path.split('.');
69
119
  const ext = arr.pop();
70
120
  const nameFromPath = arr.pop();
71
- const namespace = arr.join('.');
121
+ // 判断是否是特殊文件类型(variables extensions)
122
+ const isVariablesFile = nameFromPath === 'variables';
123
+ const isExtensionsFile = arr[0] === 'extensions';
124
+ const isThemeCss = ext === 'css' && nameFromPath === 'theme';
125
+ const isSpecialFile = isVariablesFile || isExtensionsFile || isThemeCss;
126
+ // 特殊文件的 namespace 包含文件名,普通文件不包含
127
+ const namespace = isSpecialFile ? [...arr, nameFromPath].join('.') : arr.join('.');
128
+ let content = file.content;
72
129
  if (['ts', 'tsx'].includes(ext)) {
73
- const matchArr = Array.from(file.content.matchAll(/^(export\s+)?(declare\s+)?(function|class|interface)\s+(\w+)/gm));
74
- if (matchArr.length === 0)
75
- errors.push(`${file.path} 必须有一个函数或类,错误代码:${file.content}`);
76
- else if (matchArr.length > 1)
77
- errors.push(`${file.path} 只能有一个函数或类,错误代码:${matchArr.map((match) => match[0]).join('\n')}
78
- 你可以将该文件中所有函数合并,或者将其他函数拆到多个文件中。`);
79
- for (const match of matchArr) {
80
- const [, isExport, isDeclare, type, name] = match;
81
- if (!isExport)
82
- errors.push(`${file.path} 必须使用 export,错误代码:${match[0]}`);
83
- if (name !== nameFromPath)
84
- errors.push(`${file.path} 的函数或类名必须与文件名一致,错误代码:${match[0]}`);
85
- if (/\.(entities|enums|structures)/.test(namespace) && type !== 'class')
86
- errors.push(`${file.path} 实体、数据结构和枚举只能使用 class 定义,错误代码:${match[0]}`);
87
- if (/\.(logics|views)/.test(namespace) && type !== 'function')
88
- errors.push(`${file.path} 逻辑和页面只能使用 function 定义,错误代码:${match[0]}`);
130
+ if (isVariablesFile) {
131
+ validateVariablesFile(file, errors);
132
+ }
133
+ else if (isExtensionsFile) {
134
+ validateExtensionsFile(file, errors);
135
+ }
136
+ else {
137
+ validateNormalFile(file, nameFromPath, namespace, errors);
89
138
  }
90
139
  }
91
- return `namespace ${namespace} {\n${file.content}\n}\n`;
92
- }).join('\n');
140
+ else if (isThemeCss) {
141
+ content = ` $theme\`${content}\`;`;
142
+ }
143
+ return `namespace ${namespace} {\n${content}\n}\n`;
144
+ })
145
+ .join('\n');
93
146
  if (errors.length > 0) {
94
147
  throw new Error(errors.join('\n============\n'));
95
148
  }
@@ -13586,26 +13639,29 @@ function loadConfig(configDir) {
13586
13639
  defaultLogger.error('配置文件格式不正确,缺少必需的 srcDir 或 outDir 字段');
13587
13640
  return defaultLogger.exit(1);
13588
13641
  }
13642
+ // 环境变量优先级高于配置文件
13643
+ // ideVersion: 环境变量优先,如果都没有则报错
13644
+ if (process.env.NASL_IDE_VERSION)
13645
+ config.ideVersion = process.env.NASL_IDE_VERSION;
13589
13646
  if (!config.ideVersion) {
13590
- config.ideVersion = process.env.NASL_IDE_VERSION || '';
13591
- if (!config.ideVersion) {
13592
- defaultLogger.error('缺少配置 ideVersion,请在配置文件中添加,或在环境变量中配置 NASL_IDE_VERSION');
13593
- return defaultLogger.exit(1);
13594
- }
13647
+ defaultLogger.error('缺少配置 ideVersion,请在配置文件中添加,或在环境变量中配置 NASL_IDE_VERSION');
13648
+ return defaultLogger.exit(1);
13595
13649
  }
13650
+ // serverBaseURL: 环境变量优先,如果都没有则报错
13651
+ if (process.env.NASL_SERVER_BASE_URL)
13652
+ config.serverBaseURL = process.env.NASL_SERVER_BASE_URL;
13596
13653
  if (!config.serverBaseURL) {
13597
- config.serverBaseURL = process.env.NASL_SERVER_BASE_URL || '';
13598
- if (!config.serverBaseURL) {
13599
- defaultLogger.error('缺少配置 serverBaseURL,请在配置文件中添加,或在环境变量中配置 NASL_SERVER_BASE_URL');
13600
- return defaultLogger.exit(1);
13601
- }
13654
+ defaultLogger.error('缺少配置 serverBaseURL,请在配置文件中添加,或在环境变量中配置 NASL_SERVER_BASE_URL');
13655
+ return defaultLogger.exit(1);
13602
13656
  }
13603
- if (!config.useOPENAPI)
13657
+ // 环境变量优先
13658
+ if (process.env.USE_LCAP_OPENAPI !== undefined)
13604
13659
  config.useOPENAPI = process.env.USE_LCAP_OPENAPI === 'true';
13605
- if (!config.OPENAPI_AK)
13660
+ if (process.env.LCAP_OPENAPI_AK)
13606
13661
  config.OPENAPI_AK = process.env.LCAP_OPENAPI_AK;
13607
- if (!config.OPENAPI_SK)
13662
+ if (process.env.LCAP_OPENAPI_SK)
13608
13663
  config.OPENAPI_SK = process.env.LCAP_OPENAPI_SK;
13664
+ // 如果启用了 OpenAPI,验证必需的 AK 和 SK
13609
13665
  if (config.useOPENAPI) {
13610
13666
  if (!config.OPENAPI_AK || !config.OPENAPI_SK) {
13611
13667
  defaultLogger.error(`配置了 useOPENAPI,但缺少配置 OPENAPI_AK 和 OPENAPI_SK:
@@ -13661,13 +13717,116 @@ function writeFileWithLog(filePath, content, logger) {
13661
13717
  }
13662
13718
  }
13663
13719
 
13720
+ /**
13721
+ * NASL 支持的所有文件类型配置
13722
+ */
13723
+ const NASL_FILE_TYPES = [
13724
+ {
13725
+ name: 'theme-css',
13726
+ description: '主题样式',
13727
+ fileNamePattern: 'app\\.frontendTypes\\.pc\\.frontends\\.pc\\.theme\\.css',
13728
+ codeRefPattern: 'app\\.frontendTypes\\.pc\\.frontends\\.pc\\.theme',
13729
+ extension: 'css',
13730
+ },
13731
+ {
13732
+ name: 'structures',
13733
+ description: '数据结构',
13734
+ fileNamePattern: 'app\\.structures\\.\\w+\\.ts',
13735
+ codeRefPattern: 'app\\.structures\\.\\w+(?:\\.\\w+)*',
13736
+ extension: 'ts',
13737
+ },
13738
+ {
13739
+ name: 'entities',
13740
+ description: '实体',
13741
+ fileNamePattern: 'app\\.dataSources\\.\\w+\\.entities\\.\\w+\\.ts',
13742
+ codeRefPattern: 'app\\.dataSources\\.\\w+\\.entities\\.\\w+(?:Entity)?(?:\\.\\w+)*',
13743
+ extension: 'ts',
13744
+ },
13745
+ {
13746
+ name: 'logics',
13747
+ description: '逻辑',
13748
+ fileNamePattern: 'app\\.logics\\.\\w+\\.ts',
13749
+ codeRefPattern: 'app\\.logics\\.\\w+(?:\\.\\w+)*',
13750
+ extension: 'ts',
13751
+ },
13752
+ {
13753
+ name: 'views',
13754
+ description: '视图',
13755
+ fileNamePattern: 'app\\.frontendTypes\\.\\w+\\.frontends\\.\\w+(?:\\.views\\.\\w+)+\\.tsx',
13756
+ codeRefPattern: 'app\\.frontendTypes\\.\\w+\\.frontends\\.\\w+(?:\\.views\\.\\w+)+(?:\\.\\w+)*',
13757
+ extension: 'tsx',
13758
+ },
13759
+ {
13760
+ name: 'backend-variables',
13761
+ description: '后端全局变量',
13762
+ fileNamePattern: 'app\\.backend\\.variables\\.ts',
13763
+ codeRefPattern: 'app\\.backend\\.variables(?:\\.\\w+)*',
13764
+ extension: 'ts',
13765
+ },
13766
+ {
13767
+ name: 'frontend-variables',
13768
+ description: '前端全局变量',
13769
+ fileNamePattern: 'app\\.frontendTypes\\.\\w+\\.frontends\\.\\w+\\.variables\\.ts',
13770
+ codeRefPattern: 'app\\.frontendTypes\\.\\w+\\.frontends\\.\\w+\\.variables(?:\\.\\w+)*',
13771
+ extension: 'ts',
13772
+ },
13773
+ {
13774
+ name: 'enums',
13775
+ description: '枚举',
13776
+ fileNamePattern: 'app\\.enums\\.\\w+\\.ts',
13777
+ codeRefPattern: 'app\\.enums\\.\\w+(?:\\.\\w+)*',
13778
+ extension: 'ts',
13779
+ },
13780
+ {
13781
+ name: 'extensions',
13782
+ description: '依赖库',
13783
+ fileNamePattern: 'extensions\\.\\w+\\.ts',
13784
+ codeRefPattern: 'extensions\\.\\w+(?:\\.\\w+)*',
13785
+ extension: 'ts',
13786
+ },
13787
+ ];
13788
+ /**
13789
+ * 获取用于验证文件名的正则表达式数组
13790
+ */
13791
+ function getFileNamePatterns() {
13792
+ return NASL_FILE_TYPES.map((type) => new RegExp(`^${type.fileNamePattern}$`));
13793
+ }
13794
+ const fileNamePatterns = getFileNamePatterns();
13795
+ /**
13796
+ * 获取用于提取代码引用的正则表达式数组
13797
+ */
13798
+ function getCodeRefPatterns() {
13799
+ return NASL_FILE_TYPES.map((type) => new RegExp(type.codeRefPattern, 'g'));
13800
+ }
13801
+ const codeRefPatterns = getCodeRefPatterns();
13802
+ /**
13803
+ * 判断文件路径是否为已知的 NASL 文件类型
13804
+ * 支持的类型:
13805
+ * - 数据结构 (structures): app.structures.{StructureName}.ts
13806
+ * - 实体 (entities): app.dataSources.{dsName}.entities.{EntityName}.ts
13807
+ * - 逻辑 (logics): app.logics.{logicName}.ts
13808
+ * - 视图 (views): app.frontendTypes.{type}.frontends.{name}.views.{viewName}.tsx
13809
+ * - 后端全局变量: app.backend.variables.ts
13810
+ * - 前端全局变量: app.frontendTypes.{type}.frontends.{name}.variables.ts
13811
+ * - 枚举 (enums): app.enums.{EnumName}.ts
13812
+ * - 依赖库 (extensions): extensions.{extensionName}.ts
13813
+ *
13814
+ * @param filePath 文件路径(绝对路径或相对路径)
13815
+ * @returns 是否为已知的 NASL 文件类型
13816
+ */
13817
+ function isKnownFileType(filePath) {
13818
+ // 提取文件名
13819
+ const fileName = sysPath.basename(filePath);
13820
+ return fileNamePatterns.some((pattern) => pattern.test(fileName));
13821
+ }
13822
+
13664
13823
  /**
13665
13824
  * 扫描目录下的所有 NASL 文件
13666
13825
  */
13667
13826
  async function scanNASLFiles(srcDir, representation, logger) {
13668
13827
  try {
13669
13828
  const pattern = representation === 'NaturalTS' ? '**/*.{ts,tsx}' : '**/*.nasl';
13670
- const files = await globby([pattern, '!**/*.css'], {
13829
+ const files = await globby([pattern, '**/app.frontendTypes.pc.frontends.pc.theme.css'], {
13671
13830
  cwd: srcDir,
13672
13831
  onlyFiles: true,
13673
13832
  absolute: false,
@@ -13681,7 +13840,6 @@ async function scanNASLFiles(srcDir, representation, logger) {
13681
13840
  }
13682
13841
  async function scanEntryFiles(projectRoot, patterns, logger) {
13683
13842
  try {
13684
- patterns.push('!**/*.css');
13685
13843
  const files = await globby(patterns, {
13686
13844
  cwd: projectRoot,
13687
13845
  onlyFiles: true,
@@ -13695,30 +13853,46 @@ async function scanEntryFiles(projectRoot, patterns, logger) {
13695
13853
  }
13696
13854
  }
13697
13855
  /**
13698
- * 从文件内容中提取 app.* 格式的依赖引用
13856
+ * 从文件内容中提取 指定 格式的依赖引用
13699
13857
  * @param content 文件内容
13700
- * @returns 依赖的 app.* 路径列表
13858
+ * @returns 依赖的 指定 路径列表
13701
13859
  */
13702
13860
  function extractDeps(content) {
13703
13861
  const deps = new Set();
13704
- // 预处理:移除注释,避免注释影响依赖分析
13862
+ // 预处理:移除注释和字符串字面量,避免它们影响依赖分析
13705
13863
  let processedContent = content
13706
13864
  .replace(/\/\/.*$/gm, '') // 移除单行注释 // ...
13707
- .replace(/\/\*[\s\S]*?\*\//g, ''); // 移除多行注释 /* ... */
13708
- const allMatches = processedContent.matchAll(/app\.\w+\.[\w.]+/g); // 起码要2个点
13709
- for (const match of allMatches) {
13710
- let dep = match[0];
13711
- if (/Entity$|Entity\./.test(dep)) {
13712
- dep = dep.replace(/Entity(\..*)?$/, '');
13713
- }
13714
- else if (/\.enums\.(\w+)\.(\w+)/.test(dep)) {
13715
- dep = dep.replace(/\.enums\.(\w+).+$/, '.enums.$1');
13716
- }
13717
- else if (/app\.dataSources\.\w+\.entities\.\w+\.\w+$/.test(dep)) {
13718
- continue; // 应该是写法有问题,跳过让后面的 checker 做检查
13865
+ .replace(/\/\*[\s\S]*?\*\//g, '') // 移除多行注释 /* ... */
13866
+ .replace(/'(?:[^'\\]|\\.)*'/g, '') // 移除单引号字符串 'xxx'
13867
+ .replace(/"(?:[^"\\]|\\.)*"/g, ''); // 移除双引号字符串 "xxx"
13868
+ for (const pattern of codeRefPatterns) {
13869
+ const matches = processedContent.matchAll(pattern);
13870
+ for (const match of matches) {
13871
+ let dep = match[0];
13872
+ // 处理 Entity 后缀:app.dataSources.ds.entities.EntityNameEntity -> app.dataSources.ds.entities.EntityName
13873
+ if (/Entity$|Entity\./.test(dep)) {
13874
+ dep = dep.replace(/Entity(\..*)?$/, '');
13875
+ }
13876
+ // 处理枚举:app.enums.EnumName.field -> app.enums.EnumName
13877
+ if (/^app\.enums\.\w+\./.test(dep)) {
13878
+ dep = dep.replace(/^(app\.enums\.\w+).+$/, '$1');
13879
+ }
13880
+ // 处理 variables:app.backend.variables.varName -> app.backend.variables
13881
+ if (/\.variables\.\w+/.test(dep)) {
13882
+ dep = dep.replace(/\.variables\..+$/, '.variables');
13883
+ }
13884
+ // 处理 extensions:extensions.lcap_auth.logics.getUser -> extensions.lcap_auth
13885
+ if (/^extensions\.\w+\./.test(dep)) {
13886
+ dep = dep.replace(/^(extensions\.\w+)\..+$/, '$1');
13887
+ }
13888
+ // 跳过不合法的实体引用(实体后还有字段的情况)
13889
+ if (/app\.dataSources\.\w+\.entities\.\w+\.\w+$/.test(dep)) {
13890
+ continue;
13891
+ }
13892
+ // 添加文件扩展名
13893
+ dep = `${dep}.${dep.includes('.views.') ? 'tsx' : 'ts'}`;
13894
+ deps.add(dep);
13719
13895
  }
13720
- dep = `${dep}.${dep.includes('.views.') ? 'tsx' : 'ts'}`;
13721
- deps.add(dep);
13722
13896
  }
13723
13897
  return Array.from(deps);
13724
13898
  }
@@ -13835,7 +14009,12 @@ async function collectDeps(patterns, projectRoot, srcDir, logger, verbose) {
13835
14009
  while (filesToProcess.length > 0) {
13836
14010
  const pathRelativeToSrc = filesToProcess.shift();
13837
14011
  if (processedFileMap.has(pathRelativeToSrc))
13838
- continue; // 跳过已处理的文件
14012
+ continue; // 跳过已处理的文件
14013
+ // 检查文件类型是否支持
14014
+ if (!isKnownFileType(pathRelativeToSrc)) {
14015
+ logger.warn(`跳过不支持的文件类型: ${pathRelativeToSrc}`);
14016
+ continue;
14017
+ }
13839
14018
  try {
13840
14019
  const { fileInfo, newDeps } = processFileDeps(pathRelativeToSrc, srcDir, matchedFileSet, processedFileMap, depNotFoundList, logger, verbose);
13841
14020
  result.push(fileInfo);
@@ -13915,8 +14094,18 @@ async function resolveNASLFiles(entry, logger, depMode, verbose) {
13915
14094
  }
13916
14095
  logger.info(`找到 ${collectedFiles.length} 个 NASL 文件`);
13917
14096
  }
14097
+ // 统一过滤掉不支持的文件类型
14098
+ const filteredFiles = [];
14099
+ collectedFiles.forEach((file) => {
14100
+ if (isKnownFileType(file.path)) {
14101
+ filteredFiles.push(file);
14102
+ }
14103
+ else {
14104
+ logger.warn(`跳过不支持的文件类型: ${file.path}`);
14105
+ }
14106
+ });
13918
14107
  return {
13919
- collectedFiles,
14108
+ collectedFiles: filteredFiles,
13920
14109
  config,
13921
14110
  projectRoot,
13922
14111
  srcDir,