@nasl/cli 0.3.0 → 0.3.1

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
@@ -93,7 +93,7 @@ function validateExtensionsFile(file, errors) {
93
93
  /**
94
94
  * 验证普通文件(枚举、实体、数据结构、逻辑、页面)
95
95
  */
96
- function validateNormalFile(file, nameFromPath, namespace, errors) {
96
+ function validateNormalFile(file, nameFromPath, namespace, errors, options) {
97
97
  const matchArr = Array.from(file.content.matchAll(/^(export\s+)?(declare\s+)?(function|class|interface)\s+(\w+)/gm));
98
98
  if (matchArr.length === 0)
99
99
  errors.push(`${file.path} 必须有一个函数或类,错误代码:${file.content}`);
@@ -105,14 +105,14 @@ function validateNormalFile(file, nameFromPath, namespace, errors) {
105
105
  if (!isExport)
106
106
  errors.push(`${file.path} 必须使用 export,错误代码:${match[0]}`);
107
107
  if (name !== nameFromPath)
108
- errors.push(`${file.path} 的函数或类名必须与文件名一致,错误代码:${match[0]}`);
108
+ errors.push(`${file.path} 的函数或类名必须与${options?.isDocCheck ? ' path 属性' : '文件名'}一致,错误代码:${match[0]}`);
109
109
  if (/\.(entities|enums|structures)/.test(namespace) && type !== 'class')
110
110
  errors.push(`${file.path} 实体、数据结构和枚举只能使用 class 定义,错误代码:${match[0]}`);
111
111
  if (/\.(logics|views)/.test(namespace) && type !== 'function')
112
112
  errors.push(`${file.path} 逻辑和页面只能使用 function 定义,错误代码:${match[0]}`);
113
113
  }
114
114
  }
115
- function composeToString(files) {
115
+ function composeToString(files, options) {
116
116
  files.sort((a, b) => sorter(a.path, b.path));
117
117
  const errors = [];
118
118
  let currentLine = 1;
@@ -127,7 +127,7 @@ function composeToString(files) {
127
127
  const isExtensionsFile = arr[0] === 'extensions';
128
128
  const isThemeCss = ext === 'css' && nameFromPath === 'theme';
129
129
  const isSpecialFile = isVariablesFile || isExtensionsFile || isThemeCss;
130
- // 特殊文件的 namespace 包含文件名,普通文件不包含
130
+ // 特殊文件的 namespace ${包含文件名,普通文件不包含
131
131
  const namespace = isSpecialFile ? [...arr, nameFromPath].join('.') : arr.join('.');
132
132
  let content = file.content;
133
133
  if (['ts', 'tsx'].includes(ext)) {
@@ -138,7 +138,7 @@ function composeToString(files) {
138
138
  validateExtensionsFile(file, errors);
139
139
  }
140
140
  else {
141
- validateNormalFile(file, nameFromPath, namespace, errors);
141
+ validateNormalFile(file, nameFromPath, namespace, errors, options);
142
142
  }
143
143
  }
144
144
  else if (isThemeCss) {
@@ -35163,6 +35163,32 @@ async function checkApi(fullNaturalTS, options) {
35163
35163
  }
35164
35164
  return '';
35165
35165
  }
35166
+ /**
35167
+ * 解析 NASL 代码(parse)
35168
+ */
35169
+ async function parseApi(fullNaturalTS, options) {
35170
+ const axios = await createAxios(options);
35171
+ try {
35172
+ const res = await axios.post(`/parse/tsx?ideVersion=${options.ideVersion}`, fullNaturalTS, {
35173
+ headers: buildNaslHeaders(options),
35174
+ });
35175
+ const result = res.data.result;
35176
+ const errors = (result.errors || []);
35177
+ if (errors.length > 0) {
35178
+ return truncate(`解析到以下错误(共 ${errors.length} 个):\n` + result.errorStr, 10000);
35179
+ }
35180
+ return '';
35181
+ }
35182
+ catch (error) {
35183
+ try {
35184
+ const data = JSON.parse(error?.message || '');
35185
+ return truncate(data.message || error?.message || String(error), 10000);
35186
+ }
35187
+ catch {
35188
+ return truncate(error?.message || String(error), 10000);
35189
+ }
35190
+ }
35191
+ }
35166
35192
 
35167
35193
  /**
35168
35194
  * 创建应用同步
@@ -35706,6 +35732,188 @@ async function transformJson2FilesApi(json, options) {
35706
35732
  return data.result;
35707
35733
  }
35708
35734
 
35735
+ // src/codeBlock.ts
35736
+ function extractCodeBlocks(markdown) {
35737
+ const codeBlocks = [];
35738
+ const lines = markdown.split("\n");
35739
+ let inCodeBlock = false;
35740
+ let codeBlockStart = 0;
35741
+ let currLang = "";
35742
+ let currLangExtra = "";
35743
+ let currCode = [];
35744
+ for (let i = 0; i < lines.length; i++) {
35745
+ const line = lines[i];
35746
+ if (line.startsWith("```")) {
35747
+ if (!inCodeBlock) {
35748
+ inCodeBlock = true;
35749
+ codeBlockStart = i + 1;
35750
+ const langPart = line.slice(3).trim();
35751
+ const spaceIndex = langPart.indexOf(" ");
35752
+ let potentialLang = "";
35753
+ let potentialExtra = "";
35754
+ if (spaceIndex > 0) {
35755
+ potentialLang = langPart.slice(0, spaceIndex);
35756
+ potentialExtra = langPart.slice(spaceIndex + 1).trim();
35757
+ } else {
35758
+ potentialLang = langPart;
35759
+ potentialExtra = "";
35760
+ }
35761
+ const isLikelyLanguage = potentialLang && !potentialLang.includes("=") && !potentialLang.startsWith("/") && !potentialLang.startsWith(".") && potentialLang !== "";
35762
+ if (isLikelyLanguage) {
35763
+ currLang = potentialLang;
35764
+ currLangExtra = potentialExtra;
35765
+ } else {
35766
+ currLang = "text";
35767
+ currLangExtra = langPart;
35768
+ }
35769
+ currCode = [];
35770
+ } else {
35771
+ inCodeBlock = false;
35772
+ codeBlocks.push({
35773
+ lang: currLang,
35774
+ langExtra: currLangExtra || void 0,
35775
+ code: currCode.join("\n"),
35776
+ startLine: codeBlockStart,
35777
+ endLine: i
35778
+ });
35779
+ currLang = "";
35780
+ currLangExtra = "";
35781
+ currCode = [];
35782
+ }
35783
+ } else if (inCodeBlock) {
35784
+ currCode.push(line);
35785
+ }
35786
+ }
35787
+ return codeBlocks;
35788
+ }
35789
+
35790
+ const DOC_PATTERNS = [
35791
+ /^enums\.md$/,
35792
+ /^entity-.+\.md$/,
35793
+ /^structure-.+\.md$/,
35794
+ /^logic-.+\.md$/,
35795
+ /^view-.+\.md$/,
35796
+ ];
35797
+ // 文档类型前缀 → NASL 文件类型名
35798
+ const DOC_TO_NASL_TYPE = {
35799
+ enums: 'enums',
35800
+ entity: 'entities',
35801
+ structure: 'structures',
35802
+ logic: 'logics',
35803
+ view: 'views',
35804
+ };
35805
+ /**
35806
+ * 从 tsx FileInfo 中解析出所有祖先 view,生成空壳 stub
35807
+ * 例如 path=a.views.dashboard.views.knowledgeAccess.views.accessBrowse.tsx
35808
+ * 会生成 dashboard 和 knowledgeAccess 的 stub
35809
+ */
35810
+ function buildViewStubs(files) {
35811
+ const existingPaths = new Set(files.map((f) => f.path));
35812
+ const stubs = [];
35813
+ for (const file of files) {
35814
+ if (!file.path.endsWith('.tsx'))
35815
+ continue;
35816
+ const parts = file.path.split('.').slice(0, -2);
35817
+ for (let i = 0; i < parts.length - 1; i++) {
35818
+ if (parts[i] === 'views') {
35819
+ const viewName = parts[i + 1];
35820
+ if (!viewName)
35821
+ continue;
35822
+ const stubPath = [...parts.slice(0, i + 2), 'tsx'].join('.');
35823
+ if (!existingPaths.has(stubPath)) {
35824
+ existingPaths.add(stubPath);
35825
+ stubs.push({
35826
+ path: stubPath,
35827
+ content: `$View({\n title: "",\n auth: false,\n isIndex: false,\n});\nexport function ${viewName}() {\n return ( <ElRouterView /> );\n}`,
35828
+ });
35829
+ }
35830
+ }
35831
+ }
35832
+ }
35833
+ return stubs;
35834
+ }
35835
+ /**
35836
+ * 从 plan markdown 文件中提取并组装 fullNaturalTS,返回字符串或错误信息
35837
+ */
35838
+ function extractFullNaturalTS(filePath, logger = defaultLogger) {
35839
+ // 1. 验证路径模式(只检查文件名)
35840
+ const basename = path$1.basename(filePath);
35841
+ if (!DOC_PATTERNS.some((p) => p.test(basename))) {
35842
+ logger.error(`路径不匹配任何支持的文档类型:${basename}\n` +
35843
+ `支持的类型:enums.md, entity-*.md, structure-*.md, logic-*.md, view-*.md`);
35844
+ return { success: false };
35845
+ }
35846
+ // 2. 读取文件
35847
+ if (!libExports.existsSync(filePath)) {
35848
+ logger.error(`文件不存在:${filePath}`);
35849
+ return { success: false };
35850
+ }
35851
+ const content = libExports.readFileSync(filePath, 'utf-8');
35852
+ // 3. 提取 naturalts 代码块,转换为 FileInfo[]
35853
+ const blocks = extractCodeBlocks(content).filter((b) => b.lang === 'naturalts');
35854
+ if (blocks.length === 0) {
35855
+ // logger.warn('未发现 naturalts 代码块');
35856
+ return { fullNaturalTS: '' };
35857
+ }
35858
+ const isViewDoc = /^view-.+\.md$/.test(basename);
35859
+ const expectedExt = isViewDoc ? '.tsx' : '.ts';
35860
+ const docTypePrefix = basename.split(/[-.]/)[0] ?? '';
35861
+ const naslTypeName = DOC_TO_NASL_TYPE[docTypePrefix];
35862
+ const expectedPattern = NASL_FILE_TYPES.find((t) => t.name === naslTypeName)?.fileNamePattern;
35863
+ const errors = [];
35864
+ const files = blocks.map((block) => {
35865
+ const match = block.langExtra?.match(/path="([^"]+)"/);
35866
+ if (!match) {
35867
+ errors.push(`代码块缺少 path 属性(第 ${block.startLine} 行)`);
35868
+ return null;
35869
+ }
35870
+ const blockPath = match[1];
35871
+ if (!blockPath.endsWith(expectedExt)) {
35872
+ errors.push(`代码块 path 扩展名不正确,应为 ${expectedExt}(第 ${block.startLine} 行):${blockPath}`);
35873
+ return null;
35874
+ }
35875
+ if (!isKnownFileType(blockPath)) {
35876
+ const hint = expectedPattern ? `\n正确格式应匹配:${expectedPattern}` : '';
35877
+ errors.push(`代码块 path 不是合法的 NASL 文件类型(第 ${block.startLine} 行):${blockPath}${hint}`);
35878
+ return null;
35879
+ }
35880
+ const fileInfo = { path: blockPath, content: block.code };
35881
+ return fileInfo;
35882
+ }).filter((f) => f !== null);
35883
+ if (errors.length > 0) {
35884
+ logger.error(`在 ${filePath} 检测到以下错误:\n` + errors.join('\n'));
35885
+ return { success: false };
35886
+ }
35887
+ // 4. 补充父级 view stub(tsx 文件需要祖先页面定义)
35888
+ const stubs = buildViewStubs(files);
35889
+ // 5. 复用 composeToString 组装(包含排序和 namespace 构建)
35890
+ try {
35891
+ return { fullNaturalTS: composeToString([...files, ...stubs], { isDocCheck: true }) };
35892
+ }
35893
+ catch (err) {
35894
+ logger.error(`在 ${filePath} 检测到以下错误:\n` + err.message);
35895
+ return { success: false };
35896
+ }
35897
+ }
35898
+ async function docCheck(filePath, options) {
35899
+ const logger = options?.logger || defaultLogger;
35900
+ const extracted = extractFullNaturalTS(filePath, logger);
35901
+ if ('success' in extracted)
35902
+ return extracted;
35903
+ if (!extracted.fullNaturalTS)
35904
+ return { success: true };
35905
+ logger.info(`正在调用 parse 服务,校验文档...`);
35906
+ const config = loadConfig();
35907
+ const rawResult = (await parseApi(extracted.fullNaturalTS, config)).trim();
35908
+ const result = rawResult.replace(/\*\*可能的位置\*\*:.*\n?/g, '').trim();
35909
+ if (result) {
35910
+ logger.error(`\n在 ${filePath} 检测到以下错误:\n` + result);
35911
+ return { success: false, result };
35912
+ }
35913
+ logger.success('发现 0 个错误,检查通过!');
35914
+ return { success: true };
35915
+ }
35916
+
35709
35917
  const transformFns = {
35710
35918
  /**
35711
35919
  * 将 src 中的文件组合成 fullNaturalTS
@@ -35732,6 +35940,33 @@ const transformFns = {
35732
35940
  writeFileWithLog(outputPath, fullNaturalTS, logger);
35733
35941
  logger.success(`文件已输出到: ${outputPath}`);
35734
35942
  },
35943
+ /**
35944
+ * 将 plan markdown 文档中的 naturalts 代码块组合成 fullNaturalTS 文件
35945
+ */
35946
+ async doc2full(entry, options) {
35947
+ const logger = options?.logger || defaultLogger;
35948
+ if (!entry) {
35949
+ logger.error('请指定 plan 文档路径');
35950
+ logger.exit(1);
35951
+ return;
35952
+ }
35953
+ const extracted = extractFullNaturalTS(entry, logger);
35954
+ if ('success' in extracted) {
35955
+ logger.exit(1);
35956
+ return;
35957
+ }
35958
+ if (!extracted.fullNaturalTS) {
35959
+ logger.warn('未发现 naturalts 代码块,跳过输出');
35960
+ return;
35961
+ }
35962
+ // 确定输出路径
35963
+ const projectRoot = getProjectRoot();
35964
+ const outputPath = options?.output
35965
+ ? path$1.resolve(projectRoot, options.output)
35966
+ : path$1.join(projectRoot, './full-natural.ts');
35967
+ writeFileWithLog(outputPath, extracted.fullNaturalTS, logger);
35968
+ logger.success(`文件已输出到: ${outputPath}`);
35969
+ },
35735
35970
  /**
35736
35971
  * 将 JSON 文件转换成 src 的 files
35737
35972
  * TODO: 待实现
@@ -37529,5 +37764,5 @@ var index$1 = /*#__PURE__*/Object.freeze({
37529
37764
  watch: watch
37530
37765
  });
37531
37766
 
37532
- export { BaseLogger, CONFIG_FILE_NAME, ConsoleLogger, ConsoleLoggerWithoutExit, DEFAULT_CONFIG, MemoryLogger, build, check, compile, composeToString, createAppInIde, createSorter, defaultLogger, dep, dev, fastAppendLogToFile, fastLogToFile, init, installAuto, installByJSON, installByKind, loadAppDependencies, loadMergedDependencies, mergeDependenciesIntoApp, prependDependencies, resolveNASLFiles, scanEntryFiles, scanNASLFiles, sorter, transform, tryCompile };
37767
+ export { BaseLogger, CONFIG_FILE_NAME, ConsoleLogger, ConsoleLoggerWithoutExit, DEFAULT_CONFIG, MemoryLogger, build, check, compile, composeToString, createAppInIde, createSorter, defaultLogger, dep, dev, docCheck, extractFullNaturalTS, fastAppendLogToFile, fastLogToFile, init, installAuto, installByJSON, installByKind, loadAppDependencies, loadMergedDependencies, mergeDependenciesIntoApp, prependDependencies, resolveNASLFiles, scanEntryFiles, scanNASLFiles, sorter, transform, tryCompile };
37533
37768
  //# sourceMappingURL=index.mjs.map