xinyu-pro 0.21.2 → 0.21.3

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.
Files changed (3) hide show
  1. package/bin/cli.js +114 -89
  2. package/package.json +1 -1
  3. package/version.json +1 -1
package/bin/cli.js CHANGED
@@ -1,20 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { existsSync, readFileSync, writeFileSync } = require('fs');
4
- const { join, resolve } = require('path');
3
+ const { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } = require('fs');
4
+ const { join } = require('path');
5
5
  const { createInterface } = require('readline');
6
6
  const { execSync, spawn } = require('child_process');
7
+ const { homedir } = require('os');
7
8
 
8
9
  const isWin = process.platform === 'win32';
9
- const cwd = process.cwd();
10
- const APP_DIR = __dirname.includes('node_modules') ? resolve(__dirname, '..') : cwd;
10
+ const APP_DIR = __dirname.includes('node_modules') ? resolve(__dirname, '..') : process.cwd();
11
+ const XINYU_HOME = join(homedir(), '.xinyu-pro');
12
+ const RUN_DIR = XINYU_HOME;
11
13
 
12
14
  const args = process.argv.slice(2);
13
15
  const HELP_FLAG = args.includes('--help') || args.includes('-h');
14
16
  const DEV_FLAG = args.includes('--dev');
17
+ const RESET_FLAG = args.includes('--reset');
15
18
  const PORT_INDEX = args.indexOf('--port');
16
19
  const CUSTOM_PORT = PORT_INDEX !== -1 ? args[PORT_INDEX + 1] : '3000';
17
- const BUILD_FLAG = args.includes('--build');
20
+
21
+ const SKIP_DIRS = new Set(['node_modules', '.next', 'data', '.git']);
18
22
 
19
23
  function echo(msg, type = 'info') {
20
24
  const colors = {
@@ -48,10 +52,10 @@ function showHelp() {
48
52
  echo(' ╚══════════════════════════════════════╝', 'bold');
49
53
  echo('');
50
54
  echo(' 用法:', 'bold');
51
- echo(' $ xinyu 启动服务器(自动构建)');
55
+ echo(' $ xinyu 启动服务器');
52
56
  echo(' $ xinyu --dev 开发模式(热更新)');
53
57
  echo(' $ xinyu --port 4000 指定端口');
54
- echo(' $ xinyu --build 仅构建不启动');
58
+ echo(' $ xinyu --reset 重新安装并启动');
55
59
  echo(' $ xinyu --help 显示帮助');
56
60
  echo('');
57
61
  echo(' 环境要求:', 'bold');
@@ -69,14 +73,82 @@ function checkNodeVersion() {
69
73
  }
70
74
  }
71
75
 
72
- function detectPackageManager() {
73
- if (existsSync(join(APP_DIR, 'yarn.lock'))) return 'yarn';
74
- if (existsSync(join(APP_DIR, 'pnpm-lock.yaml'))) return 'pnpm';
75
- return 'npm';
76
+ function copyRecursive(src, dest) {
77
+ mkdirSync(dest, { recursive: true });
78
+ const entries = readdirSync(src, { withFileTypes: true });
79
+ for (const entry of entries) {
80
+ if (SKIP_DIRS.has(entry.name)) continue;
81
+ if (entry.name.startsWith('.env') && entry.name !== '.env.example') continue;
82
+ const srcPath = join(src, entry.name);
83
+ const destPath = join(dest, entry.name);
84
+ if (entry.isDirectory()) {
85
+ copyRecursive(srcPath, destPath);
86
+ } else {
87
+ if (!existsSync(destPath)) {
88
+ try {
89
+ mkdirSync(join(dest, '..'), { recursive: true });
90
+ writeFileSync(destPath, readFileSync(srcPath));
91
+ } catch { }
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ function ensureAppSetup() {
98
+ const isFresh = !existsSync(join(RUN_DIR, 'package.json'));
99
+
100
+ if (isFresh || RESET_FLAG) {
101
+ if (RESET_FLAG && existsSync(RUN_DIR)) {
102
+ echo(' ■ 正在重置应用...', 'info');
103
+ try {
104
+ const rmDir = join(RUN_DIR, '..', '.xinyu-pro-backup');
105
+ if (existsSync(rmDir)) {
106
+ execSync(isWin ? `rmdir /s /q "${rmDir}"` : `rm -rf "${rmDir}"`, { shell: isWin });
107
+ }
108
+ execSync(isWin ? `move "${RUN_DIR}" "${rmDir}"` : `mv "${RUN_DIR}" "${rmDir}"`, { shell: isWin });
109
+ } catch { }
110
+ }
111
+
112
+ echo(' ■ 正在初始化应用目录...', 'info');
113
+ echo(' ' + RUN_DIR, 'dim');
114
+ mkdirSync(RUN_DIR, { recursive: true });
115
+ copyRecursive(APP_DIR, RUN_DIR);
116
+
117
+ const envTarget = join(RUN_DIR, '.env.local');
118
+ if (!existsSync(envTarget)) {
119
+ const envExample = join(RUN_DIR, '.env.example');
120
+ if (existsSync(envExample)) {
121
+ writeFileSync(envTarget, readFileSync(envExample, 'utf-8'), 'utf-8');
122
+ }
123
+ }
124
+ return true;
125
+ }
126
+
127
+ return false;
128
+ }
129
+
130
+ function installDependencies() {
131
+ if (!existsSync(join(RUN_DIR, 'node_modules'))) {
132
+ echo(' ■ 正在安装依赖...', 'info');
133
+ execSync('npm install', { cwd: RUN_DIR, stdio: 'inherit', shell: isWin });
134
+ return true;
135
+ }
136
+ return false;
137
+ }
138
+
139
+ function runBuild() {
140
+ echo(' ■ 正在构建应用(首次需要 1-2 分钟)...', 'info');
141
+ try {
142
+ execSync('npx next build', { cwd: RUN_DIR, stdio: 'inherit', shell: isWin });
143
+ echo(' ✓ 构建完成', 'success');
144
+ } catch {
145
+ echo(' ✗ 构建失败,请检查错误信息', 'error');
146
+ process.exit(1);
147
+ }
76
148
  }
77
149
 
78
- function getNextCommand() {
79
- const binDir = join(APP_DIR, 'node_modules', '.bin');
150
+ function getNextBin() {
151
+ const binDir = join(RUN_DIR, 'node_modules', '.bin');
80
152
  if (isWin) {
81
153
  const cmdPath = join(binDir, 'next.cmd');
82
154
  if (existsSync(cmdPath)) return cmdPath;
@@ -86,106 +158,66 @@ function getNextCommand() {
86
158
  return 'next';
87
159
  }
88
160
 
89
- function checkDependencies() {
90
- if (!existsSync(join(APP_DIR, 'node_modules'))) {
91
- echo(' 未检测到 node_modules,正在安装依赖...', 'warn');
92
- const pm = detectPackageManager();
93
- const cmd = pm === 'yarn' ? 'yarn install' : pm === 'pnpm' ? 'pnpm install' : 'npm install';
94
- echo(` $ ${cmd}`, 'dim');
95
- execSync(cmd, { cwd: APP_DIR, stdio: 'inherit', shell: isWin });
96
- return true;
97
- }
98
- return false;
99
- }
100
-
101
- function setupEnvFile() {
102
- const envPath = join(APP_DIR, '.env.local');
103
- const examplePath = join(APP_DIR, '.env.example');
161
+ function setupEnvInteractive() {
162
+ const envPath = join(RUN_DIR, '.env.local');
163
+ const examplePath = join(RUN_DIR, '.env.example');
104
164
 
105
165
  if (existsSync(envPath)) {
106
166
  const content = readFileSync(envPath, 'utf-8');
107
167
  if (content.includes('your-api-key') || content.includes('你的密码')) {
108
- echo(' ⚠ .env.local 中存在占位值,建议修改为真实配置', 'warn');
168
+ echo(' ⚠ .env.local 中存在占位值,建议修改', 'warn');
109
169
  }
110
170
  return;
111
171
  }
112
172
 
113
- echo(' ⚠ 未检测到 .env.local 配置文件', 'warn');
114
- echo('');
115
-
116
173
  if (!existsSync(examplePath)) {
117
- echo(' ✗ 未找到 .env.example,请手动创建 .env.local', 'error');
174
+ echo(' ✗ 未找到 .env.example', 'error');
118
175
  process.exit(1);
119
176
  }
120
177
 
121
178
  const example = readFileSync(examplePath, 'utf-8');
122
179
  writeFileSync(envPath, example, 'utf-8');
123
- echo(' ✓ 已从 .env.example 创建 .env.local', 'success');
124
- echo('');
180
+ echo(' ✓ 已创建 .env.local', 'success');
125
181
 
126
- const setupInteractive = async () => {
127
- echo(' ── 请填写以下必要配置(直接回车可跳过) ──', 'bold');
182
+ const run = async () => {
128
183
  echo('');
129
-
184
+ echo(' ── 填写配置(直接回车可跳过) ──', 'bold');
130
185
  const apiKey = await ask(' AI API 密钥 (AI_API_KEY): ');
131
186
  if (apiKey) {
132
- let content = readFileSync(envPath, 'utf-8');
133
- content = content.replace(/AI_API_KEY=.*/, `AI_API_KEY=${apiKey}`);
134
- writeFileSync(envPath, content, 'utf-8');
187
+ let c = readFileSync(envPath, 'utf-8');
188
+ writeFileSync(envPath, c.replace(/AI_API_KEY=.*/, `AI_API_KEY=${apiKey}`), 'utf-8');
135
189
  }
136
-
137
- const dbPassword = await ask(' 数据库密码 (DB_PASSWORD,默认空): ');
190
+ const dbPassword = await ask(' 数据库密码 (DB_PASSWORD): ');
138
191
  if (dbPassword) {
139
- let content = readFileSync(envPath, 'utf-8');
140
- content = content.replace(/DB_PASSWORD=.*/, `DB_PASSWORD=${dbPassword}`);
141
- writeFileSync(envPath, content, 'utf-8');
192
+ let c = readFileSync(envPath, 'utf-8');
193
+ writeFileSync(envPath, c.replace(/DB_PASSWORD=.*/, `DB_PASSWORD=${dbPassword}`), 'utf-8');
142
194
  }
143
-
144
195
  const dbName = await ask(' 数据库名称 (DB_NAME,默认 xinyu): ');
145
196
  if (dbName) {
146
- let content = readFileSync(envPath, 'utf-8');
147
- content = content.replace(/DB_NAME=.*/, `DB_NAME=${dbName}`);
148
- writeFileSync(envPath, content, 'utf-8');
197
+ let c = readFileSync(envPath, 'utf-8');
198
+ writeFileSync(envPath, c.replace(/DB_NAME=.*/, `DB_NAME=${dbName}`), 'utf-8');
149
199
  }
150
-
151
- echo('');
152
- echo(' ✓ .env.local 配置完成', 'success');
153
- echo(' ✎ 如需修改,请编辑 ' + envPath, 'dim');
200
+ echo(' ✓ 配置完成', 'success');
201
+ echo(' ✎ 编辑: ' + envPath, 'dim');
154
202
  };
155
-
156
- return setupInteractive();
157
- }
158
-
159
- function checkBuildExists() {
160
- return existsSync(join(APP_DIR, '.next', 'BUILD_ID'));
161
- }
162
-
163
- function runBuild() {
164
- echo(' ■ 正在构建应用,首次构建可能需要 1-2 分钟...', 'info');
165
- const nextCmd = getNextCommand();
166
- try {
167
- execSync(`"${nextCmd}" build`, { cwd: APP_DIR, stdio: 'inherit', shell: isWin });
168
- echo(' ✓ 构建完成', 'success');
169
- } catch (e) {
170
- echo(' ✗ 构建失败,请检查代码错误', 'error');
171
- process.exit(1);
172
- }
203
+ return run();
173
204
  }
174
205
 
175
206
  function startServer(port, devMode) {
176
- const nextCmd = getNextCommand();
207
+ const nextBin = getNextBin();
177
208
  const action = devMode ? 'dev' : 'start';
178
209
 
179
210
  echo('');
180
211
  echo(` ╔══════════════════════════════════════╗`, 'success');
181
212
  echo(` ║ 星语 Pro 服务器已启动 ║`, 'success');
182
213
  echo(` ║ 访问地址: http://localhost:${port} ║`, 'success');
214
+ echo(` ║ 工作目录: ${RUN_DIR}`, 'dim');
183
215
  echo(` ║ 按 Ctrl+C 停止 ║`, 'success');
184
216
  echo(` ╚══════════════════════════════════════╝`, 'success');
185
217
  echo('');
186
218
 
187
- const child = spawn(nextCmd, [action, '-p', port], {
188
- cwd: APP_DIR,
219
+ const child = spawn(nextBin, [action, '-p', port], {
220
+ cwd: RUN_DIR,
189
221
  stdio: 'inherit',
190
222
  shell: isWin,
191
223
  env: { ...process.env, PORT: port },
@@ -205,7 +237,7 @@ function startServer(port, devMode) {
205
237
 
206
238
  process.on('SIGINT', () => {
207
239
  echo('');
208
- echo(' ■ 正在关闭服务器...', 'info');
240
+ echo(' ■ 正在关闭...', 'info');
209
241
  child.kill('SIGINT');
210
242
  process.exit(0);
211
243
  });
@@ -220,7 +252,6 @@ async function main() {
220
252
  echo('');
221
253
  echo(' ╔══════════════════════════════════════╗', 'bold');
222
254
  echo(' ║ 星语 Pro (xinyu-pro) ║', 'bold');
223
- echo(' ║ 启动中... ║', 'bold');
224
255
  echo(' ╚══════════════════════════════════════╝', 'bold');
225
256
  echo('');
226
257
 
@@ -230,19 +261,13 @@ async function main() {
230
261
  }
231
262
 
232
263
  checkNodeVersion();
233
- checkDependencies();
234
-
235
- await setupEnvFile();
236
-
237
- if (BUILD_FLAG) {
238
- runBuild();
239
- echo(' ✓ 构建完成,可使用 xinyu 启动服务器', 'success');
240
- return;
241
- }
264
+ ensureAppSetup();
265
+ installDependencies();
266
+ await setupEnvInteractive();
242
267
 
243
- if (!DEV_FLAG && !checkBuildExists()) {
244
- echo(' ⚠ 未检测到构建产物,将自动执行构建', 'warn');
245
- echo(' (首次构建需要下载依赖并编译 TypeScript,请耐心等待)', 'dim');
268
+ if (!DEV_FLAG && !existsSync(join(RUN_DIR, '.next', 'BUILD_ID'))) {
269
+ echo(' ⚠ 未检测到构建产物,自动构建...', 'warn');
270
+ echo(' (首次构建需要下载依赖并编译,请耐心等待)', 'dim');
246
271
  echo('');
247
272
  runBuild();
248
273
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xinyu-pro",
3
- "version": "0.21.2",
3
+ "version": "0.21.3",
4
4
  "private": false,
5
5
  "description": "星语 Pro - AI 驱动的互动叙事平台",
6
6
  "author": "RestRegular",
package/version.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.21.2",
2
+ "version": "0.21.3",
3
3
  "name": "xinyu-pro",
4
4
  "displayName": "星语 Pro",
5
5
  "description": "AI 驱动的互动叙事平台"