@nasl/cli 0.1.17 → 0.1.19
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 +115 -11
- package/dist/bin/nasl.mjs +267 -57
- package/dist/bin/nasl.mjs.map +1 -1
- package/dist/bin/naslc.mjs +259 -50
- package/dist/bin/naslc.mjs.map +1 -1
- package/dist/index.mjs +266 -56
- package/dist/index.mjs.map +1 -1
- package/out/apis/cachable.d.ts +16 -0
- package/out/apis/cachable.d.ts.map +1 -0
- package/out/apis/cachable.js +142 -0
- package/out/apis/cachable.js.map +1 -0
- package/out/apis/createAxios.d.ts.map +1 -1
- package/out/apis/createAxios.js +2 -3
- package/out/apis/createAxios.js.map +1 -1
- package/out/apis/openapi.d.ts +5 -2
- package/out/apis/openapi.d.ts.map +1 -1
- package/out/apis/openapi.js +21 -25
- package/out/apis/openapi.js.map +1 -1
- package/out/commands/createAppInIde.d.ts.map +1 -1
- package/out/commands/createAppInIde.js +4 -0
- package/out/commands/createAppInIde.js.map +1 -1
- package/out/commands/transform.d.ts.map +1 -1
- package/out/commands/transform.js +1 -4
- package/out/commands/transform.js.map +1 -1
- package/out/services/compose.d.ts +1 -1
- package/out/services/compose.d.ts.map +1 -1
- package/out/services/compose.js +66 -18
- package/out/services/compose.js.map +1 -1
- package/out/services/resolve.d.ts.map +1 -1
- package/out/services/resolve.js +30 -5
- package/out/services/resolve.js.map +1 -1
- package/out/utils/config.d.ts.map +1 -1
- package/out/utils/config.js +34 -2
- package/out/utils/config.js.map +1 -1
- package/out/utils/file.d.ts +12 -0
- package/out/utils/file.d.ts.map +1 -1
- package/out/utils/file.js +18 -0
- package/out/utils/file.js.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as sysPath from 'path';
|
|
2
|
-
import sysPath__default from 'path';
|
|
3
|
-
import require$$0$3, { unwatchFile, watchFile, watch as watch$1, stat as stat$3 } from 'fs';
|
|
2
|
+
import sysPath__default, { join } from 'path';
|
|
3
|
+
import require$$0$3, { promises, unwatchFile, watchFile, watch as watch$1, stat as stat$3 } from 'fs';
|
|
4
4
|
import stream$4, { Readable } from 'stream';
|
|
5
5
|
import require$$0$1, { type as type$1 } from 'os';
|
|
6
6
|
import require$$0$2 from 'util';
|
|
@@ -11,14 +11,14 @@ import require$$5 from 'assert';
|
|
|
11
11
|
import require$$3 from 'http';
|
|
12
12
|
import require$$4 from 'https';
|
|
13
13
|
import require$$0$6 from 'url';
|
|
14
|
-
import crypto$1 from 'crypto';
|
|
14
|
+
import crypto$1, { createHash } from 'crypto';
|
|
15
15
|
import http2 from 'http2';
|
|
16
16
|
import zlib from 'zlib';
|
|
17
17
|
import { spawn, spawnSync } from 'child_process';
|
|
18
18
|
import { realpath as realpath$1, stat as stat$2, lstat as lstat$1, open, readdir as readdir$1 } from 'fs/promises';
|
|
19
19
|
import { lstat, stat as stat$1, readdir, realpath } from 'node:fs/promises';
|
|
20
20
|
import { Readable as Readable$1 } from 'node:stream';
|
|
21
|
-
import { resolve, join, relative, sep } from 'node:path';
|
|
21
|
+
import { resolve, join as join$1, relative, sep } from 'node:path';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* 默认配置
|
|
@@ -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,26 @@ function composeToString(files) {
|
|
|
68
118
|
const arr = file.path.split('.');
|
|
69
119
|
const ext = arr.pop();
|
|
70
120
|
const nameFromPath = arr.pop();
|
|
71
|
-
|
|
121
|
+
// 判断是否是特殊文件类型(variables 或 extensions)
|
|
122
|
+
const isVariablesFile = nameFromPath === 'variables';
|
|
123
|
+
const isExtensionsFile = arr[0] === 'extensions';
|
|
124
|
+
const isSpecialFile = isVariablesFile || isExtensionsFile;
|
|
125
|
+
// 特殊文件的 namespace 包含文件名,普通文件不包含
|
|
126
|
+
const namespace = isSpecialFile ? [...arr, nameFromPath].join('.') : arr.join('.');
|
|
72
127
|
if (['ts', 'tsx'].includes(ext)) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
else if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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]}`);
|
|
128
|
+
if (isVariablesFile) {
|
|
129
|
+
validateVariablesFile(file, errors);
|
|
130
|
+
}
|
|
131
|
+
else if (isExtensionsFile) {
|
|
132
|
+
validateExtensionsFile(file, errors);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
validateNormalFile(file, nameFromPath, namespace, errors);
|
|
89
136
|
}
|
|
90
137
|
}
|
|
91
138
|
return `namespace ${namespace} {\n${file.content}\n}\n`;
|
|
92
|
-
})
|
|
139
|
+
})
|
|
140
|
+
.join('\n');
|
|
93
141
|
if (errors.length > 0) {
|
|
94
142
|
throw new Error(errors.join('\n============\n'));
|
|
95
143
|
}
|
|
@@ -13582,10 +13630,42 @@ function loadConfig(configDir) {
|
|
|
13582
13630
|
const content = libExports.readFileSync(configPath, 'utf-8');
|
|
13583
13631
|
const config = JSON.parse(content);
|
|
13584
13632
|
// 验证必需字段
|
|
13585
|
-
if (!config.
|
|
13586
|
-
defaultLogger.error('
|
|
13633
|
+
if (!config.srcDir || !config.outDir) {
|
|
13634
|
+
defaultLogger.error('配置文件格式不正确,缺少必需的 srcDir 或 outDir 字段');
|
|
13587
13635
|
return defaultLogger.exit(1);
|
|
13588
13636
|
}
|
|
13637
|
+
// 环境变量优先级高于配置文件
|
|
13638
|
+
// ideVersion: 环境变量优先,如果都没有则报错
|
|
13639
|
+
if (process.env.NASL_IDE_VERSION)
|
|
13640
|
+
config.ideVersion = process.env.NASL_IDE_VERSION;
|
|
13641
|
+
if (!config.ideVersion) {
|
|
13642
|
+
defaultLogger.error('缺少配置 ideVersion,请在配置文件中添加,或在环境变量中配置 NASL_IDE_VERSION');
|
|
13643
|
+
return defaultLogger.exit(1);
|
|
13644
|
+
}
|
|
13645
|
+
// serverBaseURL: 环境变量优先,如果都没有则报错
|
|
13646
|
+
if (process.env.NASL_SERVER_BASE_URL)
|
|
13647
|
+
config.serverBaseURL = process.env.NASL_SERVER_BASE_URL;
|
|
13648
|
+
if (!config.serverBaseURL) {
|
|
13649
|
+
defaultLogger.error('缺少配置 serverBaseURL,请在配置文件中添加,或在环境变量中配置 NASL_SERVER_BASE_URL');
|
|
13650
|
+
return defaultLogger.exit(1);
|
|
13651
|
+
}
|
|
13652
|
+
// 环境变量优先
|
|
13653
|
+
if (process.env.USE_LCAP_OPENAPI !== undefined)
|
|
13654
|
+
config.useOPENAPI = process.env.USE_LCAP_OPENAPI === 'true';
|
|
13655
|
+
if (process.env.LCAP_OPENAPI_AK)
|
|
13656
|
+
config.OPENAPI_AK = process.env.LCAP_OPENAPI_AK;
|
|
13657
|
+
if (process.env.LCAP_OPENAPI_SK)
|
|
13658
|
+
config.OPENAPI_SK = process.env.LCAP_OPENAPI_SK;
|
|
13659
|
+
// 如果启用了 OpenAPI,验证必需的 AK 和 SK
|
|
13660
|
+
if (config.useOPENAPI) {
|
|
13661
|
+
if (!config.OPENAPI_AK || !config.OPENAPI_SK) {
|
|
13662
|
+
defaultLogger.error(`配置了 useOPENAPI,但缺少配置 OPENAPI_AK 和 OPENAPI_SK:
|
|
13663
|
+
- 在 nasl.config.json 中配置 OPENAPI_AK 和 OPENAPI_SK,或者在环境变量中配置 LCAP_OPENAPI_AK 和 LCAP_OPENAPI_SK
|
|
13664
|
+
- 或将 useOPENAPI 配置为 false
|
|
13665
|
+
`);
|
|
13666
|
+
return defaultLogger.exit(1);
|
|
13667
|
+
}
|
|
13668
|
+
}
|
|
13589
13669
|
return config;
|
|
13590
13670
|
}
|
|
13591
13671
|
catch (error) {
|
|
@@ -13631,6 +13711,23 @@ function writeFileWithLog(filePath, content, logger) {
|
|
|
13631
13711
|
throw error;
|
|
13632
13712
|
}
|
|
13633
13713
|
}
|
|
13714
|
+
/**
|
|
13715
|
+
* 判断文件路径是否为已知的 NASL 文件类型
|
|
13716
|
+
* 支持的类型:
|
|
13717
|
+
* - 枚举 (enums)
|
|
13718
|
+
* - 实体 (entities)
|
|
13719
|
+
* - 数据结构 (structures)
|
|
13720
|
+
* - 逻辑 (logics)
|
|
13721
|
+
* - 页面 (views)
|
|
13722
|
+
* - 前端/服务端全局变量 (variables)
|
|
13723
|
+
* - 依赖库 (extensions)
|
|
13724
|
+
*/
|
|
13725
|
+
function isKnownFileType(filePath) {
|
|
13726
|
+
return (/\.(enums|entities|structures|logics|views)\.\w+\.(ts|tsx)$/.test(filePath) || // 枚举、实体、数据结构、逻辑、页面
|
|
13727
|
+
/^app\..*\.variables\.ts$/.test(filePath) || // 前端/服务端全局变量 (以app开头,以variables.ts结尾)
|
|
13728
|
+
/^extensions\.\w+\.(ts|tsx)$/.test(filePath) // 依赖库
|
|
13729
|
+
);
|
|
13730
|
+
}
|
|
13634
13731
|
|
|
13635
13732
|
/**
|
|
13636
13733
|
* 扫描目录下的所有 NASL 文件
|
|
@@ -13672,11 +13769,13 @@ async function scanEntryFiles(projectRoot, patterns, logger) {
|
|
|
13672
13769
|
*/
|
|
13673
13770
|
function extractDeps(content) {
|
|
13674
13771
|
const deps = new Set();
|
|
13675
|
-
//
|
|
13772
|
+
// 预处理:移除注释和字符串字面量,避免它们影响依赖分析
|
|
13676
13773
|
let processedContent = content
|
|
13677
13774
|
.replace(/\/\/.*$/gm, '') // 移除单行注释 // ...
|
|
13678
|
-
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
13679
|
-
|
|
13775
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // 移除多行注释 /* ... */
|
|
13776
|
+
.replace(/'(?:[^'\\]|\\.)*'/g, '') // 移除单引号字符串 'xxx'
|
|
13777
|
+
.replace(/"(?:[^"\\]|\\.)*"/g, ''); // 移除双引号字符串 "xxx"
|
|
13778
|
+
const allMatches = processedContent.matchAll(/(app|extensions)\.\w+\.[\w.]+/g); // 起码要2个点,支持 app.* 和 extensions.*
|
|
13680
13779
|
for (const match of allMatches) {
|
|
13681
13780
|
let dep = match[0];
|
|
13682
13781
|
if (/Entity$|Entity\./.test(dep)) {
|
|
@@ -13685,6 +13784,14 @@ function extractDeps(content) {
|
|
|
13685
13784
|
else if (/\.enums\.(\w+)\.(\w+)/.test(dep)) {
|
|
13686
13785
|
dep = dep.replace(/\.enums\.(\w+).+$/, '.enums.$1');
|
|
13687
13786
|
}
|
|
13787
|
+
else if (/\.variables\.(\w+)/.test(dep)) {
|
|
13788
|
+
// 处理 variables 依赖:app.backend.variables.xxx -> app.backend.variables
|
|
13789
|
+
dep = dep.replace(/\.variables\..+$/, '.variables');
|
|
13790
|
+
}
|
|
13791
|
+
else if (/^extensions\.\w+\.\w+/.test(dep)) {
|
|
13792
|
+
// 处理 extensions 依赖:extensions.lcap_auth.xxx -> extensions.lcap_auth
|
|
13793
|
+
dep = dep.replace(/^(extensions\.\w+)\..+$/, '$1');
|
|
13794
|
+
}
|
|
13688
13795
|
else if (/app\.dataSources\.\w+\.entities\.\w+\.\w+$/.test(dep)) {
|
|
13689
13796
|
continue; // 应该是写法有问题,跳过让后面的 checker 做检查
|
|
13690
13797
|
}
|
|
@@ -13806,7 +13913,12 @@ async function collectDeps(patterns, projectRoot, srcDir, logger, verbose) {
|
|
|
13806
13913
|
while (filesToProcess.length > 0) {
|
|
13807
13914
|
const pathRelativeToSrc = filesToProcess.shift();
|
|
13808
13915
|
if (processedFileMap.has(pathRelativeToSrc))
|
|
13809
|
-
continue; // 跳过已处理的文件
|
|
13916
|
+
continue; // 跳过已处理的文件
|
|
13917
|
+
// 检查文件类型是否支持
|
|
13918
|
+
if (!isKnownFileType(pathRelativeToSrc)) {
|
|
13919
|
+
logger.warn(`跳过不支持的文件类型: ${pathRelativeToSrc}`);
|
|
13920
|
+
continue;
|
|
13921
|
+
}
|
|
13810
13922
|
try {
|
|
13811
13923
|
const { fileInfo, newDeps } = processFileDeps(pathRelativeToSrc, srcDir, matchedFileSet, processedFileMap, depNotFoundList, logger, verbose);
|
|
13812
13924
|
result.push(fileInfo);
|
|
@@ -13886,8 +13998,18 @@ async function resolveNASLFiles(entry, logger, depMode, verbose) {
|
|
|
13886
13998
|
}
|
|
13887
13999
|
logger.info(`找到 ${collectedFiles.length} 个 NASL 文件`);
|
|
13888
14000
|
}
|
|
14001
|
+
// 统一过滤掉不支持的文件类型
|
|
14002
|
+
const filteredFiles = [];
|
|
14003
|
+
collectedFiles.forEach((file) => {
|
|
14004
|
+
if (isKnownFileType(file.path)) {
|
|
14005
|
+
filteredFiles.push(file);
|
|
14006
|
+
}
|
|
14007
|
+
else {
|
|
14008
|
+
logger.warn(`跳过不支持的文件类型: ${file.path}`);
|
|
14009
|
+
}
|
|
14010
|
+
});
|
|
13889
14011
|
return {
|
|
13890
|
-
collectedFiles,
|
|
14012
|
+
collectedFiles: filteredFiles,
|
|
13891
14013
|
config,
|
|
13892
14014
|
projectRoot,
|
|
13893
14015
|
srcDir,
|
|
@@ -34401,18 +34523,99 @@ function v4(options, buf, offset) {
|
|
|
34401
34523
|
return unsafeStringify(rnds);
|
|
34402
34524
|
}
|
|
34403
34525
|
|
|
34526
|
+
function generateHash(params) {
|
|
34527
|
+
const hash = createHash('sha256');
|
|
34528
|
+
hash.update(JSON.stringify(params));
|
|
34529
|
+
return hash.digest('hex');
|
|
34530
|
+
}
|
|
34531
|
+
async function getCachePathWithTimestamp(hash) {
|
|
34532
|
+
const cacheDir = join(process.cwd(), '.cache');
|
|
34533
|
+
await promises.mkdir(cacheDir, { recursive: true });
|
|
34534
|
+
return join(cacheDir, `${hash}_${Date.now()}.json`);
|
|
34535
|
+
}
|
|
34404
34536
|
/**
|
|
34405
|
-
*
|
|
34537
|
+
* 查找指定 hash 的有效缓存文件
|
|
34538
|
+
* @param hash 缓存标识
|
|
34539
|
+
* @param ttl 缓存有效期(毫秒)
|
|
34540
|
+
* @returns 缓存文件路径和缓存内容,如果没有有效缓存则返回 null
|
|
34406
34541
|
*/
|
|
34407
|
-
function
|
|
34408
|
-
|
|
34542
|
+
async function findValidCache(hash, ttl) {
|
|
34543
|
+
const cacheDir = join(process.cwd(), '.cache');
|
|
34544
|
+
try {
|
|
34545
|
+
await promises.mkdir(cacheDir, { recursive: true });
|
|
34546
|
+
const files = await promises.readdir(cacheDir);
|
|
34547
|
+
const now = Date.now();
|
|
34548
|
+
// 查找所有匹配的缓存文件
|
|
34549
|
+
const matchingFiles = files
|
|
34550
|
+
.filter((file) => file.startsWith(`${hash}_`) && file.endsWith('.json'))
|
|
34551
|
+
.map((file) => {
|
|
34552
|
+
const match = file.match(/_(\d+)\.json$/);
|
|
34553
|
+
if (!match)
|
|
34554
|
+
return null;
|
|
34555
|
+
const timestamp = parseInt(match[1], 10);
|
|
34556
|
+
return {
|
|
34557
|
+
path: join(cacheDir, file),
|
|
34558
|
+
timestamp,
|
|
34559
|
+
isExpired: now - timestamp > ttl,
|
|
34560
|
+
};
|
|
34561
|
+
})
|
|
34562
|
+
.filter((item) => item !== null);
|
|
34563
|
+
// 删除过期的缓存文件
|
|
34564
|
+
const expiredFiles = matchingFiles.filter((item) => item.isExpired);
|
|
34565
|
+
await Promise.all(expiredFiles.map((item) => promises.unlink(item.path).catch(() => { })));
|
|
34566
|
+
// 查找有效的缓存文件(按时间戳降序排序,获取最新的)
|
|
34567
|
+
const validFiles = matchingFiles.filter((item) => !item.isExpired).sort((a, b) => b.timestamp - a.timestamp);
|
|
34568
|
+
if (validFiles.length > 0) {
|
|
34569
|
+
const cacheFile = validFiles[0];
|
|
34570
|
+
const cacheContent = await promises.readFile(cacheFile.path, 'utf-8');
|
|
34571
|
+
const cachedResult = JSON.parse(cacheContent);
|
|
34572
|
+
return { path: cacheFile.path, data: cachedResult };
|
|
34573
|
+
}
|
|
34574
|
+
return null;
|
|
34575
|
+
}
|
|
34576
|
+
catch (error) {
|
|
34577
|
+
return null;
|
|
34578
|
+
}
|
|
34409
34579
|
}
|
|
34410
34580
|
/**
|
|
34411
|
-
*
|
|
34581
|
+
* 高阶函数,为异步函数添加带时间戳的缓存功能
|
|
34582
|
+
* @param fn 需要添加缓存的函数
|
|
34583
|
+
* @param ttl 缓存有效期(毫秒),默认 1 小时
|
|
34584
|
+
* @param getCacheKey 可选的自定义缓存 key 生成函数,如果不提供则使用整个 params
|
|
34585
|
+
* @returns 带缓存的函数版本
|
|
34412
34586
|
*/
|
|
34413
|
-
function
|
|
34414
|
-
|
|
34587
|
+
function cachableWithTTL(fn, ttl = 3600 * 1000, // 默认 1 小时
|
|
34588
|
+
getCacheKey) {
|
|
34589
|
+
return async (params, extraParams) => {
|
|
34590
|
+
const functionName = fn.name;
|
|
34591
|
+
const logPrefix = functionName ? `[${functionName}]` : '[cachableWithTTL]';
|
|
34592
|
+
// 检查是否启用缓存
|
|
34593
|
+
if (process.env.NO_AI_CACHE) {
|
|
34594
|
+
return fn(params, extraParams);
|
|
34595
|
+
}
|
|
34596
|
+
// 使用自定义的 getCacheKey 或默认使用整个 params
|
|
34597
|
+
const cacheKey = getCacheKey ? getCacheKey(params) : params;
|
|
34598
|
+
const hash = generateHash(cacheKey);
|
|
34599
|
+
// 查找有效的缓存
|
|
34600
|
+
const validCache = await findValidCache(hash, ttl);
|
|
34601
|
+
if (validCache) {
|
|
34602
|
+
console.log(`${logPrefix} ✓ 使用缓存`);
|
|
34603
|
+
return validCache.data;
|
|
34604
|
+
}
|
|
34605
|
+
// 调用原函数
|
|
34606
|
+
const result = await fn(params, extraParams);
|
|
34607
|
+
// 保存结果到缓存(带时间戳)
|
|
34608
|
+
try {
|
|
34609
|
+
const cachePath = await getCachePathWithTimestamp(hash);
|
|
34610
|
+
await promises.writeFile(cachePath, JSON.stringify(result, null, 2), 'utf-8');
|
|
34611
|
+
}
|
|
34612
|
+
catch (error) {
|
|
34613
|
+
// 缓存写入失败,但不影响返回结果
|
|
34614
|
+
}
|
|
34615
|
+
return result;
|
|
34616
|
+
};
|
|
34415
34617
|
}
|
|
34618
|
+
|
|
34416
34619
|
/**
|
|
34417
34620
|
* 生成客户端签名信息
|
|
34418
34621
|
* @param appKey AppKey
|
|
@@ -34422,8 +34625,8 @@ function generateSignature(plainText) {
|
|
|
34422
34625
|
function generateClientSignature(appKey, secretKey) {
|
|
34423
34626
|
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
34424
34627
|
const nonce = v4();
|
|
34425
|
-
const plainText =
|
|
34426
|
-
const signature =
|
|
34628
|
+
const plainText = `${appKey}&${nonce}&${timestamp}&${secretKey}`;
|
|
34629
|
+
const signature = crypto$1.createHash('md5').update(plainText).digest('hex');
|
|
34427
34630
|
return {
|
|
34428
34631
|
appKey,
|
|
34429
34632
|
timestamp,
|
|
@@ -34448,12 +34651,12 @@ function generateBaseHeaders(ak, sk) {
|
|
|
34448
34651
|
};
|
|
34449
34652
|
}
|
|
34450
34653
|
/**
|
|
34451
|
-
* 获取租户的 signInfo
|
|
34654
|
+
* 获取租户的 signInfo(内部实现)
|
|
34452
34655
|
* @param options 服务器选项
|
|
34453
34656
|
* @param baseHeaders 基础认证头
|
|
34454
34657
|
* @returns x-signInfo 值
|
|
34455
34658
|
*/
|
|
34456
|
-
async function
|
|
34659
|
+
async function _fetchSignInfo({ options, baseHeaders }) {
|
|
34457
34660
|
const tenantName = options.tenantName || 'defaulttenant';
|
|
34458
34661
|
const userName = tenantName === 'defaulttenant' ? 'admin' : `${tenantName}-admin`;
|
|
34459
34662
|
const data = {
|
|
@@ -34475,6 +34678,14 @@ async function fetchSignInfo(options, baseHeaders) {
|
|
|
34475
34678
|
return null;
|
|
34476
34679
|
}
|
|
34477
34680
|
}
|
|
34681
|
+
/**
|
|
34682
|
+
* 获取租户的 signInfo(带缓存,1 小时过期)
|
|
34683
|
+
* @param options 服务器选项
|
|
34684
|
+
* @param baseHeaders 基础认证头
|
|
34685
|
+
* @returns x-signInfo 值
|
|
34686
|
+
*/
|
|
34687
|
+
const fetchSignInfo = cachableWithTTL(_fetchSignInfo, 3600 * 1000, // 1 小时
|
|
34688
|
+
(params) => params.options);
|
|
34478
34689
|
/**
|
|
34479
34690
|
* 生成完整的认证头(包含 x-signInfo)
|
|
34480
34691
|
* @param options 服务器选项
|
|
@@ -34484,19 +34695,18 @@ async function generateCompleteHeaders(options) {
|
|
|
34484
34695
|
const headers = {
|
|
34485
34696
|
'Content-Type': 'application/json',
|
|
34486
34697
|
};
|
|
34487
|
-
const OPENAPI_AK = options.OPENAPI_AK || process.env.LCAP_OPENAPI_AK;
|
|
34488
|
-
const OPENAPI_SK = options.OPENAPI_SK || process.env.LCAP_OPENAPI_SK;
|
|
34489
34698
|
// 如果没有提供 ak 和 sk,返回基础 headers
|
|
34490
|
-
if (!OPENAPI_AK || !OPENAPI_SK) {
|
|
34491
|
-
throw new Error(`配置了 useOPENAPI
|
|
34492
|
-
-
|
|
34493
|
-
-
|
|
34699
|
+
if (!options.OPENAPI_AK || !options.OPENAPI_SK) {
|
|
34700
|
+
throw new Error(`配置了 useOPENAPI,但缺少配置 OPENAPI_AK 和 OPENAPI_SK:
|
|
34701
|
+
- 在 nasl.config.json 中配置 OPENAPI_AK 和 OPENAPI_SK,或者在环境变量中配置 LCAP_OPENAPI_AK 和 LCAP_OPENAPI_SK
|
|
34702
|
+
- 或将 useOPENAPI 配置为 false
|
|
34703
|
+
`);
|
|
34494
34704
|
}
|
|
34495
34705
|
// 生成基础认证头
|
|
34496
|
-
const baseHeaders = generateBaseHeaders(OPENAPI_AK, OPENAPI_SK);
|
|
34706
|
+
const baseHeaders = generateBaseHeaders(options.OPENAPI_AK, options.OPENAPI_SK);
|
|
34497
34707
|
Object.assign(headers, baseHeaders);
|
|
34498
34708
|
// 获取 signInfo
|
|
34499
|
-
const signInfo = await fetchSignInfo(options, headers);
|
|
34709
|
+
const signInfo = await fetchSignInfo({ options, baseHeaders: headers });
|
|
34500
34710
|
if (signInfo) {
|
|
34501
34711
|
headers['x-signInfo'] = signInfo;
|
|
34502
34712
|
}
|
|
@@ -34511,10 +34721,9 @@ async function generateCompleteHeaders(options) {
|
|
|
34511
34721
|
async function createAxios(options) {
|
|
34512
34722
|
// 如果需要鉴权,拼接 /openapi/v3/nasl;否则使用原始 URL
|
|
34513
34723
|
const serverBaseURL = new URL(options.serverBaseURL).origin;
|
|
34514
|
-
const
|
|
34515
|
-
const baseURL = useOPENAPI ? `${serverBaseURL}/openapi/v3/nasl` : `${serverBaseURL}/api/v1/nasl`;
|
|
34724
|
+
const baseURL = options.useOPENAPI ? `${serverBaseURL}/openapi/v3/nasl` : `${serverBaseURL}/api/v1/nasl`;
|
|
34516
34725
|
// 如果需要鉴权,生成完整的认证头;否则只使用基础 headers
|
|
34517
|
-
const headers = useOPENAPI ? await generateCompleteHeaders(options) : { 'Content-Type': 'application/json' };
|
|
34726
|
+
const headers = options.useOPENAPI ? await generateCompleteHeaders(options) : { 'Content-Type': 'application/json' };
|
|
34518
34727
|
console.log('本次服务调用方为:', baseURL);
|
|
34519
34728
|
const instance = axios.create({
|
|
34520
34729
|
baseURL,
|
|
@@ -34602,7 +34811,7 @@ async function checkApi(fullNaturalTS, options) {
|
|
|
34602
34811
|
async function createAppSyncApi(fullNaturalTS, options) {
|
|
34603
34812
|
const url = new URL(options.serverBaseURL);
|
|
34604
34813
|
const origin = url.origin + '/app-ai-creator';
|
|
34605
|
-
const axios = await createAxios({ serverBaseURL: origin});
|
|
34814
|
+
const axios = await createAxios({ serverBaseURL: origin, ideVersion: options.ideVersion });
|
|
34606
34815
|
try {
|
|
34607
34816
|
const res = await axios.post('/api/createAppSync', {
|
|
34608
34817
|
fullNaturalTS,
|
|
@@ -34975,6 +35184,10 @@ async function createAppInIde(entry, options) {
|
|
|
34975
35184
|
logger.info('开始创建应用在 IDE 中...');
|
|
34976
35185
|
// 收集需要处理的文件
|
|
34977
35186
|
const { collectedFiles, config } = await resolveNASLFiles(entry, logger, false, options?.verbose);
|
|
35187
|
+
if (config.useOPENAPI) {
|
|
35188
|
+
logger.error('create-app-in-ide 暂不支持 useOPENAPI 模式');
|
|
35189
|
+
logger.exit(1);
|
|
35190
|
+
}
|
|
34978
35191
|
// 生成 fullNaturalTS
|
|
34979
35192
|
logger.newLine();
|
|
34980
35193
|
logger.info('正在生成 NaturalTS 代码...');
|
|
@@ -35094,10 +35307,7 @@ const transformFns = {
|
|
|
35094
35307
|
jsonContent = libExports.readFileSync(entry, 'utf-8');
|
|
35095
35308
|
logger.info(`读取到 JSON 文件: ${entry}`);
|
|
35096
35309
|
}
|
|
35097
|
-
const files = await transformJson2FilesApi(jsonContent ? JSON.parse(jsonContent) : {},
|
|
35098
|
-
serverBaseURL: config.serverBaseURL,
|
|
35099
|
-
ideVersion: config.ideVersion,
|
|
35100
|
-
});
|
|
35310
|
+
const files = await transformJson2FilesApi(jsonContent ? JSON.parse(jsonContent) : {}, config);
|
|
35101
35311
|
await Promise.all(files.map(async (file) => {
|
|
35102
35312
|
const outputPath = sysPath.join(projectRoot, config.srcDir, file.path);
|
|
35103
35313
|
await libExports.writeFile(outputPath, file.content);
|
|
@@ -35281,7 +35491,7 @@ class ReaddirpStream extends Readable$1 {
|
|
|
35281
35491
|
let entry;
|
|
35282
35492
|
const basename = this._isDirent ? dirent.name : dirent;
|
|
35283
35493
|
try {
|
|
35284
|
-
const fullPath = resolve(join(path, basename));
|
|
35494
|
+
const fullPath = resolve(join$1(path, basename));
|
|
35285
35495
|
entry = { path: relative(this._root, fullPath), fullPath, basename };
|
|
35286
35496
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
35287
35497
|
}
|