@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/README.md +14 -0
- package/dist/bin/nasl.mjs +199 -8
- package/dist/bin/nasl.mjs.map +1 -1
- package/dist/bin/naslc.mjs +5 -5
- package/dist/bin/naslc.mjs.map +1 -1
- package/dist/index.mjs +241 -6
- package/dist/index.mjs.map +1 -1
- package/out/apis/compileApi.d.ts +4 -0
- package/out/apis/compileApi.d.ts.map +1 -1
- package/out/apis/compileApi.js +27 -0
- package/out/apis/compileApi.js.map +1 -1
- package/out/bin/nasl-doc.d.ts +3 -0
- package/out/bin/nasl-doc.d.ts.map +1 -0
- package/out/bin/nasl-doc.js +36 -0
- package/out/bin/nasl-doc.js.map +1 -0
- package/out/bin/nasl.js +2 -2
- package/out/bin/nasl.js.map +1 -1
- package/out/commands/docCheck.d.ts +18 -0
- package/out/commands/docCheck.d.ts.map +1 -0
- package/out/commands/docCheck.js +172 -0
- package/out/commands/docCheck.js.map +1 -0
- package/out/commands/index.d.ts +1 -0
- package/out/commands/index.d.ts.map +1 -1
- package/out/commands/index.js +1 -0
- package/out/commands/index.js.map +1 -1
- package/out/commands/transform.d.ts +1 -1
- package/out/commands/transform.d.ts.map +1 -1
- package/out/commands/transform.js +28 -0
- package/out/commands/transform.js.map +1 -1
- package/out/services/compose.d.ts +4 -1
- package/out/services/compose.d.ts.map +1 -1
- package/out/services/compose.js +5 -5
- package/out/services/compose.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -76,6 +76,20 @@ nasl check "src/app.logics.*.ts"
|
|
|
76
76
|
nasl check "src/*.tsx"
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### 检查 plan 文档中的 NaturalTS(nasl-doc / nasld)
|
|
80
|
+
|
|
81
|
+
用于检查 plan 目录下 markdown 里 `naturalts` 代码块的语法(与 `nasl check` 面向 `src` 的 NASL 源码检查不同)。
|
|
82
|
+
|
|
83
|
+
安装 `@nasl/cli` 后,可使用 **`nasl-doc`** 或 **`nasld`**(同一命令的短名):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
nasl-doc check path/to/plan.md
|
|
87
|
+
# 或
|
|
88
|
+
nasld check path/to/plan.md
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
支持的文档类型包括:`enums.md`、`entity-*.md`、`structure-*.md`、`logic-*.md`、`view-*.md` 等(详见命令帮助:`nasl-doc check --help`)。
|
|
92
|
+
|
|
79
93
|
### 依赖分析(nasl dep)
|
|
80
94
|
|
|
81
95
|
建议指定入口文件,否则会扫描整个 src 目录,输出量较大。
|
package/dist/bin/nasl.mjs
CHANGED
|
@@ -29331,7 +29331,7 @@ function validateExtensionsFile(file, errors) {
|
|
|
29331
29331
|
/**
|
|
29332
29332
|
* 验证普通文件(枚举、实体、数据结构、逻辑、页面)
|
|
29333
29333
|
*/
|
|
29334
|
-
function validateNormalFile(file, nameFromPath, namespace, errors) {
|
|
29334
|
+
function validateNormalFile(file, nameFromPath, namespace, errors, options) {
|
|
29335
29335
|
const matchArr = Array.from(file.content.matchAll(/^(export\s+)?(declare\s+)?(function|class|interface)\s+(\w+)/gm));
|
|
29336
29336
|
if (matchArr.length === 0)
|
|
29337
29337
|
errors.push(`${file.path} 必须有一个函数或类,错误代码:${file.content}`);
|
|
@@ -29343,14 +29343,14 @@ function validateNormalFile(file, nameFromPath, namespace, errors) {
|
|
|
29343
29343
|
if (!isExport)
|
|
29344
29344
|
errors.push(`${file.path} 必须使用 export,错误代码:${match[0]}`);
|
|
29345
29345
|
if (name !== nameFromPath)
|
|
29346
|
-
errors.push(`${file.path}
|
|
29346
|
+
errors.push(`${file.path} 的函数或类名必须与${options?.isDocCheck ? ' path 属性' : '文件名'}一致,错误代码:${match[0]}`);
|
|
29347
29347
|
if (/\.(entities|enums|structures)/.test(namespace) && type !== 'class')
|
|
29348
29348
|
errors.push(`${file.path} 实体、数据结构和枚举只能使用 class 定义,错误代码:${match[0]}`);
|
|
29349
29349
|
if (/\.(logics|views)/.test(namespace) && type !== 'function')
|
|
29350
29350
|
errors.push(`${file.path} 逻辑和页面只能使用 function 定义,错误代码:${match[0]}`);
|
|
29351
29351
|
}
|
|
29352
29352
|
}
|
|
29353
|
-
function composeToString(files) {
|
|
29353
|
+
function composeToString(files, options) {
|
|
29354
29354
|
files.sort((a, b) => sorter(a.path, b.path));
|
|
29355
29355
|
const errors = [];
|
|
29356
29356
|
let currentLine = 1;
|
|
@@ -29365,7 +29365,7 @@ function composeToString(files) {
|
|
|
29365
29365
|
const isExtensionsFile = arr[0] === 'extensions';
|
|
29366
29366
|
const isThemeCss = ext === 'css' && nameFromPath === 'theme';
|
|
29367
29367
|
const isSpecialFile = isVariablesFile || isExtensionsFile || isThemeCss;
|
|
29368
|
-
// 特殊文件的 namespace 包含文件名,普通文件不包含
|
|
29368
|
+
// 特殊文件的 namespace ${包含文件名,普通文件不包含
|
|
29369
29369
|
const namespace = isSpecialFile ? [...arr, nameFromPath].join('.') : arr.join('.');
|
|
29370
29370
|
let content = file.content;
|
|
29371
29371
|
if (['ts', 'tsx'].includes(ext)) {
|
|
@@ -29376,7 +29376,7 @@ function composeToString(files) {
|
|
|
29376
29376
|
validateExtensionsFile(file, errors);
|
|
29377
29377
|
}
|
|
29378
29378
|
else {
|
|
29379
|
-
validateNormalFile(file, nameFromPath, namespace, errors);
|
|
29379
|
+
validateNormalFile(file, nameFromPath, namespace, errors, options);
|
|
29380
29380
|
}
|
|
29381
29381
|
}
|
|
29382
29382
|
else if (isThemeCss) {
|
|
@@ -39035,6 +39035,170 @@ async function transformJson2FilesApi(json, options) {
|
|
|
39035
39035
|
return data.result;
|
|
39036
39036
|
}
|
|
39037
39037
|
|
|
39038
|
+
// src/codeBlock.ts
|
|
39039
|
+
function extractCodeBlocks(markdown) {
|
|
39040
|
+
const codeBlocks = [];
|
|
39041
|
+
const lines = markdown.split("\n");
|
|
39042
|
+
let inCodeBlock = false;
|
|
39043
|
+
let codeBlockStart = 0;
|
|
39044
|
+
let currLang = "";
|
|
39045
|
+
let currLangExtra = "";
|
|
39046
|
+
let currCode = [];
|
|
39047
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39048
|
+
const line = lines[i];
|
|
39049
|
+
if (line.startsWith("```")) {
|
|
39050
|
+
if (!inCodeBlock) {
|
|
39051
|
+
inCodeBlock = true;
|
|
39052
|
+
codeBlockStart = i + 1;
|
|
39053
|
+
const langPart = line.slice(3).trim();
|
|
39054
|
+
const spaceIndex = langPart.indexOf(" ");
|
|
39055
|
+
let potentialLang = "";
|
|
39056
|
+
let potentialExtra = "";
|
|
39057
|
+
if (spaceIndex > 0) {
|
|
39058
|
+
potentialLang = langPart.slice(0, spaceIndex);
|
|
39059
|
+
potentialExtra = langPart.slice(spaceIndex + 1).trim();
|
|
39060
|
+
} else {
|
|
39061
|
+
potentialLang = langPart;
|
|
39062
|
+
potentialExtra = "";
|
|
39063
|
+
}
|
|
39064
|
+
const isLikelyLanguage = potentialLang && !potentialLang.includes("=") && !potentialLang.startsWith("/") && !potentialLang.startsWith(".") && potentialLang !== "";
|
|
39065
|
+
if (isLikelyLanguage) {
|
|
39066
|
+
currLang = potentialLang;
|
|
39067
|
+
currLangExtra = potentialExtra;
|
|
39068
|
+
} else {
|
|
39069
|
+
currLang = "text";
|
|
39070
|
+
currLangExtra = langPart;
|
|
39071
|
+
}
|
|
39072
|
+
currCode = [];
|
|
39073
|
+
} else {
|
|
39074
|
+
inCodeBlock = false;
|
|
39075
|
+
codeBlocks.push({
|
|
39076
|
+
lang: currLang,
|
|
39077
|
+
langExtra: currLangExtra || void 0,
|
|
39078
|
+
code: currCode.join("\n"),
|
|
39079
|
+
startLine: codeBlockStart,
|
|
39080
|
+
endLine: i
|
|
39081
|
+
});
|
|
39082
|
+
currLang = "";
|
|
39083
|
+
currLangExtra = "";
|
|
39084
|
+
currCode = [];
|
|
39085
|
+
}
|
|
39086
|
+
} else if (inCodeBlock) {
|
|
39087
|
+
currCode.push(line);
|
|
39088
|
+
}
|
|
39089
|
+
}
|
|
39090
|
+
return codeBlocks;
|
|
39091
|
+
}
|
|
39092
|
+
|
|
39093
|
+
const DOC_PATTERNS = [
|
|
39094
|
+
/^enums\.md$/,
|
|
39095
|
+
/^entity-.+\.md$/,
|
|
39096
|
+
/^structure-.+\.md$/,
|
|
39097
|
+
/^logic-.+\.md$/,
|
|
39098
|
+
/^view-.+\.md$/,
|
|
39099
|
+
];
|
|
39100
|
+
// 文档类型前缀 → NASL 文件类型名
|
|
39101
|
+
const DOC_TO_NASL_TYPE = {
|
|
39102
|
+
enums: 'enums',
|
|
39103
|
+
entity: 'entities',
|
|
39104
|
+
structure: 'structures',
|
|
39105
|
+
logic: 'logics',
|
|
39106
|
+
view: 'views',
|
|
39107
|
+
};
|
|
39108
|
+
/**
|
|
39109
|
+
* 从 tsx FileInfo 中解析出所有祖先 view,生成空壳 stub
|
|
39110
|
+
* 例如 path=a.views.dashboard.views.knowledgeAccess.views.accessBrowse.tsx
|
|
39111
|
+
* 会生成 dashboard 和 knowledgeAccess 的 stub
|
|
39112
|
+
*/
|
|
39113
|
+
function buildViewStubs(files) {
|
|
39114
|
+
const existingPaths = new Set(files.map((f) => f.path));
|
|
39115
|
+
const stubs = [];
|
|
39116
|
+
for (const file of files) {
|
|
39117
|
+
if (!file.path.endsWith('.tsx'))
|
|
39118
|
+
continue;
|
|
39119
|
+
const parts = file.path.split('.').slice(0, -2);
|
|
39120
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
39121
|
+
if (parts[i] === 'views') {
|
|
39122
|
+
const viewName = parts[i + 1];
|
|
39123
|
+
if (!viewName)
|
|
39124
|
+
continue;
|
|
39125
|
+
const stubPath = [...parts.slice(0, i + 2), 'tsx'].join('.');
|
|
39126
|
+
if (!existingPaths.has(stubPath)) {
|
|
39127
|
+
existingPaths.add(stubPath);
|
|
39128
|
+
stubs.push({
|
|
39129
|
+
path: stubPath,
|
|
39130
|
+
content: `$View({\n title: "",\n auth: false,\n isIndex: false,\n});\nexport function ${viewName}() {\n return ( <ElRouterView /> );\n}`,
|
|
39131
|
+
});
|
|
39132
|
+
}
|
|
39133
|
+
}
|
|
39134
|
+
}
|
|
39135
|
+
}
|
|
39136
|
+
return stubs;
|
|
39137
|
+
}
|
|
39138
|
+
/**
|
|
39139
|
+
* 从 plan markdown 文件中提取并组装 fullNaturalTS,返回字符串或错误信息
|
|
39140
|
+
*/
|
|
39141
|
+
function extractFullNaturalTS(filePath, logger = defaultLogger) {
|
|
39142
|
+
// 1. 验证路径模式(只检查文件名)
|
|
39143
|
+
const basename = path$1.basename(filePath);
|
|
39144
|
+
if (!DOC_PATTERNS.some((p) => p.test(basename))) {
|
|
39145
|
+
logger.error(`路径不匹配任何支持的文档类型:${basename}\n` +
|
|
39146
|
+
`支持的类型:enums.md, entity-*.md, structure-*.md, logic-*.md, view-*.md`);
|
|
39147
|
+
return { success: false };
|
|
39148
|
+
}
|
|
39149
|
+
// 2. 读取文件
|
|
39150
|
+
if (!libExports.existsSync(filePath)) {
|
|
39151
|
+
logger.error(`文件不存在:${filePath}`);
|
|
39152
|
+
return { success: false };
|
|
39153
|
+
}
|
|
39154
|
+
const content = libExports.readFileSync(filePath, 'utf-8');
|
|
39155
|
+
// 3. 提取 naturalts 代码块,转换为 FileInfo[]
|
|
39156
|
+
const blocks = extractCodeBlocks(content).filter((b) => b.lang === 'naturalts');
|
|
39157
|
+
if (blocks.length === 0) {
|
|
39158
|
+
// logger.warn('未发现 naturalts 代码块');
|
|
39159
|
+
return { fullNaturalTS: '' };
|
|
39160
|
+
}
|
|
39161
|
+
const isViewDoc = /^view-.+\.md$/.test(basename);
|
|
39162
|
+
const expectedExt = isViewDoc ? '.tsx' : '.ts';
|
|
39163
|
+
const docTypePrefix = basename.split(/[-.]/)[0] ?? '';
|
|
39164
|
+
const naslTypeName = DOC_TO_NASL_TYPE[docTypePrefix];
|
|
39165
|
+
const expectedPattern = NASL_FILE_TYPES.find((t) => t.name === naslTypeName)?.fileNamePattern;
|
|
39166
|
+
const errors = [];
|
|
39167
|
+
const files = blocks.map((block) => {
|
|
39168
|
+
const match = block.langExtra?.match(/path="([^"]+)"/);
|
|
39169
|
+
if (!match) {
|
|
39170
|
+
errors.push(`代码块缺少 path 属性(第 ${block.startLine} 行)`);
|
|
39171
|
+
return null;
|
|
39172
|
+
}
|
|
39173
|
+
const blockPath = match[1];
|
|
39174
|
+
if (!blockPath.endsWith(expectedExt)) {
|
|
39175
|
+
errors.push(`代码块 path 扩展名不正确,应为 ${expectedExt}(第 ${block.startLine} 行):${blockPath}`);
|
|
39176
|
+
return null;
|
|
39177
|
+
}
|
|
39178
|
+
if (!isKnownFileType(blockPath)) {
|
|
39179
|
+
const hint = expectedPattern ? `\n正确格式应匹配:${expectedPattern}` : '';
|
|
39180
|
+
errors.push(`代码块 path 不是合法的 NASL 文件类型(第 ${block.startLine} 行):${blockPath}${hint}`);
|
|
39181
|
+
return null;
|
|
39182
|
+
}
|
|
39183
|
+
const fileInfo = { path: blockPath, content: block.code };
|
|
39184
|
+
return fileInfo;
|
|
39185
|
+
}).filter((f) => f !== null);
|
|
39186
|
+
if (errors.length > 0) {
|
|
39187
|
+
logger.error(`在 ${filePath} 检测到以下错误:\n` + errors.join('\n'));
|
|
39188
|
+
return { success: false };
|
|
39189
|
+
}
|
|
39190
|
+
// 4. 补充父级 view stub(tsx 文件需要祖先页面定义)
|
|
39191
|
+
const stubs = buildViewStubs(files);
|
|
39192
|
+
// 5. 复用 composeToString 组装(包含排序和 namespace 构建)
|
|
39193
|
+
try {
|
|
39194
|
+
return { fullNaturalTS: composeToString([...files, ...stubs], { isDocCheck: true }) };
|
|
39195
|
+
}
|
|
39196
|
+
catch (err) {
|
|
39197
|
+
logger.error(`在 ${filePath} 检测到以下错误:\n` + err.message);
|
|
39198
|
+
return { success: false };
|
|
39199
|
+
}
|
|
39200
|
+
}
|
|
39201
|
+
|
|
39038
39202
|
const transformFns = {
|
|
39039
39203
|
/**
|
|
39040
39204
|
* 将 src 中的文件组合成 fullNaturalTS
|
|
@@ -39061,6 +39225,33 @@ const transformFns = {
|
|
|
39061
39225
|
writeFileWithLog(outputPath, fullNaturalTS, logger);
|
|
39062
39226
|
logger.success(`文件已输出到: ${outputPath}`);
|
|
39063
39227
|
},
|
|
39228
|
+
/**
|
|
39229
|
+
* 将 plan markdown 文档中的 naturalts 代码块组合成 fullNaturalTS 文件
|
|
39230
|
+
*/
|
|
39231
|
+
async doc2full(entry, options) {
|
|
39232
|
+
const logger = options?.logger || defaultLogger;
|
|
39233
|
+
if (!entry) {
|
|
39234
|
+
logger.error('请指定 plan 文档路径');
|
|
39235
|
+
logger.exit(1);
|
|
39236
|
+
return;
|
|
39237
|
+
}
|
|
39238
|
+
const extracted = extractFullNaturalTS(entry, logger);
|
|
39239
|
+
if ('success' in extracted) {
|
|
39240
|
+
logger.exit(1);
|
|
39241
|
+
return;
|
|
39242
|
+
}
|
|
39243
|
+
if (!extracted.fullNaturalTS) {
|
|
39244
|
+
logger.warn('未发现 naturalts 代码块,跳过输出');
|
|
39245
|
+
return;
|
|
39246
|
+
}
|
|
39247
|
+
// 确定输出路径
|
|
39248
|
+
const projectRoot = getProjectRoot();
|
|
39249
|
+
const outputPath = options?.output
|
|
39250
|
+
? path$1.resolve(projectRoot, options.output)
|
|
39251
|
+
: path$1.join(projectRoot, './full-natural.ts');
|
|
39252
|
+
writeFileWithLog(outputPath, extracted.fullNaturalTS, logger);
|
|
39253
|
+
logger.success(`文件已输出到: ${outputPath}`);
|
|
39254
|
+
},
|
|
39064
39255
|
/**
|
|
39065
39256
|
* 将 JSON 文件转换成 src 的 files
|
|
39066
39257
|
* TODO: 待实现
|
|
@@ -39193,7 +39384,7 @@ async function installByJSON(json, options) {
|
|
|
39193
39384
|
logger.success(`成功写入 ${writtenCount} 个依赖文件到 ${config.srcDir} 目录`);
|
|
39194
39385
|
}
|
|
39195
39386
|
|
|
39196
|
-
var version = "0.3.
|
|
39387
|
+
var version = "0.3.1";
|
|
39197
39388
|
var pkg = {
|
|
39198
39389
|
version: version};
|
|
39199
39390
|
|
|
@@ -39355,12 +39546,12 @@ program
|
|
|
39355
39546
|
});
|
|
39356
39547
|
program
|
|
39357
39548
|
.command('transform <transformType> [entry]')
|
|
39358
|
-
.description('转换文件格式\n transformType: files2full (将 src 文件组合成 fullNaturalTS), json2files (将 JSON 转换为文件), files2json (将 src 文件转换为 JSON)')
|
|
39549
|
+
.description('转换文件格式\n transformType: files2full (将 src 文件组合成 fullNaturalTS), json2files (将 JSON 转换为文件), files2json (将 src 文件转换为 JSON), doc2full (将 plan markdown 文档组合成 fullNaturalTS)')
|
|
39359
39550
|
.option('-o, --output <outputPath>', '指定输出路径')
|
|
39360
39551
|
.option('-v, --verbose', '显示详细信息,如依赖分析树')
|
|
39361
39552
|
.action(async (transformType, entry, options) => {
|
|
39362
39553
|
try {
|
|
39363
|
-
const validTypes = ['files2full', 'json2files', 'files2json'];
|
|
39554
|
+
const validTypes = ['files2full', 'json2files', 'files2json', 'doc2full'];
|
|
39364
39555
|
if (!validTypes.includes(transformType)) {
|
|
39365
39556
|
defaultLogger.error(`无效的转换类型: ${transformType}。支持的类型: ${validTypes.join(', ')}`);
|
|
39366
39557
|
process.exit(1);
|