@lark-apaas/coding-template-nestjs-react-fullstack 0.1.42 → 0.1.43-alpha.20260918074821
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 +18 -0
- package/package.json +1 -1
- package/template/package-lock.json +692 -48
- package/template/package.json +4 -4
- package/template/scripts/build.sh +139 -52
- package/template/scripts/prune-smart.js +100 -11
- package/template/scripts/run.sh +4 -1
- package/template/tsconfig.node.json +2 -1
package/template/package.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"upgrade": "npx -y @lark-apaas/fullstack-cli sync --disable-gen-openapi"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@lark-apaas/fullstack-nestjs-core": "
|
|
31
|
+
"@lark-apaas/fullstack-nestjs-core": "1.1.63-alpha.20260918073530",
|
|
32
32
|
"@nestjs/axios": "^4.0.1",
|
|
33
33
|
"@nestjs/cache-manager": "^3.1.3",
|
|
34
34
|
"@nestjs/common": "^10.4.20",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@hookform/resolvers": "^5.2.2",
|
|
55
|
-
"@lark-apaas/client-toolkit": "^1.2.
|
|
56
|
-
"@lark-apaas/coding-preset-vite-react": "
|
|
57
|
-
"@lark-apaas/fullstack-presets": "
|
|
55
|
+
"@lark-apaas/client-toolkit": "^1.2.70",
|
|
56
|
+
"@lark-apaas/coding-preset-vite-react": "1.0.24-alpha.20260918073537",
|
|
57
|
+
"@lark-apaas/fullstack-presets": "1.1.24-alpha.20260918073533",
|
|
58
58
|
"@nestjs/cli": "^10.4.9",
|
|
59
59
|
"@radix-ui/react-accordion": "^1.2.12",
|
|
60
60
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
5
|
ROOT_DIR="$(pwd)"
|
|
6
|
-
|
|
6
|
+
DIST_DIR="$ROOT_DIR/dist"
|
|
7
7
|
|
|
8
8
|
# 记录总开始时间
|
|
9
9
|
TOTAL_START=$(node -e "console.log(Date.now())")
|
|
@@ -19,94 +19,174 @@ print_time() {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
# ==================== 步骤 0 ====================
|
|
22
|
-
echo "🗑️ [0/
|
|
22
|
+
echo "🗑️ [0/6] 安装插件"
|
|
23
23
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
24
24
|
npx fullstack-cli action-plugin init
|
|
25
25
|
print_time $STEP_START
|
|
26
26
|
echo ""
|
|
27
27
|
|
|
28
28
|
# ==================== 步骤 1 ====================
|
|
29
|
-
echo "📝 [1/
|
|
29
|
+
echo "📝 [1/6] 更新 openapi 代码"
|
|
30
30
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
31
31
|
npm run gen:openapi
|
|
32
32
|
print_time $STEP_START
|
|
33
33
|
echo ""
|
|
34
34
|
|
|
35
35
|
# ==================== 步骤 2 ====================
|
|
36
|
-
echo "🗑️ [2/
|
|
36
|
+
echo "🗑️ [2/6] 清理 dist 目录"
|
|
37
37
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
38
38
|
rm -rf "$ROOT_DIR/dist"
|
|
39
39
|
print_time $STEP_START
|
|
40
40
|
echo ""
|
|
41
41
|
|
|
42
42
|
# ==================== 步骤 3 ====================
|
|
43
|
-
echo "
|
|
43
|
+
echo "🗺️ [3/6] 生成路由定义"
|
|
44
44
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
45
45
|
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
# 在 client/server 构建之前生成到 dist/,供 DefinePlugin 注入前端 bundle
|
|
47
|
+
# 注意:nest-cli.json 中 deleteOutDir 必须为 false(模板默认值),否则 nest build 会清掉 dist/
|
|
48
|
+
echo " ├─ 生成 API 路由定义..."
|
|
49
|
+
npx generate-api-routes --server-dir ./server --out-dir ./dist > /tmp/gen-api-routes.log 2>&1 &
|
|
50
|
+
API_ROUTES_PID=$!
|
|
50
51
|
|
|
51
|
-
echo " ├─
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
echo " ├─ 生成页面路由定义..."
|
|
53
|
+
npx generate-page-routes --app-path ./client/src/app.tsx --out-dir ./dist > /tmp/gen-page-routes.log 2>&1 &
|
|
54
|
+
PAGE_ROUTES_PID=$!
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
CLIENT_EXIT=0
|
|
56
|
+
API_ROUTES_EXIT=0
|
|
57
|
+
PAGE_ROUTES_EXIT=0
|
|
58
58
|
|
|
59
|
-
wait $
|
|
60
|
-
wait $
|
|
59
|
+
wait $API_ROUTES_PID || API_ROUTES_EXIT=$?
|
|
60
|
+
wait $PAGE_ROUTES_PID || PAGE_ROUTES_EXIT=$?
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
if [ $API_ROUTES_EXIT -ne 0 ]; then
|
|
63
|
+
echo " ⚠️ API 路由生成失败(不影响构建)"
|
|
64
|
+
cat /tmp/gen-api-routes.log
|
|
65
|
+
else
|
|
66
|
+
echo " ✅ API 路由生成完成"
|
|
67
67
|
fi
|
|
68
68
|
|
|
69
|
-
if [ $
|
|
70
|
-
echo "
|
|
71
|
-
cat /tmp/
|
|
72
|
-
|
|
69
|
+
if [ $PAGE_ROUTES_EXIT -ne 0 ]; then
|
|
70
|
+
echo " ⚠️ 页面路由生成失败(不影响构建)"
|
|
71
|
+
cat /tmp/gen-page-routes.log
|
|
72
|
+
else
|
|
73
|
+
echo " ✅ 页面路由生成完成"
|
|
73
74
|
fi
|
|
74
|
-
|
|
75
|
-
echo " ✅ Server 构建完成"
|
|
76
|
-
echo " ✅ Client 构建完成"
|
|
77
75
|
print_time $STEP_START
|
|
78
76
|
echo ""
|
|
79
77
|
|
|
80
78
|
# ==================== 步骤 4 ====================
|
|
81
|
-
echo "
|
|
79
|
+
echo "🔨 [4/6] 并行构建 server 和 client"
|
|
82
80
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
# 给 server/client 构建子进程预留 8GB heap,缓解 vite build transform 阶段 OOM
|
|
83
|
+
# (典型错误:Reached heap limit Allocation failed)。
|
|
84
|
+
# 仅在外部未设置 NODE_OPTIONS 时注入,允许 CI / 用户通过外部环境变量完全覆盖
|
|
85
|
+
BUILD_NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=8192}"
|
|
86
|
+
|
|
87
|
+
# 根据 only_frontend_change 决定是否构建 server
|
|
88
|
+
if [[ "${only_frontend_change:-false}" == "true" ]]; then
|
|
89
|
+
echo "🔨 [4/6] 仅构建 client (only_frontend_change=true)"
|
|
90
|
+
|
|
91
|
+
echo " ├─ 启动 client 构建..."
|
|
92
|
+
NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:client > /tmp/build-client.log 2>&1
|
|
93
|
+
CLIENT_EXIT=$?
|
|
94
|
+
|
|
95
|
+
if [ $CLIENT_EXIT -ne 0 ]; then
|
|
96
|
+
echo " ❌ Client 构建失败"
|
|
97
|
+
cat /tmp/build-client.log
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
echo " ✅ Client 构建完成"
|
|
102
|
+
else
|
|
103
|
+
echo "🔨 [4/6] 并行构建 server 和 client"
|
|
104
|
+
|
|
105
|
+
# 并行构建
|
|
106
|
+
echo " ├─ 启动 server 构建..."
|
|
107
|
+
NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:server > /tmp/build-server.log 2>&1 &
|
|
108
|
+
SERVER_PID=$!
|
|
109
|
+
|
|
110
|
+
echo " ├─ 启动 client 构建..."
|
|
111
|
+
NODE_OPTIONS="$BUILD_NODE_OPTIONS" npm run build:client > /tmp/build-client.log 2>&1 &
|
|
112
|
+
CLIENT_PID=$!
|
|
113
|
+
|
|
114
|
+
# 等待两个构建完成
|
|
115
|
+
SERVER_EXIT=0
|
|
116
|
+
CLIENT_EXIT=0
|
|
117
|
+
|
|
118
|
+
wait $SERVER_PID || SERVER_EXIT=$?
|
|
119
|
+
wait $CLIENT_PID || CLIENT_EXIT=$?
|
|
120
|
+
|
|
121
|
+
# 检查构建结果
|
|
122
|
+
if [ $SERVER_EXIT -ne 0 ]; then
|
|
123
|
+
echo " ❌ Server 构建失败"
|
|
124
|
+
cat /tmp/build-server.log
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
if [ $CLIENT_EXIT -ne 0 ]; then
|
|
129
|
+
echo " ❌ Client 构建失败"
|
|
130
|
+
cat /tmp/build-client.log
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
echo " ✅ Server 构建完成"
|
|
135
|
+
echo " ✅ Client 构建完成"
|
|
89
136
|
fi
|
|
90
137
|
|
|
91
|
-
|
|
92
|
-
|
|
138
|
+
print_time $STEP_START
|
|
139
|
+
echo ""
|
|
140
|
+
|
|
141
|
+
# ==================== 步骤 5 ====================
|
|
142
|
+
echo "📦 [5/6] 准备产物"
|
|
143
|
+
STEP_START=$(node -e "console.log(Date.now())")
|
|
144
|
+
|
|
145
|
+
# 移动 client 下的 HTML 文件到 dist/dist/client,保证 views 路径在 dev/prod 下一致
|
|
146
|
+
# 使用 mv 而非 cp:HTML 不能上传到公网 CDN,移走后 dist/client 中不再包含 HTML
|
|
147
|
+
if [ -d "$DIST_DIR/client" ]; then
|
|
148
|
+
mkdir -p "$DIST_DIR/dist/client"
|
|
149
|
+
# public 先进(顶层 client 打包被排除;server setBaseViewsDir + 中间件同源 serve)
|
|
150
|
+
if [ -d "$ROOT_DIR/client/public" ]; then
|
|
151
|
+
cp -R "$ROOT_DIR/client/public/." "$DIST_DIR/dist/client/"
|
|
152
|
+
fi
|
|
153
|
+
# 构建产物 HTML 随后 move,覆盖 public 里的同名文件(保证入口页是构建版,不被 public 静默覆盖)
|
|
154
|
+
find "$DIST_DIR/client" -maxdepth 1 -name "*.html" -exec mv {} "$DIST_DIR/dist/client/" \;
|
|
155
|
+
fi
|
|
93
156
|
|
|
94
|
-
#
|
|
95
|
-
|
|
157
|
+
# server 相关产物准备(only_frontend_change=true 时跳过)
|
|
158
|
+
if [[ "${only_frontend_change:-false}" == "true" ]]; then
|
|
159
|
+
echo " [skip] 跳过 run.sh/.env 复制 (only_frontend_change=true)"
|
|
160
|
+
else
|
|
161
|
+
# 拷贝 run.sh 到 dist/(prod 从 dist/ 启动,确保 cwd 一致性)
|
|
162
|
+
cp "$ROOT_DIR/scripts/run.sh" "$DIST_DIR/"
|
|
163
|
+
|
|
164
|
+
# 拷贝 .env 文件(如果存在)
|
|
165
|
+
if [ -f "$ROOT_DIR/.env" ]; then
|
|
166
|
+
cp "$ROOT_DIR/.env" "$DIST_DIR/"
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
96
169
|
|
|
97
170
|
# 清理无用文件
|
|
98
|
-
rm -rf "$
|
|
99
|
-
rm -rf "$
|
|
171
|
+
rm -rf "$DIST_DIR/scripts"
|
|
172
|
+
rm -rf "$DIST_DIR/tsconfig.node.tsbuildinfo"
|
|
100
173
|
|
|
101
174
|
print_time $STEP_START
|
|
102
175
|
echo ""
|
|
103
176
|
|
|
104
|
-
# ==================== 步骤
|
|
105
|
-
echo "✂️ [
|
|
177
|
+
# ==================== 步骤 6 ====================
|
|
178
|
+
echo "✂️ [6/6] 智能依赖裁剪"
|
|
106
179
|
STEP_START=$(node -e "console.log(Date.now())")
|
|
107
180
|
|
|
108
|
-
#
|
|
109
|
-
|
|
181
|
+
# 智能依赖裁剪(仅全量构建时执行,纯前端场景不打包 server,无需裁剪)
|
|
182
|
+
if [[ "${only_frontend_change:-false}" == "true" ]]; then
|
|
183
|
+
echo "✂️ [6/6] 跳过智能依赖裁剪 (only_frontend_change=true)"
|
|
184
|
+
else
|
|
185
|
+
echo "✂️ [6/6] 智能依赖裁剪"
|
|
186
|
+
|
|
187
|
+
# 分析实际依赖、复制并裁剪 node_modules、生成精简的 package.json
|
|
188
|
+
node "$ROOT_DIR/scripts/prune-smart.js"
|
|
189
|
+
fi
|
|
110
190
|
|
|
111
191
|
print_time $STEP_START
|
|
112
192
|
echo ""
|
|
@@ -116,10 +196,17 @@ echo "构建完成"
|
|
|
116
196
|
print_time $TOTAL_START
|
|
117
197
|
|
|
118
198
|
# 输出产物信息
|
|
119
|
-
DIST_SIZE=$(du -sh "$
|
|
120
|
-
|
|
121
|
-
echo ""
|
|
122
|
-
echo "📊 构建产物统计:"
|
|
123
|
-
echo "
|
|
124
|
-
echo "
|
|
125
|
-
|
|
199
|
+
DIST_SIZE=$(du -sh "$DIST_DIR" | cut -f1)
|
|
200
|
+
if [[ "${only_frontend_change:-false}" == "true" ]]; then
|
|
201
|
+
echo ""
|
|
202
|
+
echo "📊 构建产物统计:"
|
|
203
|
+
echo " 产物大小: $DIST_SIZE"
|
|
204
|
+
echo ""
|
|
205
|
+
else
|
|
206
|
+
NODE_MODULES_SIZE=$(du -sh "$DIST_DIR/node_modules" | cut -f1)
|
|
207
|
+
echo ""
|
|
208
|
+
echo "📊 构建产物统计:"
|
|
209
|
+
echo " 产物大小: $DIST_SIZE"
|
|
210
|
+
echo " node_modules: $NODE_MODULES_SIZE"
|
|
211
|
+
echo ""
|
|
212
|
+
fi
|
|
@@ -6,15 +6,53 @@ const fs = require('fs');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
|
|
8
8
|
const ROOT_DIR = path.resolve(__dirname, '..');
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
// 加载提交的 .env,供读取构建期配置(如 MIAODA_RUNTIME_ENTRIES)。dotenv 不一定安装 → 静默降级。
|
|
11
|
+
try {
|
|
12
|
+
require('dotenv').config({ path: path.join(ROOT_DIR, '.env') });
|
|
13
|
+
} catch {
|
|
14
|
+
// dotenv 未安装:跳过;MIAODA_RUNTIME_ENTRIES 仍可来自真实环境变量
|
|
15
|
+
}
|
|
16
|
+
const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
|
17
|
+
const DIST_SERVER_DIR = path.join(DIST_DIR, 'server');
|
|
10
18
|
const ROOT_PACKAGE_JSON = path.join(ROOT_DIR, 'package.json');
|
|
11
19
|
const ROOT_NODE_MODULES = path.join(ROOT_DIR, 'node_modules');
|
|
12
|
-
const OUT_NODE_MODULES = path.join(
|
|
13
|
-
const OUT_PACKAGE_JSON = path.join(
|
|
20
|
+
const OUT_NODE_MODULES = path.join(DIST_DIR, 'node_modules');
|
|
21
|
+
const OUT_PACKAGE_JSON = path.join(DIST_DIR, 'package.json');
|
|
14
22
|
|
|
15
23
|
// Server 入口文件
|
|
16
24
|
const SERVER_ENTRY = path.join(DIST_SERVER_DIR, 'main.js');
|
|
17
25
|
|
|
26
|
+
// nest sourceRoot(源码根,默认 server):把 .env 里声明的源码路径映射到构建产物位置
|
|
27
|
+
let SOURCE_ROOT = 'server';
|
|
28
|
+
try {
|
|
29
|
+
const nestCli = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'nest-cli.json'), 'utf8'));
|
|
30
|
+
if (nestCli.sourceRoot) SOURCE_ROOT = nestCli.sourceRoot;
|
|
31
|
+
} catch {
|
|
32
|
+
// 读不到 nest-cli.json 则用默认 server
|
|
33
|
+
}
|
|
34
|
+
const SRC_TO_OUT_EXT = { '.mts': '.mjs', '.cts': '.cjs', '.ts': '.js' };
|
|
35
|
+
|
|
36
|
+
// 额外 trace 入口:经 new Function/动态 import 加载、nft 无法从 main.js 静态发现的 ESM runtime。
|
|
37
|
+
// 应用在提交的 .env 里用 MIAODA_RUNTIME_ENTRIES 声明【源码路径】(相对项目根,逗号分隔),例如:
|
|
38
|
+
// MIAODA_RUNTIME_ENTRIES=server/modules/ethics/runtime/review-run.mjs,server/modules/ethics/runtime/parse-core.mjs
|
|
39
|
+
// 脚本按 nest sourceRoot 自动映射到 dist/server 下的构建产物(并重写 .mts→.mjs / .ts→.js)。
|
|
40
|
+
const RUNTIME_ENTRIES = (process.env.MIAODA_RUNTIME_ENTRIES || '')
|
|
41
|
+
.split(',')
|
|
42
|
+
.map((s) => s.trim())
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.map((srcRel) => {
|
|
45
|
+
const fromSrc = path.relative(SOURCE_ROOT, srcRel); // 去掉 sourceRoot 前缀
|
|
46
|
+
const ext = path.extname(fromSrc);
|
|
47
|
+
const outRel = SRC_TO_OUT_EXT[ext] ? fromSrc.slice(0, -ext.length) + SRC_TO_OUT_EXT[ext] : fromSrc;
|
|
48
|
+
return path.join(DIST_SERVER_DIR, outRel);
|
|
49
|
+
})
|
|
50
|
+
.filter((p) => {
|
|
51
|
+
if (fs.existsSync(p)) return true;
|
|
52
|
+
console.warn(`⚠️ MIAODA_RUNTIME_ENTRIES 入口在构建产物中不存在,已忽略: ${path.relative(ROOT_DIR, p)}`);
|
|
53
|
+
return false;
|
|
54
|
+
});
|
|
55
|
+
|
|
18
56
|
// Node.js 内置模块列表
|
|
19
57
|
const BUILTIN_MODULES = new Set([
|
|
20
58
|
'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
|
|
@@ -144,12 +182,15 @@ async function smartPrune() {
|
|
|
144
182
|
}
|
|
145
183
|
|
|
146
184
|
console.log(`📂 分析入口文件: ${path.relative(ROOT_DIR, SERVER_ENTRY)}`);
|
|
185
|
+
if (RUNTIME_ENTRIES.length) {
|
|
186
|
+
console.log(`📎 额外 trace 入口 (${RUNTIME_ENTRIES.length}): ${RUNTIME_ENTRIES.map((p) => path.relative(DIST_SERVER_DIR, p)).join(', ')}`);
|
|
187
|
+
}
|
|
147
188
|
|
|
148
189
|
// 2. 使用 @vercel/nft 追踪依赖
|
|
149
190
|
console.log('🔎 追踪实际依赖...');
|
|
150
191
|
const analyzeStart = Date.now();
|
|
151
192
|
|
|
152
|
-
const { fileList } = await nodeFileTrace([SERVER_ENTRY], {
|
|
193
|
+
const { fileList } = await nodeFileTrace([SERVER_ENTRY, ...RUNTIME_ENTRIES], {
|
|
153
194
|
base: ROOT_DIR,
|
|
154
195
|
processCwd: ROOT_DIR,
|
|
155
196
|
ts: false, // 禁用 TS 解析
|
|
@@ -180,8 +221,59 @@ async function smartPrune() {
|
|
|
180
221
|
|
|
181
222
|
console.log(`📦 静态分析需要 ${requiredPackages.size} 个 npm 包`);
|
|
182
223
|
|
|
183
|
-
// 4.
|
|
224
|
+
// 4. 从 nft 结果递归收集子依赖(处理动态 require 无法被静态分析追踪的问题)
|
|
225
|
+
// 某些包(如 pdf-parse)使用 try-catch 动态加载依赖(如 @napi-rs/canvas),
|
|
226
|
+
// @vercel/nft 无法追踪这类依赖,需要基于 package.json 声明补充
|
|
227
|
+
// 策略:只对 nft 已追踪到的包递归,避免收集无关依赖
|
|
184
228
|
const originalPackage = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON, 'utf8'));
|
|
229
|
+
|
|
230
|
+
let addedSubDeps = 0;
|
|
231
|
+
const visited = new Set(); // 防止循环依赖导致无限循环
|
|
232
|
+
const queue = [...requiredPackages]; // BFS 队列,从 nft 结果开始(更精准)
|
|
233
|
+
|
|
234
|
+
while (queue.length > 0) {
|
|
235
|
+
const depName = queue.shift();
|
|
236
|
+
|
|
237
|
+
// 跳过已访问的包(防止循环依赖)
|
|
238
|
+
if (visited.has(depName)) continue;
|
|
239
|
+
visited.add(depName);
|
|
240
|
+
|
|
241
|
+
const depPkgPath = path.join(ROOT_NODE_MODULES, depName, 'package.json');
|
|
242
|
+
if (!fs.existsSync(depPkgPath)) continue;
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const depPkg = JSON.parse(fs.readFileSync(depPkgPath, 'utf8'));
|
|
246
|
+
const subDeps = {
|
|
247
|
+
...depPkg.dependencies,
|
|
248
|
+
...depPkg.optionalDependencies,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
for (const subDep of Object.keys(subDeps)) {
|
|
252
|
+
// 检查包是否存在于 node_modules 中
|
|
253
|
+
const subDepPath = path.join(ROOT_NODE_MODULES, subDep);
|
|
254
|
+
if (!fs.existsSync(subDepPath)) continue;
|
|
255
|
+
|
|
256
|
+
// 如果是新发现的包,添加到结果集并加入队列继续遍历
|
|
257
|
+
if (!requiredPackages.has(subDep)) {
|
|
258
|
+
requiredPackages.add(subDep);
|
|
259
|
+
addedSubDeps++;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 无论是否已在 requiredPackages 中,都需要继续遍历其子依赖
|
|
263
|
+
if (!visited.has(subDep)) {
|
|
264
|
+
queue.push(subDep);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
// 忽略解析错误
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (addedSubDeps > 0) {
|
|
273
|
+
console.log(`📦 递归补充动态依赖,新增 ${addedSubDeps} 个包`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 5. 处理 actionPlugins(动态加载的插件,无法被静态分析追踪)
|
|
185
277
|
const actionPlugins = originalPackage.actionPlugins || {};
|
|
186
278
|
const actionPluginNames = Object.keys(actionPlugins);
|
|
187
279
|
|
|
@@ -212,7 +304,7 @@ async function smartPrune() {
|
|
|
212
304
|
|
|
213
305
|
console.log(`📦 总共需要 ${requiredPackages.size} 个 npm 包\n`);
|
|
214
306
|
|
|
215
|
-
//
|
|
307
|
+
// 6. 选择性复制包(只复制需要的)
|
|
216
308
|
console.log('📋 选择性复制 node_modules(仅复制需要的包)...');
|
|
217
309
|
const copyStart = Date.now();
|
|
218
310
|
|
|
@@ -224,7 +316,7 @@ async function smartPrune() {
|
|
|
224
316
|
console.log(` 成功: ${copiedCount.success} 个包,失败: ${copiedCount.failed} 个`);
|
|
225
317
|
console.log(` 硬链接: ${fileStats.hardLinks} 个文件,复制: ${fileStats.copies} 个文件\n`);
|
|
226
318
|
|
|
227
|
-
//
|
|
319
|
+
// 7. 生成精简版 package.json
|
|
228
320
|
// 优化:直接构建 dependencies,避免多次对象展开
|
|
229
321
|
const prunedDependencies = {};
|
|
230
322
|
const allDeps = originalPackage.dependencies || {};
|
|
@@ -243,9 +335,6 @@ async function smartPrune() {
|
|
|
243
335
|
version: originalPackage.version,
|
|
244
336
|
private: true,
|
|
245
337
|
dependencies: prunedDependencies,
|
|
246
|
-
scripts: {
|
|
247
|
-
start: originalPackage.scripts?.start || 'node main.js'
|
|
248
|
-
},
|
|
249
338
|
engines: originalPackage.engines
|
|
250
339
|
};
|
|
251
340
|
|
|
@@ -253,7 +342,7 @@ async function smartPrune() {
|
|
|
253
342
|
|
|
254
343
|
const totalElapsed = Date.now() - totalStartTime;
|
|
255
344
|
|
|
256
|
-
//
|
|
345
|
+
// 8. 输出统计信息
|
|
257
346
|
console.log('='.repeat(60));
|
|
258
347
|
console.log('📊 智能裁剪统计:');
|
|
259
348
|
console.log('='.repeat(60));
|
package/template/scripts/run.sh
CHANGED
|
@@ -13,5 +13,6 @@
|
|
|
13
13
|
"excludeDirectories": ["node_modules/**"]
|
|
14
14
|
},
|
|
15
15
|
"include": ["server/**/*", "shared/**/*.ts"],
|
|
16
|
-
|
|
16
|
+
// MCP UI 是独立浏览器工程,不使用服务端的 TypeScript 配置。
|
|
17
|
+
"exclude": ["server/mcp/ui", "node_modules", "dist", "client", "source_package", "**/*.spec.ts", "**/*.e2e-spec.ts"],
|
|
17
18
|
}
|