@lark-apaas/fullstack-cli 1.0.1-alpha.0 → 1.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "1.0.1-alpha.0",
3
+ "version": "1.0.1",
4
4
  "description": "CLI tool for fullstack template management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,11 +18,6 @@ print_time() {
18
18
  echo " ⏱️ 耗时: ${seconds}.$(printf "%03d" $ms)s"
19
19
  }
20
20
 
21
- echo "╔════════════════════════════════════════════════════════╗"
22
- echo "║ 开始构建 - 智能裁剪版本 ║"
23
- echo "╚════════════════════════════════════════════════════════╝"
24
- echo ""
25
-
26
21
  # ==================== 步骤 0 ====================
27
22
  echo "📝 [0/4] 更新 openapi 代码"
28
23
  STEP_START=$(node -e "console.log(Date.now())")
@@ -105,9 +100,7 @@ print_time $STEP_START
105
100
  echo ""
106
101
 
107
102
  # 总耗时
108
- echo "╔════════════════════════════════════════════════════════╗"
109
- echo "║ 构建完成 ║"
110
- echo "╚════════════════════════════════════════════════════════╝"
103
+ echo "构建完成"
111
104
  print_time $TOTAL_START
112
105
 
113
106
  # 输出产物信息
@@ -1,14 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // This file is auto-generated by @lark-apaas/fullstack-cli, do not modify it manually.
3
-
4
- /**
5
- * 智能依赖裁剪脚本 - 高性能版本
6
- *
7
- * 策略:
8
- * 1. 分析 dist/server 下所有文件的实际依赖
9
- * 2. **选择性复制**:只复制需要的包,而不是全部复制再删除
10
- * 3. 生成精简的 package.json 到 dist/server
11
- */
2
+ // This file is auto-generated by @lark-apaas/fullstack-cli
12
3
 
13
4
  const { nodeFileTrace } = require('@vercel/nft');
14
5
  const fs = require('fs');
@@ -21,6 +12,9 @@ const ROOT_NODE_MODULES = path.join(ROOT_DIR, 'node_modules');
21
12
  const OUT_NODE_MODULES = path.join(DIST_SERVER_DIR, 'node_modules');
22
13
  const OUT_PACKAGE_JSON = path.join(DIST_SERVER_DIR, 'package.json');
23
14
 
15
+ // Server 入口文件
16
+ const SERVER_ENTRY = path.join(DIST_SERVER_DIR, 'main.js');
17
+
24
18
  // Node.js 内置模块列表
25
19
  const BUILTIN_MODULES = new Set([
26
20
  'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
@@ -30,50 +24,25 @@ const BUILTIN_MODULES = new Set([
30
24
  ]);
31
25
 
32
26
  /**
33
- * 递归获取目录下所有 .js 文件
34
- */
35
- function getJsFiles(dir) {
36
- const files = [];
37
-
38
- if (!fs.existsSync(dir)) {
39
- console.error(`❌ 目录不存在: ${dir}`);
40
- process.exit(1);
41
- }
42
-
43
- function traverse(currentDir) {
44
- const items = fs.readdirSync(currentDir);
45
- for (const item of items) {
46
- const fullPath = path.join(currentDir, item);
47
- const stat = fs.statSync(fullPath);
48
-
49
- if (stat.isDirectory() && item !== 'node_modules') {
50
- traverse(fullPath);
51
- } else if (stat.isFile() && item.endsWith('.js')) {
52
- files.push(fullPath);
53
- }
54
- }
55
- }
56
-
57
- traverse(dir);
58
- return files;
59
- }
60
-
61
- /**
62
- * 从完整路径中提取 npm 包名
27
+ * 从完整路径中提取 npm 包名(优化版)
63
28
  */
29
+ const nodeModulesSep = 'node_modules' + path.sep;
64
30
  function extractPackageName(filePath) {
65
- const parts = filePath.split('node_modules' + path.sep);
66
- if (parts.length < 2) return null;
31
+ const idx = filePath.lastIndexOf(nodeModulesSep);
32
+ if (idx === -1) return null;
67
33
 
68
- const afterNodeModules = parts[parts.length - 1];
69
- const segments = afterNodeModules.split(path.sep);
34
+ const afterNodeModules = filePath.slice(idx + nodeModulesSep.length);
35
+ const slashIdx = afterNodeModules.indexOf(path.sep);
70
36
 
71
37
  // 处理 scoped package (@xxx/yyy)
72
- if (segments[0].startsWith('@')) {
73
- return segments[0] + '/' + segments[1];
38
+ if (afterNodeModules[0] === '@') {
39
+ const secondSlash = afterNodeModules.indexOf(path.sep, slashIdx + 1);
40
+ return secondSlash === -1
41
+ ? afterNodeModules
42
+ : afterNodeModules.slice(0, secondSlash);
74
43
  }
75
44
 
76
- return segments[0];
45
+ return slashIdx === -1 ? afterNodeModules : afterNodeModules.slice(0, slashIdx);
77
46
  }
78
47
 
79
48
  /**
@@ -86,9 +55,9 @@ function removeDir(dir) {
86
55
  }
87
56
 
88
57
  /**
89
- * 递归复制目录
58
+ * 递归复制目录(优化版:优先使用硬链接)
90
59
  */
91
- function copyDir(src, dest) {
60
+ function copyDir(src, dest, stats = { hardLinks: 0, copies: 0 }) {
92
61
  if (!fs.existsSync(dest)) {
93
62
  fs.mkdirSync(dest, { recursive: true });
94
63
  }
@@ -100,11 +69,21 @@ function copyDir(src, dest) {
100
69
  const destPath = path.join(dest, entry.name);
101
70
 
102
71
  if (entry.isDirectory()) {
103
- copyDir(srcPath, destPath);
72
+ copyDir(srcPath, destPath, stats);
104
73
  } else {
105
- fs.copyFileSync(srcPath, destPath);
74
+ try {
75
+ // 优先使用硬链接(CI 环境中安全,速度快,节省空间)
76
+ fs.linkSync(srcPath, destPath);
77
+ stats.hardLinks++;
78
+ } catch {
79
+ // 硬链接失败时回退到复制(如跨文件系统)
80
+ fs.copyFileSync(srcPath, destPath);
81
+ stats.copies++;
82
+ }
106
83
  }
107
84
  }
85
+
86
+ return stats;
108
87
  }
109
88
 
110
89
  /**
@@ -112,6 +91,7 @@ function copyDir(src, dest) {
112
91
  */
113
92
  function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
114
93
  const copiedCount = { success: 0, failed: 0 };
94
+ const fileStats = { hardLinks: 0, copies: 0 };
115
95
 
116
96
  // 确保输出目录存在
117
97
  if (fs.existsSync(outNodeModules)) {
@@ -119,8 +99,6 @@ function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
119
99
  }
120
100
  fs.mkdirSync(outNodeModules, { recursive: true });
121
101
 
122
- // 注意:不复制 .bin 目录,运行时不需要
123
-
124
102
  // 复制每个包
125
103
  for (const pkg of packages) {
126
104
  const srcPath = path.join(rootNodeModules, pkg);
@@ -138,8 +116,8 @@ function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
138
116
  fs.mkdirSync(destDir, { recursive: true });
139
117
  }
140
118
 
141
- // 复制包目录
142
- copyDir(srcPath, destPath);
119
+ // 复制包目录,收集统计信息
120
+ copyDir(srcPath, destPath, fileStats);
143
121
  copiedCount.success++;
144
122
  } catch (err) {
145
123
  console.error(` ⚠️ 复制失败: ${pkg} - ${err.message}`);
@@ -147,7 +125,7 @@ function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
147
125
  }
148
126
  }
149
127
 
150
- return copiedCount;
128
+ return { copiedCount, fileStats };
151
129
  }
152
130
 
153
131
  /**
@@ -158,50 +136,45 @@ async function smartPrune() {
158
136
 
159
137
  const totalStartTime = Date.now();
160
138
 
161
- // 1. 获取所有 server 构建产物
162
- const jsFiles = getJsFiles(DIST_SERVER_DIR);
163
- console.log(`📂 找到 ${jsFiles.length} 个 JS 文件`);
164
-
165
- if (jsFiles.length === 0) {
166
- console.error('❌ 没有找到任何 JS 文件');
139
+ // 1. 检查入口文件是否存在
140
+ if (!fs.existsSync(SERVER_ENTRY)) {
141
+ console.error(`❌ 入口文件不存在: ${SERVER_ENTRY}`);
142
+ console.error(' 请先运行 npm run build:server');
167
143
  process.exit(1);
168
144
  }
169
145
 
146
+ console.log(`📂 分析入口文件: ${path.relative(ROOT_DIR, SERVER_ENTRY)}`);
147
+
170
148
  // 2. 使用 @vercel/nft 追踪依赖
171
149
  console.log('🔎 追踪实际依赖...');
172
150
  const analyzeStart = Date.now();
173
151
 
174
- const { fileList } = await nodeFileTrace(jsFiles, {
152
+ const { fileList } = await nodeFileTrace([SERVER_ENTRY], {
175
153
  base: ROOT_DIR,
176
154
  processCwd: ROOT_DIR,
177
- // 性能优化选项
178
- cache: {}, // 启用缓存,避免重复分析相同文件
179
- ignore: [
180
- // 忽略明显不需要的文件类型
181
- '**/*.md',
182
- '**/*.ts',
183
- '**/*.d.ts',
184
- '**/test/**',
185
- '**/tests/**',
186
- '**/__tests__/**',
187
- '**/examples/**',
188
- '**/docs/**',
189
- '**/.git/**',
190
- ],
155
+ ts: false, // 禁用 TS 解析
156
+ conditions: ['node', 'production'], // 只解析 Node.js 生产环境的导出
157
+ exportsOnly: true, // 只使用 package.json 的 exports 字段
158
+ analysis: {
159
+ emitGlobs: false, // 禁用 glob 分析
160
+ computeFileReferences: false, // 禁用文件引用计算
161
+ evaluatePureExpressions: false, // 禁用纯表达式求值
162
+ },
191
163
  });
192
164
 
193
165
  const analyzeElapsed = Date.now() - analyzeStart;
194
166
  console.log(` ✅ 分析完成,耗时: ${analyzeElapsed}ms\n`);
195
167
 
196
- // 3. 提取 npm 包名
168
+ // 3. 提取 npm 包名(优化:减少字符串操作)
197
169
  const requiredPackages = new Set();
198
170
 
199
171
  for (const file of fileList) {
200
- if (file.includes('node_modules')) {
201
- const pkgName = extractPackageName(file);
202
- if (pkgName && !BUILTIN_MODULES.has(pkgName)) {
203
- requiredPackages.add(pkgName);
204
- }
172
+ // 快速跳过非 node_modules 文件
173
+ if (!file.includes('node_modules')) continue;
174
+
175
+ const pkgName = extractPackageName(file);
176
+ if (pkgName && !BUILTIN_MODULES.has(pkgName)) {
177
+ requiredPackages.add(pkgName);
205
178
  }
206
179
  }
207
180
 
@@ -212,23 +185,25 @@ async function smartPrune() {
212
185
  const copyStart = Date.now();
213
186
 
214
187
  const sortedPackages = Array.from(requiredPackages).sort();
215
- const copiedCount = copyPackagesSelectively(sortedPackages, ROOT_NODE_MODULES, OUT_NODE_MODULES);
188
+ const { copiedCount, fileStats } = copyPackagesSelectively(sortedPackages, ROOT_NODE_MODULES, OUT_NODE_MODULES);
216
189
 
217
190
  const copyElapsed = Date.now() - copyStart;
218
191
  console.log(` ✅ 复制完成,耗时: ${copyElapsed}ms`);
219
- console.log(` 成功: ${copiedCount.success} 个,失败: ${copiedCount.failed} 个\n`);
192
+ console.log(` 成功: ${copiedCount.success} 个包,失败: ${copiedCount.failed} 个`);
193
+ console.log(` 硬链接: ${fileStats.hardLinks} 个文件,复制: ${fileStats.copies} 个文件\n`);
220
194
 
221
195
  // 5. 读取原始 package.json 并生成精简版本
222
196
  const originalPackage = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON, 'utf8'));
223
- const allDeps = {
224
- ...originalPackage.dependencies || {},
225
- ...originalPackage.devDependencies || {}
226
- };
227
197
 
198
+ // 优化:直接构建 dependencies,避免多次对象展开
228
199
  const prunedDependencies = {};
200
+ const allDeps = originalPackage.dependencies || {};
201
+ const allDevDeps = originalPackage.devDependencies || {};
202
+
229
203
  for (const pkg of requiredPackages) {
230
- if (allDeps[pkg]) {
231
- prunedDependencies[pkg] = allDeps[pkg];
204
+ const version = allDeps[pkg] || allDevDeps[pkg];
205
+ if (version) {
206
+ prunedDependencies[pkg] = version;
232
207
  }
233
208
  }
234
209
 
@@ -252,8 +227,10 @@ async function smartPrune() {
252
227
  console.log('📊 智能裁剪统计:');
253
228
  console.log('='.repeat(60));
254
229
  console.log(` 需要的包数量: ${requiredPackages.size}`);
255
- console.log(` 成功复制: ${copiedCount.success} 个`);
256
- console.log(` 失败: ${copiedCount.failed} 个`);
230
+ console.log(` 成功复制: ${copiedCount.success} 个包`);
231
+ console.log(` 失败: ${copiedCount.failed} 个包`);
232
+ console.log(` 硬链接文件: ${fileStats.hardLinks} 个`);
233
+ console.log(` 复制文件: ${fileStats.copies} 个`);
257
234
  console.log(` 分析耗时: ${analyzeElapsed}ms`);
258
235
  console.log(` 复制耗时: ${copyElapsed}ms`);
259
236
  console.log(` 总耗时: ${totalElapsed}ms`);