xinyu-pro 0.21.1 → 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 +118 -99
  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, mkdirSync, copyFileSync } = 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
7
  const { homedir } = require('os');
8
8
 
9
- const cwd = process.cwd();
10
- const APP_DIR = __dirname.includes('node_modules') ? resolve(__dirname, '..') : cwd;
9
+ const isWin = process.platform === 'win32';
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,132 +73,154 @@ 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
+ }
76
95
  }
77
96
 
78
- function getNextCliPath() {
79
- const possiblePaths = [
80
- join(APP_DIR, 'node_modules', '.bin', 'next'),
81
- join(APP_DIR, 'node_modules', 'next', 'dist', 'bin', 'next'),
82
- join(cwd, 'node_modules', '.bin', 'next'),
83
- ];
84
- for (const p of possiblePaths) {
85
- if (existsSync(p)) return p;
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;
86
125
  }
87
- return 'next';
126
+
127
+ return false;
88
128
  }
89
129
 
90
- function checkDependencies() {
91
- if (!existsSync(join(APP_DIR, 'node_modules'))) {
92
- echo(' 未检测到 node_modules,正在安装依赖...', 'warn');
93
- const pm = detectPackageManager();
94
- const cmd = pm === 'yarn' ? 'yarn install' : pm === 'pnpm' ? 'pnpm install' : 'npm install';
95
- echo(` $ ${cmd}`, 'dim');
96
- execSync(cmd, { cwd: APP_DIR, stdio: 'inherit' });
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 });
97
134
  return true;
98
135
  }
99
136
  return false;
100
137
  }
101
138
 
102
- function setupEnvFile() {
103
- const envPath = join(APP_DIR, '.env.local');
104
- const examplePath = join(APP_DIR, '.env.example');
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
+ }
148
+ }
149
+
150
+ function getNextBin() {
151
+ const binDir = join(RUN_DIR, 'node_modules', '.bin');
152
+ if (isWin) {
153
+ const cmdPath = join(binDir, 'next.cmd');
154
+ if (existsSync(cmdPath)) return cmdPath;
155
+ }
156
+ const unixPath = join(binDir, 'next');
157
+ if (existsSync(unixPath)) return unixPath;
158
+ return 'next';
159
+ }
160
+
161
+ function setupEnvInteractive() {
162
+ const envPath = join(RUN_DIR, '.env.local');
163
+ const examplePath = join(RUN_DIR, '.env.example');
105
164
 
106
165
  if (existsSync(envPath)) {
107
166
  const content = readFileSync(envPath, 'utf-8');
108
167
  if (content.includes('your-api-key') || content.includes('你的密码')) {
109
- echo(' ⚠ .env.local 中存在占位值,建议修改为真实配置', 'warn');
168
+ echo(' ⚠ .env.local 中存在占位值,建议修改', 'warn');
110
169
  }
111
170
  return;
112
171
  }
113
172
 
114
- echo(' ⚠ 未检测到 .env.local 配置文件', 'warn');
115
- echo('');
116
-
117
173
  if (!existsSync(examplePath)) {
118
- echo(' ✗ 未找到 .env.example,请手动创建 .env.local', 'error');
174
+ echo(' ✗ 未找到 .env.example', 'error');
119
175
  process.exit(1);
120
176
  }
121
177
 
122
178
  const example = readFileSync(examplePath, 'utf-8');
123
179
  writeFileSync(envPath, example, 'utf-8');
124
- echo(' ✓ 已从 .env.example 创建 .env.local', 'success');
125
- echo('');
180
+ echo(' ✓ 已创建 .env.local', 'success');
126
181
 
127
- const setupInteractive = async () => {
128
- echo(' ── 请填写以下必要配置(直接回车可跳过) ──', 'bold');
182
+ const run = async () => {
129
183
  echo('');
130
-
184
+ echo(' ── 填写配置(直接回车可跳过) ──', 'bold');
131
185
  const apiKey = await ask(' AI API 密钥 (AI_API_KEY): ');
132
186
  if (apiKey) {
133
- let content = readFileSync(envPath, 'utf-8');
134
- content = content.replace(/AI_API_KEY=.*/, `AI_API_KEY=${apiKey}`);
135
- 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');
136
189
  }
137
-
138
- const dbPassword = await ask(' 数据库密码 (DB_PASSWORD,默认空): ');
190
+ const dbPassword = await ask(' 数据库密码 (DB_PASSWORD): ');
139
191
  if (dbPassword) {
140
- let content = readFileSync(envPath, 'utf-8');
141
- content = content.replace(/DB_PASSWORD=.*/, `DB_PASSWORD=${dbPassword}`);
142
- writeFileSync(envPath, content, 'utf-8');
192
+ let c = readFileSync(envPath, 'utf-8');
193
+ writeFileSync(envPath, c.replace(/DB_PASSWORD=.*/, `DB_PASSWORD=${dbPassword}`), 'utf-8');
143
194
  }
144
-
145
195
  const dbName = await ask(' 数据库名称 (DB_NAME,默认 xinyu): ');
146
196
  if (dbName) {
147
- let content = readFileSync(envPath, 'utf-8');
148
- content = content.replace(/DB_NAME=.*/, `DB_NAME=${dbName}`);
149
- writeFileSync(envPath, content, 'utf-8');
197
+ let c = readFileSync(envPath, 'utf-8');
198
+ writeFileSync(envPath, c.replace(/DB_NAME=.*/, `DB_NAME=${dbName}`), 'utf-8');
150
199
  }
151
-
152
- echo('');
153
- echo(' ✓ .env.local 配置完成', 'success');
154
- echo(' ✎ 如需修改,请编辑 ' + envPath, 'dim');
200
+ echo(' ✓ 配置完成', 'success');
201
+ echo(' ✎ 编辑: ' + envPath, 'dim');
155
202
  };
156
-
157
- return setupInteractive();
158
- }
159
-
160
- function checkBuildExists() {
161
- return existsSync(join(APP_DIR, '.next', 'BUILD_ID'));
162
- }
163
-
164
- function runBuild() {
165
- echo(' ■ 正在构建应用,首次构建可能需要 1-2 分钟...', 'info');
166
- const nextCli = getNextCliPath();
167
- try {
168
- execSync(`"${process.execPath}" "${nextCli}" build`, {
169
- cwd: APP_DIR,
170
- stdio: 'inherit',
171
- });
172
- echo(' ✓ 构建完成', 'success');
173
- } catch (e) {
174
- echo(' ✗ 构建失败,请检查代码错误', 'error');
175
- process.exit(1);
176
- }
203
+ return run();
177
204
  }
178
205
 
179
206
  function startServer(port, devMode) {
180
- const nextCli = getNextCliPath();
207
+ const nextBin = getNextBin();
208
+ const action = devMode ? 'dev' : 'start';
181
209
 
182
210
  echo('');
183
211
  echo(` ╔══════════════════════════════════════╗`, 'success');
184
212
  echo(` ║ 星语 Pro 服务器已启动 ║`, 'success');
185
213
  echo(` ║ 访问地址: http://localhost:${port} ║`, 'success');
214
+ echo(` ║ 工作目录: ${RUN_DIR}`, 'dim');
186
215
  echo(` ║ 按 Ctrl+C 停止 ║`, 'success');
187
216
  echo(` ╚══════════════════════════════════════╝`, 'success');
188
217
  echo('');
189
218
 
190
- const cmd = devMode ? 'dev' : 'start';
191
- const child = spawn(process.execPath, [nextCli, cmd, '-p', port], {
192
- cwd: APP_DIR,
219
+ const child = spawn(nextBin, [action, '-p', port], {
220
+ cwd: RUN_DIR,
193
221
  stdio: 'inherit',
194
- env: {
195
- ...process.env,
196
- PORT: port,
197
- },
222
+ shell: isWin,
223
+ env: { ...process.env, PORT: port },
198
224
  });
199
225
 
200
226
  child.on('error', (err) => {
@@ -211,7 +237,7 @@ function startServer(port, devMode) {
211
237
 
212
238
  process.on('SIGINT', () => {
213
239
  echo('');
214
- echo(' ■ 正在关闭服务器...', 'info');
240
+ echo(' ■ 正在关闭...', 'info');
215
241
  child.kill('SIGINT');
216
242
  process.exit(0);
217
243
  });
@@ -226,7 +252,6 @@ async function main() {
226
252
  echo('');
227
253
  echo(' ╔══════════════════════════════════════╗', 'bold');
228
254
  echo(' ║ 星语 Pro (xinyu-pro) ║', 'bold');
229
- echo(' ║ 启动中... ║', 'bold');
230
255
  echo(' ╚══════════════════════════════════════╝', 'bold');
231
256
  echo('');
232
257
 
@@ -236,19 +261,13 @@ async function main() {
236
261
  }
237
262
 
238
263
  checkNodeVersion();
239
- checkDependencies();
240
-
241
- await setupEnvFile();
242
-
243
- if (BUILD_FLAG) {
244
- runBuild();
245
- echo(' ✓ 构建完成,可使用 xinyu 启动服务器', 'success');
246
- return;
247
- }
264
+ ensureAppSetup();
265
+ installDependencies();
266
+ await setupEnvInteractive();
248
267
 
249
- if (!DEV_FLAG && !checkBuildExists()) {
250
- echo(' ⚠ 未检测到构建产物,将自动执行构建', 'warn');
251
- echo(' (首次构建需要下载依赖并编译 TypeScript,请耐心等待)', 'dim');
268
+ if (!DEV_FLAG && !existsSync(join(RUN_DIR, '.next', 'BUILD_ID'))) {
269
+ echo(' ⚠ 未检测到构建产物,自动构建...', 'warn');
270
+ echo(' (首次构建需要下载依赖并编译,请耐心等待)', 'dim');
252
271
  echo('');
253
272
  runBuild();
254
273
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xinyu-pro",
3
- "version": "0.21.1",
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.1",
2
+ "version": "0.21.3",
3
3
  "name": "xinyu-pro",
4
4
  "displayName": "星语 Pro",
5
5
  "description": "AI 驱动的互动叙事平台"