@cloud411716/fancy-webnovel 0.1.99 → 0.2.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/infra.js CHANGED
@@ -101,18 +101,28 @@ export function onUserCancel(session, onCancel) {
101
101
  export function spawnScript(cmd, args, { timeout = 30000, cwd, stop } = {}) {
102
102
  return new Promise(async (resolve) => {
103
103
  const { spawn } = await import('child_process');
104
- const proc = spawn(cmd, args, { timeout, cwd, stdio: ['ignore', 'pipe', 'pipe'] });
104
+ const proc = spawn(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
105
105
  const outParts = [], errParts = [];
106
+ let settled = false;
107
+ const settle = (v) => { if (!settled) { settled = true; resolve(v); } };
108
+
109
+ const timer = timeout > 0 ? setTimeout(() => {
110
+ proc.kill('SIGTERM');
111
+ settle({ ok: false, output: outParts.join(''), error: 'timeout after ' + timeout + 'ms' });
112
+ }, timeout) : null;
113
+
106
114
  proc.stdout.on('data', (d) => { outParts.push(d.toString()); });
107
115
  proc.stderr.on('data', (d) => { errParts.push(d.toString()); });
108
116
 
109
117
  const cleanup = () => { try { stop?.(); } catch (_) {} };
110
118
  proc.on('close', (code) => {
111
- const stdout = outParts.join(''), stderr = errParts.join('');
119
+ clearTimeout(timer);
112
120
  cleanup();
121
+ if (settled) return;
122
+ const stdout = outParts.join(''), stderr = errParts.join('');
113
123
  try {
114
124
  const parsed = JSON.parse(stdout);
115
- resolve({
125
+ settle({
116
126
  ok: parsed.ok === true,
117
127
  output: stdout,
118
128
  error: parsed.error
@@ -122,9 +132,9 @@ export function spawnScript(cmd, args, { timeout = 30000, cwd, stop } = {}) {
122
132
  : stderr || undefined,
123
133
  });
124
134
  } catch (_) {
125
- resolve({ ok: false, output: stdout, error: stderr || ('exit ' + code) });
135
+ settle({ ok: false, output: stdout, error: stderr || ('exit ' + code) });
126
136
  }
127
137
  });
128
- proc.on('error', (err) => { cleanup(); resolve({ ok: false, output: '', error: err.message }); });
138
+ proc.on('error', (err) => { clearTimeout(timer); cleanup(); settle({ ok: false, output: '', error: err.message }); });
129
139
  });
130
140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud411716/fancy-webnovel",
3
- "version": "0.1.99",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * fancy-bootstrap init — 项目初始化
3
3
  */
4
- import { writeFileSync, mkdirSync, existsSync, appendFileSync } from 'fs';
4
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
5
5
  import { expandHome, resolvePath, joinPath } from './path-utils.js';
6
6
 
7
7
  const DIRS = [
@@ -27,10 +27,7 @@ function nowIso() {
27
27
  function main() {
28
28
  const projectRootRaw = process.argv[2] ?? '';
29
29
 
30
- // 调试:打印接收到的参数
31
- try { appendFileSync('/tmp/fancy-init-debug.txt', `argv=${JSON.stringify(process.argv)}\nraw=${projectRootRaw}\n`); } catch(e) {}
32
-
33
- if (projectRootRaw.includes('..') || /[<>:"'|?*]/.test(projectRootRaw) || /[\x00-\x1f]/.test(projectRootRaw)) {
30
+ if (projectRootRaw.includes('..') || /[<>:\"'|?*]/.test(projectRootRaw) || /[\x00-\x1f]/.test(projectRootRaw)) {
34
31
  console.log(JSON.stringify({ ok: false, error: { code: 1, code_name: 'EC001', message: '路径不合法' } }));
35
32
  process.exit(1);
36
33
  }
@@ -99,7 +96,6 @@ function main() {
99
96
 
100
97
  } catch (e) {
101
98
  const errMsg = e instanceof Error ? e.message : String(e);
102
- try { appendFileSync('/tmp/fancy-init-debug.txt', `catch: ${errMsg}\n`); } catch(_) {}
103
99
  console.log(JSON.stringify({ ok: false, error: { code: 17, code_name: 'EC017', message: `初始化失败: ${errMsg}` } }));
104
100
  process.exit(17);
105
101
  }
@@ -34,8 +34,8 @@ export const bootstrap = {
34
34
 
35
35
  confirmOptions() {
36
36
  return [
37
- { label: '确认创建', description: '开始在此目录初始化项目' },
38
- { label: '取消', description: '取消本次操作' },
37
+ { label: '确认创建' },
38
+ { label: '取消' },
39
39
  ];
40
40
  },
41
41
  };
@@ -4,7 +4,7 @@
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname, join } from 'path';
6
6
  import { existsSync } from 'fs';
7
- import { notify, llmFollowup, spawnScript, startActivity, onUserCancel } from '../../infra.js';
7
+ import { notify, llmFollowup, spawnScript, startActivity } from '../../infra.js';
8
8
  import { scan, PLATFORM_TABLE, platformLabel } from './templates.js';
9
9
 
10
10
  export const inject = ['commands', 'userQuestions'];
@@ -109,12 +109,10 @@ export async function apply(ctx) {
109
109
  }
110
110
 
111
111
  /**
112
- * 执行采集,检测 Chromium 缺失并弹框处理。
113
- * 返回 undefined 表示已处理完毕(取消/失败),返回 'retry' 表示安装后重试完成。
112
+ * 执行采集,检测 Chromium 缺失则提示用户手动安装。
113
+ * 返回 undefined 表示已处理完毕(取消/失败),返回 'retry' 表示重试完成。
114
114
  */
115
115
  async function runScanWithChromiumFix(ctx, session, invocation, { platform, length, projectRoot }) {
116
- const { spawn } = await import('child_process');
117
-
118
116
  const runOnce = async (isRetry = false) => {
119
117
  const stop = startActivity(session);
120
118
  if (isRetry) notify(session, '⏳ 重新采集...\n');
@@ -147,84 +145,16 @@ async function runScanWithChromiumFix(ctx, session, invocation, { platform, leng
147
145
  ? (scriptResult.error.message || JSON.stringify(scriptResult.error))
148
146
  : String(scriptResult.error || '未知错误');
149
147
 
150
- // Chromium 未找到 → 弹框让用户选择
148
+ // Chromium 未找到 → 提示用户手动安装
151
149
  if (rawErr.includes("Executable doesn't exist") || rawErr.includes('Executable doesn')) {
152
- const choice = await ctx.userQuestions.ask({
153
- questions: [scan.askChromiumInstall()],
154
- });
155
- const answer = choice.answers[0]?.selected?.[0] ?? choice.answers[0]?.custom;
156
- if (!answer || answer === '我自己安装') {
157
- notify(session, '⚠️ 已取消。请手动安装 Chromium 后再次运行 /fancy-scan。\n安装命令:npm install -g playwright@1.40.0 --registry=https://registry.npmmirror.com && PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/ playwright install chromium --with-deps');
158
- return undefined;
159
- }
160
-
161
- // 用户选择自动安装
162
- notify(session, '⏳ 正在下载 Chromium 浏览器(首次约 2-3 分钟),请耐心等待...\n');
163
- const installStop = startActivity(session);
164
-
165
- // 预先弹出密码框(不等 sudo 提示出现)
166
- // 密码通过 stdin pipe 传入,用 sudo -S 确保 sudo 从 stdin 读
167
- const pwdAskPromise = ctx.userQuestions.ask({
168
- questions: [{
169
- question: '🔐 安装 Chromium 需要 sudo 权限,请输入密码:',
170
- hideCustomInput: false,
171
- }],
172
- }).then(() => {
173
- notify(session, '✅ 密码已收到,正在安装...\n');
174
- });
175
-
176
- const installProc = spawn('sh', ['-c',
177
- 'sudo -S npm install -g playwright@1.40.0 --registry=https://registry.npmmirror.com && ' +
178
- 'sudo -S PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/ playwright install chromium --with-deps'
179
- ], {
180
- stdio: ['pipe', 'pipe', 'pipe'],
181
- cwd: process.cwd(),
182
- });
183
- const installOut = [], installErr = [];
184
-
185
- installProc.stdout.on('data', d => { installOut.push(d.toString()); });
186
- installProc.stderr.on('data', d => { installErr.push(d.toString()); });
187
-
188
- // 注册 CTRL+C 取消处理:kill 安装进程
189
- const stopCancel = onUserCancel(session, () => {
190
- installProc.kill('SIGTERM');
191
- installStop();
192
- });
193
-
194
- // 预先弹出的密码框结果 → 写入子进程 stdin
195
- pwdAskPromise.then(async (pwdResult) => {
196
- const pwd = pwdResult.answers[0]?.custom ?? '';
197
- if (pwd) installProc.stdin?.write(pwd + '\n');
198
- }).catch(() => {});
199
-
200
- return new Promise((resolve) => {
201
- installProc.on('close', async (code) => {
202
- // 等预弹框完成
203
- await Promise.race([
204
- pwdAskPromise.catch(() => {}),
205
- new Promise(r => setTimeout(r, 180000)),
206
- ]);
207
- stopCancel(); // 移除取消监听
208
- installStop();
209
- if (code === 0) {
210
- notify(session, '✅ Chromium 安装完成,正在重新采集...\n');
211
- const retryResult = await runOnce(true);
212
- if (!retryResult.ok) {
213
- const err2 = String(retryResult.error || '未知错误');
214
- notify(session, scan.notify({ type: 'failed', err: err2 }));
215
- resolve(undefined);
216
- return;
217
- }
218
- handleSuccess(retryResult.output);
219
- resolve('retry');
220
- } else {
221
- const out = installOut.join('').slice(-300);
222
- const err = installErr.join('').slice(-300);
223
- notify(session, `❌ Chromium 安装失败(exit ${code})。\n请手动运行以下命令后重试:\n npm install -g playwright@1.40.0 --registry=https://registry.npmmirror.com && PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/ playwright install chromium --with-deps\n\nstdout末尾:\n${out}\n\nstderr末尾:\n${err}`);
224
- resolve(undefined);
225
- }
226
- });
227
- });
150
+ notify(session,
151
+ '⚠️ 未找到 Chromium 浏览器,请手动安装后再次运行 /fancy-scan。\n\n' +
152
+ '安装命令:\n' +
153
+ ' npm install -g playwright@1.40.0 --registry=https://registry.npmmirror.com\n' +
154
+ ' PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright/ playwright install chromium --with-deps\n\n' +
155
+ '安装完成后重新运行 /fancy-scan 即可。'
156
+ );
157
+ return undefined;
228
158
  }
229
159
 
230
160
  notify(session, scan.notify({ type: 'failed', err: rawErr }));
@@ -43,24 +43,13 @@ export const scan = {
43
43
  question: `${platformLabel(platform)} 同时支持长篇和短篇,请选择:`,
44
44
  hideCustomInput: true,
45
45
  options: [
46
- { label: '长篇', description: '' },
47
- { label: '短篇', description: '' },
46
+ { label: '长篇' },
47
+ { label: '短篇' },
48
48
  ],
49
49
  };
50
50
  },
51
51
 
52
- askChromiumInstall() {
53
- return {
54
- question: '⚠️ 未检测到 Chromium 浏览器,请选择处理方式:',
55
- hideCustomInput: true,
56
- options: [
57
- { label: '自动安装', description: '下载 Chromium 到 ~/.cache/ms-playwright/(需要网络,首次约 2-3 分钟)' },
58
- { label: '我自己安装', description: '手动安装后再次运行 /fancy-scan' },
59
- ],
60
- };
61
- },
62
-
63
- notify({ type, platform, length, files, receipt, topicFile, projectRoot, err }) {
52
+ notify({ type, platform, length, files, topicFile, projectRoot, err }) {
64
53
  const lenStr = length === 'long' ? '长篇' : '短篇';
65
54
  const pLabel = platformLabel(platform);
66
55
 
@@ -77,14 +66,6 @@ export const scan = {
77
66
  case 'cancelled_with_hint':
78
67
  return '⚠️ 已取消。请再次调用 /fancy-scan 继续。';
79
68
 
80
- case 'collecting':
81
- return `⏳ 开始采集 ${pLabel}(${lenStr})...`;
82
-
83
- case 'scan_complete':
84
- if (!files || files.length === 0) return `✅ ${pLabel}(${lenStr})采集完成`;
85
- const fileList = files.map(f => ' - ' + f.replace(projectRoot + '/', '')).join('\n');
86
- return `✅ ${pLabel}(${lenStr})采集完成\n\n📁 生成文件:\n${fileList}`;
87
-
88
69
  case 'failed':
89
70
  return '❌ 采集失败:' + err;
90
71