@lark-apaas/miaoda-cli 0.1.6 → 0.1.7-alpha.eb0aa5c
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/cli/commands/app/index.js +67 -7
- package/dist/cli/commands/index.js +36 -1
- package/dist/cli/commands/skills/index.js +18 -2
- package/dist/cli/handlers/app/index.js +4 -1
- package/dist/cli/handlers/app/init.js +53 -9
- package/dist/cli/handlers/app/sync.js +220 -0
- package/dist/cli/handlers/skills/sync.js +15 -4
- package/dist/config/fullstack-cli-pin.js +13 -0
- package/dist/config/sync-configs/design-stack.js +98 -0
- package/dist/config/sync-configs/index.js +62 -0
- package/dist/config/sync-configs/nestjs-react-fullstack.js +177 -0
- package/dist/config/sync.js +14 -0
- package/dist/services/app/init/install.js +35 -13
- package/dist/services/app/init/template.js +23 -6
- package/dist/utils/coding-steering.js +107 -28
- package/dist/utils/file-ops.js +45 -0
- package/dist/utils/githooks.js +55 -0
- package/dist/utils/merge-json.js +63 -0
- package/dist/utils/platform-sync.js +160 -0
- package/dist/utils/sync-rule.js +295 -0
- package/package.json +5 -3
- package/upgrade/templates/README.md +34 -0
- package/upgrade/templates/design-stack/templates/.githooks/pre-commit +4 -0
- package/upgrade/templates/design-stack/templates/scripts/dev-local.js +83 -0
- package/upgrade/templates/design-stack/templates/scripts/dev.sh +25 -0
- package/upgrade/templates/design-stack/templates/scripts/hooks/run-precommit.js +37 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/.githooks/pre-commit +4 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/.gitignore.append +8 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/.spark_project +16 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/drizzle.config.ts +55 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/helper/gen-openapi.ts +34 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/nest-cli.json +25 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/build.sh +207 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev-local.js +111 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev.js +295 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/dev.sh +25 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/hooks/run-precommit.js +37 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/lint.js +150 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/prune-smart.js +330 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/scripts/run.sh +8 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/server/global.d.ts +19 -0
- package/upgrade/templates/nestjs-react-fullstack/templates/tsconfig.node.json +5 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// This file is auto-generated by @lark-apaas/fullstack-cli
|
|
3
|
+
|
|
4
|
+
const { nodeFileTrace } = require('@vercel/nft');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const ROOT_DIR = path.resolve(__dirname, '..');
|
|
9
|
+
const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
|
10
|
+
const DIST_SERVER_DIR = path.join(DIST_DIR, 'server');
|
|
11
|
+
const ROOT_PACKAGE_JSON = path.join(ROOT_DIR, 'package.json');
|
|
12
|
+
const ROOT_NODE_MODULES = path.join(ROOT_DIR, 'node_modules');
|
|
13
|
+
const OUT_NODE_MODULES = path.join(DIST_DIR, 'node_modules');
|
|
14
|
+
const OUT_PACKAGE_JSON = path.join(DIST_DIR, 'package.json');
|
|
15
|
+
|
|
16
|
+
// Server 入口文件
|
|
17
|
+
const SERVER_ENTRY = path.join(DIST_SERVER_DIR, 'main.js');
|
|
18
|
+
|
|
19
|
+
// Node.js 内置模块列表
|
|
20
|
+
const BUILTIN_MODULES = new Set([
|
|
21
|
+
'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
|
|
22
|
+
'events', 'fs', 'http', 'http2', 'https', 'net', 'os', 'path', 'stream',
|
|
23
|
+
'string_decoder', 'timers', 'tls', 'url', 'util', 'v8', 'vm', 'zlib',
|
|
24
|
+
'async_hooks', 'perf_hooks', 'worker_threads', 'inspector', 'trace_events'
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 从完整路径中提取 npm 包名(优化版)
|
|
29
|
+
*/
|
|
30
|
+
const nodeModulesSep = 'node_modules' + path.sep;
|
|
31
|
+
function extractPackageName(filePath) {
|
|
32
|
+
const idx = filePath.lastIndexOf(nodeModulesSep);
|
|
33
|
+
if (idx === -1) return null;
|
|
34
|
+
|
|
35
|
+
const afterNodeModules = filePath.slice(idx + nodeModulesSep.length);
|
|
36
|
+
const slashIdx = afterNodeModules.indexOf(path.sep);
|
|
37
|
+
|
|
38
|
+
// 处理 scoped package (@xxx/yyy)
|
|
39
|
+
if (afterNodeModules[0] === '@') {
|
|
40
|
+
const secondSlash = afterNodeModules.indexOf(path.sep, slashIdx + 1);
|
|
41
|
+
return secondSlash === -1
|
|
42
|
+
? afterNodeModules
|
|
43
|
+
: afterNodeModules.slice(0, secondSlash);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return slashIdx === -1 ? afterNodeModules : afterNodeModules.slice(0, slashIdx);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 递归删除目录
|
|
51
|
+
*/
|
|
52
|
+
function removeDir(dir) {
|
|
53
|
+
if (fs.existsSync(dir)) {
|
|
54
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 递归复制目录(优化版:优先使用硬链接)
|
|
60
|
+
*/
|
|
61
|
+
function copyDir(src, dest, stats = { hardLinks: 0, copies: 0 }) {
|
|
62
|
+
if (!fs.existsSync(dest)) {
|
|
63
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
67
|
+
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
const srcPath = path.join(src, entry.name);
|
|
70
|
+
const destPath = path.join(dest, entry.name);
|
|
71
|
+
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
copyDir(srcPath, destPath, stats);
|
|
74
|
+
} else {
|
|
75
|
+
try {
|
|
76
|
+
// 优先使用硬链接(CI 环境中安全,速度快,节省空间)
|
|
77
|
+
fs.linkSync(srcPath, destPath);
|
|
78
|
+
stats.hardLinks++;
|
|
79
|
+
} catch {
|
|
80
|
+
// 硬链接失败时回退到复制(如跨文件系统)
|
|
81
|
+
fs.copyFileSync(srcPath, destPath);
|
|
82
|
+
stats.copies++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return stats;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 选择性复制包
|
|
92
|
+
*/
|
|
93
|
+
function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
|
|
94
|
+
const copiedCount = { success: 0, failed: 0 };
|
|
95
|
+
const fileStats = { hardLinks: 0, copies: 0 };
|
|
96
|
+
|
|
97
|
+
// 确保输出目录存在
|
|
98
|
+
if (fs.existsSync(outNodeModules)) {
|
|
99
|
+
removeDir(outNodeModules);
|
|
100
|
+
}
|
|
101
|
+
fs.mkdirSync(outNodeModules, { recursive: true });
|
|
102
|
+
|
|
103
|
+
// 复制每个包
|
|
104
|
+
for (const pkg of packages) {
|
|
105
|
+
const srcPath = path.join(rootNodeModules, pkg);
|
|
106
|
+
const destPath = path.join(outNodeModules, pkg);
|
|
107
|
+
|
|
108
|
+
if (!fs.existsSync(srcPath)) {
|
|
109
|
+
copiedCount.failed++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// 确保父目录存在(处理 scoped packages)
|
|
115
|
+
const destDir = path.dirname(destPath);
|
|
116
|
+
if (!fs.existsSync(destDir)) {
|
|
117
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 复制包目录,收集统计信息
|
|
121
|
+
copyDir(srcPath, destPath, fileStats);
|
|
122
|
+
copiedCount.success++;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error(` ⚠️ 复制失败: ${pkg} - ${err.message}`);
|
|
125
|
+
copiedCount.failed++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { copiedCount, fileStats };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 主函数:智能裁剪
|
|
134
|
+
*/
|
|
135
|
+
async function smartPrune() {
|
|
136
|
+
console.log('🔍 开始智能依赖分析...\n');
|
|
137
|
+
|
|
138
|
+
const totalStartTime = Date.now();
|
|
139
|
+
|
|
140
|
+
// 1. 检查入口文件是否存在
|
|
141
|
+
if (!fs.existsSync(SERVER_ENTRY)) {
|
|
142
|
+
console.error(`❌ 入口文件不存在: ${SERVER_ENTRY}`);
|
|
143
|
+
console.error(' 请先运行 npm run build:server');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(`📂 分析入口文件: ${path.relative(ROOT_DIR, SERVER_ENTRY)}`);
|
|
148
|
+
|
|
149
|
+
// 2. 使用 @vercel/nft 追踪依赖
|
|
150
|
+
console.log('🔎 追踪实际依赖...');
|
|
151
|
+
const analyzeStart = Date.now();
|
|
152
|
+
|
|
153
|
+
const { fileList } = await nodeFileTrace([SERVER_ENTRY], {
|
|
154
|
+
base: ROOT_DIR,
|
|
155
|
+
processCwd: ROOT_DIR,
|
|
156
|
+
ts: false, // 禁用 TS 解析
|
|
157
|
+
conditions: ['node', 'production'], // 只解析 Node.js 生产环境的导出
|
|
158
|
+
exportsOnly: true, // 只使用 package.json 的 exports 字段
|
|
159
|
+
analysis: {
|
|
160
|
+
emitGlobs: false, // 禁用 glob 分析
|
|
161
|
+
computeFileReferences: false, // 禁用文件引用计算
|
|
162
|
+
evaluatePureExpressions: false, // 禁用纯表达式求值
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const analyzeElapsed = Date.now() - analyzeStart;
|
|
167
|
+
console.log(` ✅ 分析完成,耗时: ${analyzeElapsed}ms\n`);
|
|
168
|
+
|
|
169
|
+
// 3. 提取 npm 包名(优化:减少字符串操作)
|
|
170
|
+
const requiredPackages = new Set();
|
|
171
|
+
|
|
172
|
+
for (const file of fileList) {
|
|
173
|
+
// 快速跳过非 node_modules 文件
|
|
174
|
+
if (!file.includes('node_modules')) continue;
|
|
175
|
+
|
|
176
|
+
const pkgName = extractPackageName(file);
|
|
177
|
+
if (pkgName && !BUILTIN_MODULES.has(pkgName)) {
|
|
178
|
+
requiredPackages.add(pkgName);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log(`📦 静态分析需要 ${requiredPackages.size} 个 npm 包`);
|
|
183
|
+
|
|
184
|
+
// 4. 从 nft 结果递归收集子依赖(处理动态 require 无法被静态分析追踪的问题)
|
|
185
|
+
// 某些包(如 pdf-parse)使用 try-catch 动态加载依赖(如 @napi-rs/canvas),
|
|
186
|
+
// @vercel/nft 无法追踪这类依赖,需要基于 package.json 声明补充
|
|
187
|
+
// 策略:只对 nft 已追踪到的包递归,避免收集无关依赖
|
|
188
|
+
const originalPackage = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON, 'utf8'));
|
|
189
|
+
|
|
190
|
+
let addedSubDeps = 0;
|
|
191
|
+
const visited = new Set(); // 防止循环依赖导致无限循环
|
|
192
|
+
const queue = [...requiredPackages]; // BFS 队列,从 nft 结果开始(更精准)
|
|
193
|
+
|
|
194
|
+
while (queue.length > 0) {
|
|
195
|
+
const depName = queue.shift();
|
|
196
|
+
|
|
197
|
+
// 跳过已访问的包(防止循环依赖)
|
|
198
|
+
if (visited.has(depName)) continue;
|
|
199
|
+
visited.add(depName);
|
|
200
|
+
|
|
201
|
+
const depPkgPath = path.join(ROOT_NODE_MODULES, depName, 'package.json');
|
|
202
|
+
if (!fs.existsSync(depPkgPath)) continue;
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const depPkg = JSON.parse(fs.readFileSync(depPkgPath, 'utf8'));
|
|
206
|
+
const subDeps = {
|
|
207
|
+
...depPkg.dependencies,
|
|
208
|
+
...depPkg.optionalDependencies,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
for (const subDep of Object.keys(subDeps)) {
|
|
212
|
+
// 检查包是否存在于 node_modules 中
|
|
213
|
+
const subDepPath = path.join(ROOT_NODE_MODULES, subDep);
|
|
214
|
+
if (!fs.existsSync(subDepPath)) continue;
|
|
215
|
+
|
|
216
|
+
// 如果是新发现的包,添加到结果集并加入队列继续遍历
|
|
217
|
+
if (!requiredPackages.has(subDep)) {
|
|
218
|
+
requiredPackages.add(subDep);
|
|
219
|
+
addedSubDeps++;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 无论是否已在 requiredPackages 中,都需要继续遍历其子依赖
|
|
223
|
+
if (!visited.has(subDep)) {
|
|
224
|
+
queue.push(subDep);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
// 忽略解析错误
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (addedSubDeps > 0) {
|
|
233
|
+
console.log(`📦 递归补充动态依赖,新增 ${addedSubDeps} 个包`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 5. 处理 actionPlugins(动态加载的插件,无法被静态分析追踪)
|
|
237
|
+
const actionPlugins = originalPackage.actionPlugins || {};
|
|
238
|
+
const actionPluginNames = Object.keys(actionPlugins);
|
|
239
|
+
|
|
240
|
+
if (actionPluginNames.length > 0) {
|
|
241
|
+
console.log(`🔌 发现 ${actionPluginNames.length} 个 Action 插件,添加到依赖列表...`);
|
|
242
|
+
|
|
243
|
+
for (const pluginName of actionPluginNames) {
|
|
244
|
+
requiredPackages.add(pluginName);
|
|
245
|
+
|
|
246
|
+
// 检查插件的 peerDependencies,也添加进去
|
|
247
|
+
const pluginPkgPath = path.join(ROOT_NODE_MODULES, pluginName, 'package.json');
|
|
248
|
+
if (fs.existsSync(pluginPkgPath)) {
|
|
249
|
+
try {
|
|
250
|
+
const pluginPkg = JSON.parse(fs.readFileSync(pluginPkgPath, 'utf8'));
|
|
251
|
+
if (pluginPkg.peerDependencies) {
|
|
252
|
+
for (const peerDep of Object.keys(pluginPkg.peerDependencies)) {
|
|
253
|
+
requiredPackages.add(peerDep);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
// 忽略解析错误
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log(` ✅ 插件: ${actionPluginNames.join(', ')}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
console.log(`📦 总共需要 ${requiredPackages.size} 个 npm 包\n`);
|
|
266
|
+
|
|
267
|
+
// 6. 选择性复制包(只复制需要的)
|
|
268
|
+
console.log('📋 选择性复制 node_modules(仅复制需要的包)...');
|
|
269
|
+
const copyStart = Date.now();
|
|
270
|
+
|
|
271
|
+
const sortedPackages = Array.from(requiredPackages).sort();
|
|
272
|
+
const { copiedCount, fileStats } = copyPackagesSelectively(sortedPackages, ROOT_NODE_MODULES, OUT_NODE_MODULES);
|
|
273
|
+
|
|
274
|
+
const copyElapsed = Date.now() - copyStart;
|
|
275
|
+
console.log(` ✅ 复制完成,耗时: ${copyElapsed}ms`);
|
|
276
|
+
console.log(` 成功: ${copiedCount.success} 个包,失败: ${copiedCount.failed} 个`);
|
|
277
|
+
console.log(` 硬链接: ${fileStats.hardLinks} 个文件,复制: ${fileStats.copies} 个文件\n`);
|
|
278
|
+
|
|
279
|
+
// 7. 生成精简版 package.json
|
|
280
|
+
// 优化:直接构建 dependencies,避免多次对象展开
|
|
281
|
+
const prunedDependencies = {};
|
|
282
|
+
const allDeps = originalPackage.dependencies || {};
|
|
283
|
+
const allDevDeps = originalPackage.devDependencies || {};
|
|
284
|
+
|
|
285
|
+
for (const pkg of requiredPackages) {
|
|
286
|
+
// 优先从 dependencies/devDependencies 获取版本,其次从 actionPlugins 获取
|
|
287
|
+
const version = allDeps[pkg] || allDevDeps[pkg] || actionPlugins[pkg];
|
|
288
|
+
if (version) {
|
|
289
|
+
prunedDependencies[pkg] = version;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const prunedPackage = {
|
|
294
|
+
name: originalPackage.name,
|
|
295
|
+
version: originalPackage.version,
|
|
296
|
+
private: true,
|
|
297
|
+
dependencies: prunedDependencies,
|
|
298
|
+
engines: originalPackage.engines
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
fs.writeFileSync(OUT_PACKAGE_JSON, JSON.stringify(prunedPackage, null, 2));
|
|
302
|
+
|
|
303
|
+
const totalElapsed = Date.now() - totalStartTime;
|
|
304
|
+
|
|
305
|
+
// 8. 输出统计信息
|
|
306
|
+
console.log('='.repeat(60));
|
|
307
|
+
console.log('📊 智能裁剪统计:');
|
|
308
|
+
console.log('='.repeat(60));
|
|
309
|
+
console.log(` 需要的包数量: ${requiredPackages.size}`);
|
|
310
|
+
if (actionPluginNames.length > 0) {
|
|
311
|
+
console.log(` Action 插件: ${actionPluginNames.length} 个`);
|
|
312
|
+
}
|
|
313
|
+
console.log(` 成功复制: ${copiedCount.success} 个包`);
|
|
314
|
+
console.log(` 失败: ${copiedCount.failed} 个包`);
|
|
315
|
+
console.log(` 硬链接文件: ${fileStats.hardLinks} 个`);
|
|
316
|
+
console.log(` 复制文件: ${fileStats.copies} 个`);
|
|
317
|
+
console.log(` 分析耗时: ${analyzeElapsed}ms`);
|
|
318
|
+
console.log(` 复制耗时: ${copyElapsed}ms`);
|
|
319
|
+
console.log(` 总耗时: ${totalElapsed}ms`);
|
|
320
|
+
console.log('='.repeat(60) + '\n');
|
|
321
|
+
|
|
322
|
+
console.log('✅ 智能裁剪完成!');
|
|
323
|
+
console.log(`📍 精简的 package.json 已保存到: ${OUT_PACKAGE_JSON}\n`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 运行智能裁剪
|
|
327
|
+
smartPrune().catch(err => {
|
|
328
|
+
console.error('❌ 智能裁剪失败:', err);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# This file is auto-generated by @lark-apaas/fullstack-cli
|
|
3
|
+
|
|
4
|
+
# 生产环境下,启动服务
|
|
5
|
+
# 从 dist/ 根目录执行,确保 process.cwd() 与 dev 模式一致
|
|
6
|
+
# dev: cwd = 项目根, shared/ client/ 可达
|
|
7
|
+
# prod: cwd = dist/, shared/ client/ 可达
|
|
8
|
+
NODE_ENV=production node server/main.js
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// automatically generated by fullstack-nestjs-core, do not edit manually
|
|
2
|
+
declare namespace Express {
|
|
3
|
+
interface Request {
|
|
4
|
+
// don't depend on this field, it's used by client to get platform info
|
|
5
|
+
__platform_data__?: {
|
|
6
|
+
userId?: string;
|
|
7
|
+
tenantId?: string;
|
|
8
|
+
env?: string;
|
|
9
|
+
csrfToken?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
userContext: {
|
|
13
|
+
userId?: string;
|
|
14
|
+
tenantId?: number;
|
|
15
|
+
appId?: string;
|
|
16
|
+
},
|
|
17
|
+
csrfToken?: string;
|
|
18
|
+
}
|
|
19
|
+
}
|