@lark-apaas/fullstack-cli 0.1.0-alpha.9 → 1.0.1-alpha.0

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/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Lark Technologies Pte. Ltd. and/or its affiliates
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
8
+ IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
9
+ INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
10
+ EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
11
+ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
12
+ DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
13
+ ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/bin/cli.js ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'node:url';
4
+ import path from 'node:path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ // 直接执行编译后的 index.js
10
+ const indexPath = path.join(__dirname, '../dist/index.js');
11
+ await import(indexPath);
@@ -1,10 +1,19 @@
1
1
  /**
2
2
  * 生成数据库 schema
3
3
  *
4
+ * 命令行选项:
5
+ * - --output <path>: schema 输出路径,默认 'server/database/schema.ts'
6
+ * - --schema-filter <schemas>: schema 过滤器,逗号分隔
7
+ * - --tables-filter <tables>: 表过滤器,逗号分隔,默认 '*'
8
+ *
4
9
  * 环境变量配置:
5
10
  * - SUDA_DATABASE_URL: 数据库连接 URL(必需)
6
- * - DB_SCHEMA_OUTPUT: schema 输出路径,默认 'server/database/schema.ts'
7
- * - DRIZZLE_SCHEMA_FILTER: schema 过滤器,逗号分隔
8
- * - DRIZZLE_TABLES_FILTER: 表过滤器,逗号分隔,默认 '*'
11
+ * - DB_SCHEMA_OUTPUT: schema 输出路径(命令行选项优先)
12
+ * - DRIZZLE_SCHEMA_FILTER: schema 过滤器(命令行选项优先)
13
+ * - DRIZZLE_TABLES_FILTER: 表过滤器(命令行选项优先)
9
14
  */
10
- export declare function run(): Promise<void>;
15
+ export declare function run(options?: {
16
+ output?: string;
17
+ schemaFilter?: string;
18
+ tablesFilter?: string;
19
+ }): Promise<void>;
@@ -10,13 +10,18 @@ const require = createRequire(import.meta.url);
10
10
  /**
11
11
  * 生成数据库 schema
12
12
  *
13
+ * 命令行选项:
14
+ * - --output <path>: schema 输出路径,默认 'server/database/schema.ts'
15
+ * - --schema-filter <schemas>: schema 过滤器,逗号分隔
16
+ * - --tables-filter <tables>: 表过滤器,逗号分隔,默认 '*'
17
+ *
13
18
  * 环境变量配置:
14
19
  * - SUDA_DATABASE_URL: 数据库连接 URL(必需)
15
- * - DB_SCHEMA_OUTPUT: schema 输出路径,默认 'server/database/schema.ts'
16
- * - DRIZZLE_SCHEMA_FILTER: schema 过滤器,逗号分隔
17
- * - DRIZZLE_TABLES_FILTER: 表过滤器,逗号分隔,默认 '*'
20
+ * - DB_SCHEMA_OUTPUT: schema 输出路径(命令行选项优先)
21
+ * - DRIZZLE_SCHEMA_FILTER: schema 过滤器(命令行选项优先)
22
+ * - DRIZZLE_TABLES_FILTER: 表过滤器(命令行选项优先)
18
23
  */
19
- export async function run() {
24
+ export async function run(options = {}) {
20
25
  // 加载用户项目的 .env 文件
21
26
  const envPath = path.resolve(process.cwd(), '.env');
22
27
  if (fs.existsSync(envPath)) {
@@ -29,7 +34,8 @@ export async function run() {
29
34
  console.error('[gen-db-schema] Please set it in .env file or pass it as environment variable');
30
35
  process.exit(1);
31
36
  }
32
- const outputPath = process.env.DB_SCHEMA_OUTPUT || 'server/database/schema.ts';
37
+ // 命令行选项优先于环境变量
38
+ const outputPath = options.output || process.env.DB_SCHEMA_OUTPUT || 'server/database/schema.ts';
33
39
  const OUT_DIR = path.resolve(process.cwd(), 'server/database/.introspect');
34
40
  const SCHEMA_FILE = path.resolve(process.cwd(), outputPath);
35
41
  console.log('[gen-db-schema] Starting...');
@@ -38,8 +44,8 @@ export async function run() {
38
44
  const __filename = fileURLToPath(import.meta.url);
39
45
  const __dirname = path.dirname(__filename);
40
46
  // 使用 CLI 内部的 drizzle 配置
41
- const cliRoot = path.resolve(__dirname, '../..');
42
- const configPath = path.join(cliRoot, 'templates', 'drizzle.config.ts');
47
+ const cliRoot = path.resolve(__dirname, '../');
48
+ const configPath = path.join(cliRoot, 'config', 'drizzle.config.js');
43
49
  if (!fs.existsSync(configPath)) {
44
50
  console.error('[gen-db-schema] Error: drizzle config not found in CLI package');
45
51
  process.exit(1);
@@ -0,0 +1,4 @@
1
+ /**
2
+ * 同步模板文件到用户项目
3
+ */
4
+ export declare function run(): Promise<void>;
@@ -0,0 +1,162 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { syncConfig } from '../config/sync.js';
5
+ /**
6
+ * 同步模板文件到用户项目
7
+ */
8
+ export async function run() {
9
+ // 检测是否在用户项目中(通过 INIT_CWD 判断)
10
+ const userProjectRoot = process.env.INIT_CWD || process.cwd();
11
+ // 获取插件根目录
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const pluginRoot = path.resolve(__dirname, '../..');
15
+ // 只有在插件自己的开发目录中才跳过(避免在开发插件时触发 sync)
16
+ if (userProjectRoot === pluginRoot) {
17
+ console.log('[fullstack-cli] Skip syncing (installing plugin itself)');
18
+ process.exit(0);
19
+ }
20
+ // 检查是否存在 package.json,确保是有效的项目
21
+ const userPackageJson = path.join(userProjectRoot, 'package.json');
22
+ if (!fs.existsSync(userPackageJson)) {
23
+ console.log('[fullstack-cli] Skip syncing (not a valid npm project)');
24
+ process.exit(0);
25
+ }
26
+ try {
27
+ console.log('[fullstack-cli] Starting sync...');
28
+ // 使用配置
29
+ const config = syncConfig;
30
+ if (!config || !config.sync) {
31
+ console.warn('[fullstack-cli] No sync configuration found');
32
+ process.exit(0);
33
+ }
34
+ // 执行同步规则
35
+ for (const rule of config.sync) {
36
+ await syncRule(rule, pluginRoot, userProjectRoot);
37
+ }
38
+ // 设置权限
39
+ if (config.permissions) {
40
+ setPermissions(config.permissions, userProjectRoot);
41
+ }
42
+ console.log('[fullstack-cli] Sync completed successfully ✅');
43
+ }
44
+ catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ console.error('[fullstack-cli] Failed to sync:', message);
47
+ process.exit(1);
48
+ }
49
+ }
50
+ /**
51
+ * 执行单个同步规则
52
+ */
53
+ async function syncRule(rule, pluginRoot, userProjectRoot) {
54
+ const srcPath = path.join(pluginRoot, rule.from);
55
+ const destPath = path.join(userProjectRoot, rule.to);
56
+ if (!fs.existsSync(srcPath)) {
57
+ console.warn(`[fullstack-cli] Source not found: ${rule.from}`);
58
+ return;
59
+ }
60
+ switch (rule.type) {
61
+ case 'directory':
62
+ syncDirectory(srcPath, destPath, rule.overwrite ?? true);
63
+ break;
64
+ case 'file':
65
+ syncFile(srcPath, destPath, rule.overwrite ?? true);
66
+ break;
67
+ case 'append':
68
+ appendToFile(srcPath, destPath);
69
+ break;
70
+ default:
71
+ console.warn(`[fullstack-cli] Unknown sync type: ${rule.type}`);
72
+ }
73
+ }
74
+ /**
75
+ * 同步单个文件
76
+ */
77
+ function syncFile(src, dest, overwrite = true) {
78
+ // 确保目标目录存在
79
+ const destDir = path.dirname(dest);
80
+ if (!fs.existsSync(destDir)) {
81
+ fs.mkdirSync(destDir, { recursive: true });
82
+ }
83
+ // 检查目标文件是否存在
84
+ if (fs.existsSync(dest) && !overwrite) {
85
+ console.log(`[fullstack-cli] ○ ${path.basename(dest)} (skipped, already exists)`);
86
+ return;
87
+ }
88
+ // 复制文件
89
+ fs.copyFileSync(src, dest);
90
+ console.log(`[fullstack-cli] ✓ ${path.basename(dest)}`);
91
+ }
92
+ /**
93
+ * 同步整个目录
94
+ */
95
+ function syncDirectory(src, dest, overwrite = true) {
96
+ // 确保目标目录存在
97
+ if (!fs.existsSync(dest)) {
98
+ fs.mkdirSync(dest, { recursive: true });
99
+ }
100
+ // 读取源目录所有文件
101
+ const files = fs.readdirSync(src);
102
+ let count = 0;
103
+ files.forEach(file => {
104
+ const srcFile = path.join(src, file);
105
+ const destFile = path.join(dest, file);
106
+ const stats = fs.statSync(srcFile);
107
+ if (stats.isDirectory()) {
108
+ // 递归处理子目录
109
+ syncDirectory(srcFile, destFile, overwrite);
110
+ }
111
+ else {
112
+ // 复制文件
113
+ if (overwrite || !fs.existsSync(destFile)) {
114
+ fs.copyFileSync(srcFile, destFile);
115
+ console.log(`[fullstack-cli] ✓ ${path.relative(dest, destFile)}`);
116
+ count++;
117
+ }
118
+ }
119
+ });
120
+ if (count > 0) {
121
+ console.log(`[fullstack-cli] Synced ${count} files to ${path.basename(dest)}/`);
122
+ }
123
+ }
124
+ /**
125
+ * 追加内容到文件
126
+ */
127
+ function appendToFile(src, dest) {
128
+ const content = fs.readFileSync(src, 'utf-8');
129
+ // 读取目标文件内容(如果存在)
130
+ let existingContent = '';
131
+ if (fs.existsSync(dest)) {
132
+ existingContent = fs.readFileSync(dest, 'utf-8');
133
+ }
134
+ // 检查是否已包含相同内容(避免重复追加)
135
+ if (existingContent.includes(content.trim())) {
136
+ console.log(`[fullstack-cli] ○ ${path.basename(dest)} (already contains content)`);
137
+ return;
138
+ }
139
+ // 追加内容
140
+ fs.appendFileSync(dest, content);
141
+ console.log(`[fullstack-cli] ✓ ${path.basename(dest)} (appended)`);
142
+ }
143
+ /**
144
+ * 设置文件权限
145
+ */
146
+ function setPermissions(permissions, projectRoot) {
147
+ for (const [pattern, mode] of Object.entries(permissions)) {
148
+ // 简单实现:只支持 **/*.sh 这种模式
149
+ if (pattern === '**/*.sh') {
150
+ const scriptsDir = path.join(projectRoot, 'scripts');
151
+ if (fs.existsSync(scriptsDir)) {
152
+ const files = fs.readdirSync(scriptsDir);
153
+ files.forEach(file => {
154
+ if (file.endsWith('.sh')) {
155
+ const filePath = path.join(scriptsDir, file);
156
+ fs.chmodSync(filePath, mode);
157
+ }
158
+ });
159
+ }
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import("drizzle-kit").Config;
2
+ export default _default;
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'drizzle-kit';
2
+ const outputDir = process.env.__DRIZZLE_OUT_DIR__ || './server/database/.introspect';
3
+ const schemaPath = process.env.__DRIZZLE_SCHEMA_PATH__ || './server/database/schema.ts';
4
+ const parsedUrl = new URL(process.env.SUDA_DATABASE_URL || '');
5
+ const schemaFilter = parsedUrl.searchParams.get('schema')?.split(',') ?? [];
6
+ export default defineConfig({
7
+ schema: schemaPath,
8
+ out: outputDir,
9
+ tablesFilter: ['*'],
10
+ schemaFilter,
11
+ dialect: 'postgresql',
12
+ dbCredentials: {
13
+ host: parsedUrl.hostname,
14
+ port: Number(parsedUrl.port) || 5432,
15
+ user: decodeURIComponent(parsedUrl.username),
16
+ password: decodeURIComponent(parsedUrl.password),
17
+ database: parsedUrl.pathname.split('/')[1],
18
+ },
19
+ });
@@ -12,11 +12,10 @@ export interface SyncRule {
12
12
  /** 是否覆盖已存在的文件(仅 directory 和 file 类型有效) */
13
13
  overwrite?: boolean;
14
14
  }
15
- export interface PostinstallConfig {
15
+ export interface SyncConfig {
16
16
  /** 派生规则 */
17
17
  sync: SyncRule[];
18
18
  /** 文件权限设置 */
19
19
  permissions?: Record<string, number>;
20
20
  }
21
- declare const config: PostinstallConfig;
22
- export default config;
21
+ export declare const syncConfig: SyncConfig;
@@ -2,7 +2,7 @@
2
2
  * fullstack-cli 派生配置
3
3
  * 定义哪些文件/目录需要同步到用户项目
4
4
  */
5
- const config = {
5
+ export const syncConfig = {
6
6
  // 派生规则
7
7
  sync: [
8
8
  // 1. 派生 scripts 目录(总是覆盖)
@@ -12,23 +12,22 @@ const config = {
12
12
  type: 'directory',
13
13
  overwrite: true,
14
14
  },
15
- // 2. 追加内容到 .gitignore
16
- {
17
- from: 'templates/.gitignore.append',
18
- to: '.gitignore',
19
- type: 'append',
20
- },
15
+ // // 2. 追加内容到 .gitignore
16
+ // {
17
+ // from: 'templates/.gitignore.append',
18
+ // to: '.gitignore',
19
+ // type: 'append',
20
+ // },
21
21
  // 3. 派生 server/type.ts 文件(总是覆盖)
22
- {
23
- from: 'templates/server/type.ts',
24
- to: 'server/type.ts',
25
- type: 'file',
26
- },
22
+ // {
23
+ // from: 'templates/server/global.d.ts',
24
+ // to: 'server/types/global.d.ts',
25
+ // type: 'file',
26
+ // },
27
27
  ],
28
28
  // 文件权限设置
29
29
  permissions: {
30
- // 所有 .sh 文件设置为可执行
31
- '**/*.sh': 0o755,
30
+ // 所有 .sh 文件设置为可执行
31
+ // '**/*.sh': 0o755,
32
32
  },
33
33
  };
34
- export default config;
package/dist/index.d.ts CHANGED
@@ -1,10 +1 @@
1
- /**
2
- * @lark-apaas/fullstack-cli
3
- *
4
- * CLI tool for fullstack template management
5
- */
6
- export declare const version = "1.0.0";
7
- declare const _default: {
8
- version: string;
9
- };
10
- export default _default;
1
+ export {};
package/dist/index.js CHANGED
@@ -1,9 +1,52 @@
1
- /**
2
- * @lark-apaas/fullstack-cli
3
- *
4
- * CLI tool for fullstack template management
5
- */
6
- export const version = '1.0.0';
7
- export default {
8
- version,
9
- };
1
+ import { fileURLToPath } from 'node:url';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import cac from 'cac';
5
+ import { run as runGenDbSchema } from './commands/gen-db-schema.js';
6
+ import { run as runSync } from './commands/sync.js';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ // 读取 package.json
10
+ const pkgPath = path.join(__dirname, '../package.json');
11
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
12
+ const cli = cac('fullstack-cli');
13
+ // 设置全局描述和版本
14
+ cli
15
+ .version(pkg.version)
16
+ .help();
17
+ // gen-db-schema 命令
18
+ cli
19
+ .command('gen-db-schema', 'Generate database schema from existing database')
20
+ .option('--output <path>', 'Output path for schema', {
21
+ default: 'server/database/schema.ts',
22
+ })
23
+ .option('--schema-filter <schemas>', 'Schema filter, comma-separated')
24
+ .option('--tables-filter <tables>', 'Tables filter, comma-separated')
25
+ .example('fullstack-cli gen-db-schema')
26
+ .example('fullstack-cli gen-db-schema --output custom/schema.ts')
27
+ .action(async (options) => {
28
+ try {
29
+ await runGenDbSchema(options);
30
+ }
31
+ catch (error) {
32
+ console.error(`Failed to execute command:`, error.message);
33
+ console.error(error.stack);
34
+ process.exit(1);
35
+ }
36
+ });
37
+ // sync 命令
38
+ cli
39
+ .command('sync', 'Sync template files (scripts, configs) to user project')
40
+ .example('fullstack-cli sync')
41
+ .action(async () => {
42
+ try {
43
+ await runSync();
44
+ }
45
+ catch (error) {
46
+ console.error(`Failed to execute command:`, error.message);
47
+ console.error(error.stack);
48
+ process.exit(1);
49
+ }
50
+ });
51
+ // 解析命令行参数
52
+ cli.parse();
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-cli",
3
- "version": "0.1.0-alpha.9",
3
+ "version": "1.0.1-alpha.0",
4
4
  "description": "CLI tool for fullstack template management",
5
+ "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
- "bin": "./bin/cli.mjs",
8
+ "bin": "./bin/cli.js",
8
9
  "files": [
9
10
  "dist",
10
11
  "templates",
@@ -12,8 +13,7 @@
12
13
  ],
13
14
  "scripts": {
14
15
  "build": "tsc",
15
- "prepublishOnly": "npm run build",
16
- "postinstall": "node bin/sync-scripts.js"
16
+ "prepublishOnly": "npm run build"
17
17
  },
18
18
  "keywords": [
19
19
  "fullstack",
@@ -30,6 +30,8 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@lark-apaas/devtool-kits": "^1.0.0",
33
+ "@vercel/nft": "^0.30.3",
34
+ "cac": "^6.7.14",
33
35
  "dotenv": "^16.0.0",
34
36
  "drizzle-kit": "0.31.5"
35
37
  },
@@ -1,37 +1,89 @@
1
1
  #!/usr/bin/env bash
2
+ # This file is auto-generated by @lark-apaas/fullstack-cli
2
3
  set -euo pipefail
3
4
 
4
5
  ROOT_DIR="$(pwd)"
5
6
  OUT_DIR="$ROOT_DIR/dist/server"
6
7
 
7
- echo "===================="
8
- echo "0) 更新 openapi 代码"
9
- echo "===================="
8
+ # 记录总开始时间
9
+ TOTAL_START=$(node -e "console.log(Date.now())")
10
+
11
+ # 打印耗时的辅助函数
12
+ print_time() {
13
+ local start=$1
14
+ local end=$(node -e "console.log(Date.now())")
15
+ local elapsed=$((end - start))
16
+ local seconds=$((elapsed / 1000))
17
+ local ms=$((elapsed % 1000))
18
+ echo " ⏱️ 耗时: ${seconds}.$(printf "%03d" $ms)s"
19
+ }
20
+
21
+ echo "╔════════════════════════════════════════════════════════╗"
22
+ echo "║ 开始构建 - 智能裁剪版本 ║"
23
+ echo "╚════════════════════════════════════════════════════════╝"
24
+ echo ""
25
+
26
+ # ==================== 步骤 0 ====================
27
+ echo "📝 [0/4] 更新 openapi 代码"
28
+ STEP_START=$(node -e "console.log(Date.now())")
10
29
  npm run gen:openapi
30
+ print_time $STEP_START
31
+ echo ""
11
32
 
12
- echo "===================="
13
- echo "1) 清理 dist 目录"
14
- echo "===================="
33
+ # ==================== 步骤 1 ====================
34
+ echo "🗑️ [1/4] 清理 dist 目录"
35
+ STEP_START=$(node -e "console.log(Date.now())")
15
36
  rm -rf "$ROOT_DIR/dist"
37
+ print_time $STEP_START
38
+ echo ""
39
+
40
+ # ==================== 步骤 2 ====================
41
+ echo "🔨 [2/4] 并行构建 server 和 client"
42
+ STEP_START=$(node -e "console.log(Date.now())")
43
+
44
+ # 并行构建
45
+ echo " ├─ 启动 server 构建..."
46
+ npm run build:server > /tmp/build-server.log 2>&1 &
47
+ SERVER_PID=$!
48
+
49
+ echo " ├─ 启动 client 构建..."
50
+ npm run build:client > /tmp/build-client.log 2>&1 &
51
+ CLIENT_PID=$!
52
+
53
+ # 等待两个构建完成
54
+ SERVER_EXIT=0
55
+ CLIENT_EXIT=0
56
+
57
+ wait $SERVER_PID || SERVER_EXIT=$?
58
+ wait $CLIENT_PID || CLIENT_EXIT=$?
16
59
 
17
- echo "===================="
18
- echo "2) 构建项目"
19
- echo "===================="
20
- npm run build:prod
21
- echo "✅ Build completed"
60
+ # 检查构建结果
61
+ if [ $SERVER_EXIT -ne 0 ]; then
62
+ echo " ❌ Server 构建失败"
63
+ cat /tmp/build-server.log
64
+ exit 1
65
+ fi
66
+
67
+ if [ $CLIENT_EXIT -ne 0 ]; then
68
+ echo " ❌ Client 构建失败"
69
+ cat /tmp/build-client.log
70
+ exit 1
71
+ fi
72
+
73
+ echo " ✅ Server 构建完成"
74
+ echo " ✅ Client 构建完成"
75
+ print_time $STEP_START
76
+ echo ""
77
+
78
+ # ==================== 步骤 3 ====================
79
+ echo "📦 [3/4] 准备 server 依赖产物"
80
+ STEP_START=$(node -e "console.log(Date.now())")
22
81
 
23
- echo "===================="
24
- echo "3) 准备 server 产物目录"
25
- echo "===================="
26
82
  mkdir -p "$OUT_DIR/dist/client"
27
83
 
28
84
  # 拷贝 HTML
29
85
  cp "$ROOT_DIR/dist/client/"*.html "$OUT_DIR/dist/client/" || true
30
86
 
31
- # 拷贝 package.json
32
- cp "$ROOT_DIR/package.json" "$OUT_DIR/"
33
- [ -f "$ROOT_DIR/pnpm-lock.yaml" ] && cp "$ROOT_DIR/pnpm-lock.yaml" "$OUT_DIR/"
34
-
35
87
  # 拷贝 run.sh 文件
36
88
  cp "$ROOT_DIR/scripts/run.sh" "$OUT_DIR/"
37
89
 
@@ -39,11 +91,30 @@ cp "$ROOT_DIR/scripts/run.sh" "$OUT_DIR/"
39
91
  rm -rf "$ROOT_DIR/dist/scripts"
40
92
  rm -rf "$ROOT_DIR/dist/tsconfig.node.tsbuildinfo"
41
93
 
42
- echo "===================="
43
- echo "4) 复制 node_modules(仅生产依赖)"
44
- echo "===================="
94
+ print_time $STEP_START
95
+ echo ""
96
+
97
+ # ==================== 步骤 4 ====================
98
+ echo "✂️ [4/4] 智能依赖裁剪"
99
+ STEP_START=$(node -e "console.log(Date.now())")
100
+
101
+ # 分析实际依赖、复制并裁剪 node_modules、生成精简的 package.json
102
+ node "$ROOT_DIR/scripts/prune-smart.js"
103
+
104
+ print_time $STEP_START
105
+ echo ""
45
106
 
46
- npm prune --omit=dev
47
- cp -R "$ROOT_DIR/node_modules" "$OUT_DIR/"
107
+ # 总耗时
108
+ echo "╔════════════════════════════════════════════════════════╗"
109
+ echo "║ 构建完成 ║"
110
+ echo "╚════════════════════════════════════════════════════════╝"
111
+ print_time $TOTAL_START
48
112
 
49
- echo "✅ node_modules ready"
113
+ # 输出产物信息
114
+ DIST_SIZE=$(du -sh "$OUT_DIR" | cut -f1)
115
+ NODE_MODULES_SIZE=$(du -sh "$OUT_DIR/node_modules" | cut -f1)
116
+ echo ""
117
+ echo "📊 构建产物统计:"
118
+ echo " 总大小: $DIST_SIZE"
119
+ echo " node_modules: $NODE_MODULES_SIZE"
120
+ echo ""
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env bash
2
+ # This file is auto-generated by @lark-apaas/fullstack-cli
3
+
2
4
  set -euo pipefail
3
5
 
4
6
  # Ensure the script always runs from the project root
@@ -1,5 +1,6 @@
1
+ // This file is auto-generated by @lark-apaas/fullstack-cli
1
2
  import { NestFactory } from '@nestjs/core';
2
- import { DevToolsModule } from '@lark-apaas/fullstack-nestjs-core';
3
+ import { DevToolsV2Module } from '@lark-apaas/fullstack-nestjs-core';
3
4
 
4
5
  import { AppModule } from '../server/app.module';
5
6
 
@@ -17,7 +18,7 @@ async function generateOpenApi() {
17
18
  app.setGlobalPrefix(basePath);
18
19
  }
19
20
 
20
- await DevToolsModule.mount(app, {
21
+ await DevToolsV2Module.mount(app, {
21
22
  basePath,
22
23
  docsPath: '/api_docs',
23
24
  needSetupServer: false,
@@ -0,0 +1,270 @@
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
+ */
12
+
13
+ const { nodeFileTrace } = require('@vercel/nft');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ const ROOT_DIR = path.resolve(__dirname, '..');
18
+ const DIST_SERVER_DIR = path.join(ROOT_DIR, 'dist/server');
19
+ const ROOT_PACKAGE_JSON = path.join(ROOT_DIR, 'package.json');
20
+ const ROOT_NODE_MODULES = path.join(ROOT_DIR, 'node_modules');
21
+ const OUT_NODE_MODULES = path.join(DIST_SERVER_DIR, 'node_modules');
22
+ const OUT_PACKAGE_JSON = path.join(DIST_SERVER_DIR, 'package.json');
23
+
24
+ // Node.js 内置模块列表
25
+ const BUILTIN_MODULES = new Set([
26
+ 'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns',
27
+ 'events', 'fs', 'http', 'http2', 'https', 'net', 'os', 'path', 'stream',
28
+ 'string_decoder', 'timers', 'tls', 'url', 'util', 'v8', 'vm', 'zlib',
29
+ 'async_hooks', 'perf_hooks', 'worker_threads', 'inspector', 'trace_events'
30
+ ]);
31
+
32
+ /**
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 包名
63
+ */
64
+ function extractPackageName(filePath) {
65
+ const parts = filePath.split('node_modules' + path.sep);
66
+ if (parts.length < 2) return null;
67
+
68
+ const afterNodeModules = parts[parts.length - 1];
69
+ const segments = afterNodeModules.split(path.sep);
70
+
71
+ // 处理 scoped package (@xxx/yyy)
72
+ if (segments[0].startsWith('@')) {
73
+ return segments[0] + '/' + segments[1];
74
+ }
75
+
76
+ return segments[0];
77
+ }
78
+
79
+ /**
80
+ * 递归删除目录
81
+ */
82
+ function removeDir(dir) {
83
+ if (fs.existsSync(dir)) {
84
+ fs.rmSync(dir, { recursive: true, force: true });
85
+ }
86
+ }
87
+
88
+ /**
89
+ * 递归复制目录
90
+ */
91
+ function copyDir(src, dest) {
92
+ if (!fs.existsSync(dest)) {
93
+ fs.mkdirSync(dest, { recursive: true });
94
+ }
95
+
96
+ const entries = fs.readdirSync(src, { withFileTypes: true });
97
+
98
+ for (const entry of entries) {
99
+ const srcPath = path.join(src, entry.name);
100
+ const destPath = path.join(dest, entry.name);
101
+
102
+ if (entry.isDirectory()) {
103
+ copyDir(srcPath, destPath);
104
+ } else {
105
+ fs.copyFileSync(srcPath, destPath);
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * 选择性复制包
112
+ */
113
+ function copyPackagesSelectively(packages, rootNodeModules, outNodeModules) {
114
+ const copiedCount = { success: 0, failed: 0 };
115
+
116
+ // 确保输出目录存在
117
+ if (fs.existsSync(outNodeModules)) {
118
+ removeDir(outNodeModules);
119
+ }
120
+ fs.mkdirSync(outNodeModules, { recursive: true });
121
+
122
+ // 注意:不复制 .bin 目录,运行时不需要
123
+
124
+ // 复制每个包
125
+ for (const pkg of packages) {
126
+ const srcPath = path.join(rootNodeModules, pkg);
127
+ const destPath = path.join(outNodeModules, pkg);
128
+
129
+ if (!fs.existsSync(srcPath)) {
130
+ copiedCount.failed++;
131
+ continue;
132
+ }
133
+
134
+ try {
135
+ // 确保父目录存在(处理 scoped packages)
136
+ const destDir = path.dirname(destPath);
137
+ if (!fs.existsSync(destDir)) {
138
+ fs.mkdirSync(destDir, { recursive: true });
139
+ }
140
+
141
+ // 复制包目录
142
+ copyDir(srcPath, destPath);
143
+ copiedCount.success++;
144
+ } catch (err) {
145
+ console.error(` ⚠️ 复制失败: ${pkg} - ${err.message}`);
146
+ copiedCount.failed++;
147
+ }
148
+ }
149
+
150
+ return copiedCount;
151
+ }
152
+
153
+ /**
154
+ * 主函数:智能裁剪
155
+ */
156
+ async function smartPrune() {
157
+ console.log('🔍 开始智能依赖分析...\n');
158
+
159
+ const totalStartTime = Date.now();
160
+
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 文件');
167
+ process.exit(1);
168
+ }
169
+
170
+ // 2. 使用 @vercel/nft 追踪依赖
171
+ console.log('🔎 追踪实际依赖...');
172
+ const analyzeStart = Date.now();
173
+
174
+ const { fileList } = await nodeFileTrace(jsFiles, {
175
+ base: ROOT_DIR,
176
+ 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
+ ],
191
+ });
192
+
193
+ const analyzeElapsed = Date.now() - analyzeStart;
194
+ console.log(` ✅ 分析完成,耗时: ${analyzeElapsed}ms\n`);
195
+
196
+ // 3. 提取 npm 包名
197
+ const requiredPackages = new Set();
198
+
199
+ 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
+ }
205
+ }
206
+ }
207
+
208
+ console.log(`📦 实际需要 ${requiredPackages.size} 个 npm 包\n`);
209
+
210
+ // 4. 选择性复制包(只复制需要的)
211
+ console.log('📋 选择性复制 node_modules(仅复制需要的包)...');
212
+ const copyStart = Date.now();
213
+
214
+ const sortedPackages = Array.from(requiredPackages).sort();
215
+ const copiedCount = copyPackagesSelectively(sortedPackages, ROOT_NODE_MODULES, OUT_NODE_MODULES);
216
+
217
+ const copyElapsed = Date.now() - copyStart;
218
+ console.log(` ✅ 复制完成,耗时: ${copyElapsed}ms`);
219
+ console.log(` 成功: ${copiedCount.success} 个,失败: ${copiedCount.failed} 个\n`);
220
+
221
+ // 5. 读取原始 package.json 并生成精简版本
222
+ const originalPackage = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON, 'utf8'));
223
+ const allDeps = {
224
+ ...originalPackage.dependencies || {},
225
+ ...originalPackage.devDependencies || {}
226
+ };
227
+
228
+ const prunedDependencies = {};
229
+ for (const pkg of requiredPackages) {
230
+ if (allDeps[pkg]) {
231
+ prunedDependencies[pkg] = allDeps[pkg];
232
+ }
233
+ }
234
+
235
+ const prunedPackage = {
236
+ name: originalPackage.name,
237
+ version: originalPackage.version,
238
+ private: true,
239
+ dependencies: prunedDependencies,
240
+ scripts: {
241
+ start: originalPackage.scripts?.start || 'node main.js'
242
+ },
243
+ engines: originalPackage.engines
244
+ };
245
+
246
+ fs.writeFileSync(OUT_PACKAGE_JSON, JSON.stringify(prunedPackage, null, 2));
247
+
248
+ const totalElapsed = Date.now() - totalStartTime;
249
+
250
+ // 6. 输出统计信息
251
+ console.log('='.repeat(60));
252
+ console.log('📊 智能裁剪统计:');
253
+ console.log('='.repeat(60));
254
+ console.log(` 需要的包数量: ${requiredPackages.size}`);
255
+ console.log(` 成功复制: ${copiedCount.success} 个`);
256
+ console.log(` 失败: ${copiedCount.failed} 个`);
257
+ console.log(` 分析耗时: ${analyzeElapsed}ms`);
258
+ console.log(` 复制耗时: ${copyElapsed}ms`);
259
+ console.log(` 总耗时: ${totalElapsed}ms`);
260
+ console.log('='.repeat(60) + '\n');
261
+
262
+ console.log('✅ 智能裁剪完成!');
263
+ console.log(`📍 精简的 package.json 已保存到: ${OUT_PACKAGE_JSON}\n`);
264
+ }
265
+
266
+ // 运行智能裁剪
267
+ smartPrune().catch(err => {
268
+ console.error('❌ 智能裁剪失败:', err);
269
+ process.exit(1);
270
+ });
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+ # This file is auto-generated by @lark-apaas/fullstack-cli
2
3
 
3
4
  # 生产环境下,启动服务
4
5
  npm run start
@@ -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
+ }
package/bin/cli.mjs DELETED
@@ -1,84 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { fileURLToPath } from 'node:url';
4
- import path from 'node:path';
5
- import { createRequire } from 'node:module';
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
- const require = createRequire(import.meta.url);
10
-
11
- const command = process.argv[2];
12
- const distPath = path.join(__dirname, '..', 'dist');
13
-
14
- async function main() {
15
- try {
16
- switch (command) {
17
- case 'gen-db-schema': {
18
- const modulePath = path.join(distPath, 'commands', 'gen-db-schema.js');
19
- const { run } = await import(modulePath);
20
- await run();
21
- break;
22
- }
23
- case 'sync':
24
- case 'postinstall': {
25
- // 调用原有的 sync-scripts.js (CommonJS)
26
- require('./sync-scripts.js');
27
- break;
28
- }
29
- case '--version':
30
- case '-v': {
31
- const pkg = require('../package.json');
32
- console.log(pkg.version);
33
- break;
34
- }
35
- case '--help':
36
- case '-h':
37
- case undefined: {
38
- showHelp();
39
- break;
40
- }
41
- default: {
42
- console.error(`Unknown command: ${command}`);
43
- console.error('');
44
- showHelp();
45
- process.exit(1);
46
- }
47
- }
48
- } catch (error) {
49
- console.error(`Failed to execute command "${command}":`, error.message);
50
- console.error(error.stack);
51
- process.exit(1);
52
- }
53
- }
54
-
55
- function showHelp() {
56
- console.log(`
57
- Usage: fullstack-cli <command> [options]
58
-
59
- Commands:
60
- gen-db-schema Generate database schema from existing database
61
- sync Sync template files to user project (internal use)
62
-
63
- Options:
64
- --version, -v Show version number
65
- --help, -h Show help
66
-
67
- Environment Variables:
68
- # gen-db-schema
69
- SUDA_DATABASE_URL Database connection URL (required)
70
- DB_SCHEMA_OUTPUT Output path for schema (default: server/database/schema.ts)
71
- DRIZZLE_SCHEMA_FILTER Schema filter, comma-separated (optional)
72
- DRIZZLE_TABLES_FILTER Tables filter (default: *)
73
-
74
- Examples:
75
- $ fullstack-cli gen-db-schema
76
- $ DB_SCHEMA_OUTPUT=custom/schema.ts fullstack-cli gen-db-schema
77
-
78
- Scripts (auto-generated):
79
- scripts/gen-openapi.ts - Generate OpenAPI documentation (use: npm run gen:openapi)
80
- scripts/gen-db-schema.ts - Generate database schema (deprecated, use CLI command)
81
- `);
82
- }
83
-
84
- main();
@@ -1,183 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const path = require('path');
4
- const fs = require('fs');
5
-
6
- // 主函数
7
- async function main() {
8
- // 检测是否在用户项目中(通过 INIT_CWD 判断)
9
- const userProjectRoot = process.env.INIT_CWD || process.cwd();
10
- const pluginRoot = path.resolve(__dirname, '..');
11
-
12
- // 只有在插件自己的开发目录中才跳过(避免在开发插件时触发 sync)
13
- if (userProjectRoot === pluginRoot) {
14
- console.log('[fullstack-cli] Skip syncing (installing plugin itself)');
15
- process.exit(0);
16
- }
17
-
18
- // 检查是否存在 package.json,确保是有效的项目
19
- const userPackageJson = path.join(userProjectRoot, 'package.json');
20
- if (!fs.existsSync(userPackageJson)) {
21
- console.log('[fullstack-cli] Skip syncing (not a valid npm project)');
22
- process.exit(0);
23
- }
24
-
25
- try {
26
- console.log('[fullstack-cli] Starting sync...');
27
-
28
- // 加载配置文件(从 dist 目录加载编译后的配置)
29
- const { default: config } = await import('../dist/postinstall.config.js');
30
-
31
- if (!config || !config.sync) {
32
- console.warn('[fullstack-cli] No sync configuration found');
33
- process.exit(0);
34
- }
35
-
36
- // 执行同步规则
37
- for (const rule of config.sync) {
38
- await syncRule(rule, pluginRoot, userProjectRoot);
39
- }
40
-
41
- // 设置权限
42
- if (config.permissions) {
43
- setPermissions(config.permissions, userProjectRoot);
44
- }
45
-
46
- console.log('[fullstack-cli] Sync completed successfully ✅');
47
- } catch (error) {
48
- console.error('[fullstack-cli] Failed to sync:', error.message);
49
- // 不阻塞安装过程
50
- process.exit(0);
51
- }
52
- }
53
-
54
- /**
55
- * 执行单个同步规则
56
- */
57
- async function syncRule(rule, pluginRoot, userProjectRoot) {
58
- const srcPath = path.join(pluginRoot, rule.from);
59
- const destPath = path.join(userProjectRoot, rule.to);
60
-
61
- if (!fs.existsSync(srcPath)) {
62
- console.warn(`[fullstack-cli] Source not found: ${rule.from}`);
63
- return;
64
- }
65
-
66
- switch (rule.type) {
67
- case 'directory':
68
- syncDirectory(srcPath, destPath, rule.overwrite);
69
- break;
70
- case 'file':
71
- syncFile(srcPath, destPath, rule.overwrite);
72
- break;
73
- case 'append':
74
- appendToFile(srcPath, destPath);
75
- break;
76
- default:
77
- console.warn(`[fullstack-cli] Unknown sync type: ${rule.type}`);
78
- }
79
- }
80
-
81
- /**
82
- * 同步单个文件
83
- */
84
- function syncFile(src, dest, overwrite = true) {
85
- // 确保目标目录存在
86
- const destDir = path.dirname(dest);
87
- if (!fs.existsSync(destDir)) {
88
- fs.mkdirSync(destDir, { recursive: true });
89
- }
90
-
91
- // 检查目标文件是否存在
92
- if (fs.existsSync(dest) && !overwrite) {
93
- console.log(`[fullstack-cli] ○ ${path.basename(dest)} (skipped, already exists)`);
94
- return;
95
- }
96
-
97
- // 复制文件
98
- fs.copyFileSync(src, dest);
99
- console.log(`[fullstack-cli] ✓ ${path.basename(dest)}`);
100
- }
101
-
102
- /**
103
- * 同步整个目录
104
- */
105
- function syncDirectory(src, dest, overwrite = true) {
106
- // 确保目标目录存在
107
- if (!fs.existsSync(dest)) {
108
- fs.mkdirSync(dest, { recursive: true });
109
- }
110
-
111
- // 读取源目录所有文件
112
- const files = fs.readdirSync(src);
113
- let count = 0;
114
-
115
- files.forEach(file => {
116
- const srcFile = path.join(src, file);
117
- const destFile = path.join(dest, file);
118
- const stats = fs.statSync(srcFile);
119
-
120
- if (stats.isDirectory()) {
121
- // 递归处理子目录
122
- syncDirectory(srcFile, destFile, overwrite);
123
- } else {
124
- // 复制文件
125
- if (overwrite || !fs.existsSync(destFile)) {
126
- fs.copyFileSync(srcFile, destFile);
127
- console.log(`[fullstack-cli] ✓ ${path.relative(dest, destFile)}`);
128
- count++;
129
- }
130
- }
131
- });
132
-
133
- if (count > 0) {
134
- console.log(`[fullstack-cli] Synced ${count} files to ${path.basename(dest)}/`);
135
- }
136
- }
137
-
138
- /**
139
- * 追加内容到文件
140
- */
141
- function appendToFile(src, dest) {
142
- const content = fs.readFileSync(src, 'utf-8');
143
-
144
- // 读取目标文件内容(如果存在)
145
- let existingContent = '';
146
- if (fs.existsSync(dest)) {
147
- existingContent = fs.readFileSync(dest, 'utf-8');
148
- }
149
-
150
- // 检查是否已包含相同内容(避免重复追加)
151
- if (existingContent.includes(content.trim())) {
152
- console.log(`[fullstack-cli] ○ ${path.basename(dest)} (already contains content)`);
153
- return;
154
- }
155
-
156
- // 追加内容
157
- fs.appendFileSync(dest, content);
158
- console.log(`[fullstack-cli] ✓ ${path.basename(dest)} (appended)`);
159
- }
160
-
161
- /**
162
- * 设置文件权限
163
- */
164
- function setPermissions(permissions, projectRoot) {
165
- for (const [pattern, mode] of Object.entries(permissions)) {
166
- // 简单实现:只支持 **/*.sh 这种模式
167
- if (pattern === '**/*.sh') {
168
- const scriptsDir = path.join(projectRoot, 'scripts');
169
- if (fs.existsSync(scriptsDir)) {
170
- const files = fs.readdirSync(scriptsDir);
171
- files.forEach(file => {
172
- if (file.endsWith('.sh')) {
173
- const filePath = path.join(scriptsDir, file);
174
- fs.chmodSync(filePath, mode);
175
- }
176
- });
177
- }
178
- }
179
- }
180
- }
181
-
182
- // 运行主函数
183
- main();
@@ -1,25 +0,0 @@
1
- import { defineConfig } from 'drizzle-kit';
2
-
3
- // 这个配置会被 fullstack-cli 在运行时动态处理
4
- // 通过环境变量传递具体参数
5
- const outputDir = process.env.__DRIZZLE_OUT_DIR__ || './server/database/.introspect';
6
- const schemaPath = process.env.__DRIZZLE_SCHEMA_PATH__ || './server/database/schema.ts';
7
-
8
- const parsedUrl = new URL(process.env.SUDA_DATABASE_URL || '');
9
- const schemaFilter = parsedUrl.searchParams.get('schema')?.split(',') ?? [];
10
-
11
- const config = {
12
- schema: schemaPath,
13
- out: outputDir,
14
- tablesFilter: ['*'],
15
- schemaFilter,
16
- dialect: 'postgresql',
17
- dbCredentials: {
18
- host: parsedUrl.hostname,
19
- port: Number(parsedUrl.port) || 5432,
20
- user: decodeURIComponent(parsedUrl.username),
21
- password: decodeURIComponent(parsedUrl.password),
22
- database: parsedUrl.pathname.split('/')[1],
23
- },
24
- }
25
- export default defineConfig(config);
@@ -1,4 +0,0 @@
1
- export interface ServerType {
2
- // 服务器类型
3
- serverType: string;
4
- }