@cloud411716/fancy-webnovel 0.1.80 → 0.1.82

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud411716/fancy-webnovel",
3
- "version": "0.1.80",
3
+ "version": "0.1.82",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -90,47 +90,112 @@ export async function apply(ctx) {
90
90
  length = 'long';
91
91
  }
92
92
 
93
- notify(session, scan.notify({ type: 'collecting', platform, length }));
94
-
95
- const stopActivity = startActivity(session);
96
- const scriptResult = await spawnScript(
97
- 'node',
98
- [join(SCAN_SCRIPTS_DIR, 'run-scan.js'),
99
- '--project-root', projectRoot, '--platform', platform, '--length', length],
100
- { timeout: 120000, stop: stopActivity }
101
- );
102
-
103
- if (!scriptResult.ok) {
104
- const err = (typeof scriptResult.error === 'object' && scriptResult.error !== null)
105
- ? (scriptResult.error.message || JSON.stringify(scriptResult.error))
106
- : String(scriptResult.error || '未知错误');
107
- notify(session, scan.notify({ type: 'failed', err }));
93
+ // 执行采集,包含 Chromium 缺失处理
94
+ const scanResult = await runScanWithChromiumFix(ctx, session, invocation, {
95
+ platform, length, projectRoot,
96
+ });
97
+
98
+ if (scanResult === 'retry') {
99
+ // Chromium 安装后重试已完成,runScanWithChromiumFix 已处理所有输出
108
100
  return { kind: 'success', text: '' };
109
101
  }
110
-
111
- // 解析 receipt
112
- let receipt;
113
- try { receipt = JSON.parse(scriptResult.output); } catch (_) {
114
- notify(session, scan.notify({ type: 'parse_error' }));
102
+ if (!scanResult) {
115
103
  return { kind: 'success', text: '' };
116
104
  }
117
105
 
118
- const files = receipt.scan_files || [];
119
- const topicFile = receipt.topic_decision || '';
120
-
121
- // 通知用户 + 触发 LLM 分析
122
- notify(session, scan.notify({ type: 'handover', platform, length, files, topicFile, projectRoot }));
106
+ return { kind: 'success', text: '' };
107
+ },
108
+ });
109
+ }
123
110
 
124
- const prompt = scan.llmAnalysisPrompt({
125
- platform, length,
126
- date: receipt.date || '',
127
- files,
128
- topicFile,
129
- projectRoot,
111
+ /**
112
+ * 执行采集,检测 Chromium 缺失并弹框处理。
113
+ * 返回 undefined 表示已处理完毕(取消/失败),返回 'retry' 表示安装后重试完成。
114
+ */
115
+ async function runScanWithChromiumFix(ctx, session, invocation, { platform, length, projectRoot }) {
116
+ const { spawn } = await import('child_process');
117
+
118
+ const runOnce = async (isRetry = false) => {
119
+ const stop = startActivity(session);
120
+ if (isRetry) notify(session, '⏳ 重新采集...\n');
121
+ const r = await spawnScript(
122
+ 'node',
123
+ [join(SCAN_SCRIPTS_DIR, 'run-scan.js'),
124
+ '--project-root', projectRoot, '--platform', platform, '--length', length],
125
+ { timeout: 120000, stop }
126
+ );
127
+ return r;
128
+ };
129
+
130
+ const handleSuccess = (stdout) => {
131
+ let receipt;
132
+ try { receipt = JSON.parse(stdout); } catch (_) {
133
+ notify(session, scan.notify({ type: 'parse_error' }));
134
+ return;
135
+ }
136
+ const files = receipt.scan_files || [];
137
+ const topicFile = receipt.topic_decision || '';
138
+ notify(session, scan.notify({ type: 'handover', platform, length, files, topicFile, projectRoot }));
139
+ const prompt = scan.llmAnalysisPrompt({ platform, length, date: receipt.date || '', files, topicFile, projectRoot });
140
+ llmFollowup(invocation, prompt);
141
+ };
142
+
143
+ const scriptResult = await runOnce();
144
+
145
+ if (!scriptResult.ok) {
146
+ const rawErr = (typeof scriptResult.error === 'object' && scriptResult.error !== null)
147
+ ? (scriptResult.error.message || JSON.stringify(scriptResult.error))
148
+ : String(scriptResult.error || '未知错误');
149
+
150
+ // Chromium 未找到 → 弹框让用户选择
151
+ if (rawErr.includes("Executable doesn't exist") || rawErr.includes('Executable doesn')) {
152
+ const choice = await ctx.userQuestions.ask({
153
+ questions: [scan.askChromiumInstall()],
130
154
  });
131
- llmFollowup(invocation, prompt);
155
+ const answer = choice.answers[0]?.selected?.[0] ?? choice.answers[0]?.custom;
156
+ if (!answer || answer === '我自己安装') {
157
+ notify(session, '⚠️ 已取消。请手动安装 Chromium 后再次运行 /fancy-scan。\n安装命令:npx playwright install chromium');
158
+ return undefined;
159
+ }
132
160
 
133
- return { kind: 'success', text: '' };
161
+ // 用户选择自动安装
162
+ notify(session, '⏳ 正在下载 Chromium 浏览器(首次约 2-3 分钟),请耐心等待...\n');
163
+ const installStop = startActivity(session);
164
+
165
+ const installProc = spawn('npx', ['playwright', 'install', 'chromium'], {
166
+ stdio: ['ignore', 'pipe', 'pipe'],
167
+ });
168
+ const installOut = [];
169
+ installProc.stdout.on('data', d => { installOut.push(d.toString()); });
170
+ installProc.stderr.on('data', d => { installOut.push(d.toString()); });
171
+
172
+ return new Promise((resolve) => {
173
+ installProc.on('close', async (code) => {
174
+ installStop();
175
+ if (code === 0) {
176
+ notify(session, '✅ Chromium 安装完成,正在重新采集...\n');
177
+ const retryResult = await runOnce(true);
178
+ if (!retryResult.ok) {
179
+ const err2 = String(retryResult.error || '未知错误');
180
+ notify(session, scan.notify({ type: 'failed', err: err2 }));
181
+ resolve(undefined);
182
+ return;
183
+ }
184
+ handleSuccess(retryResult.output);
185
+ resolve('retry');
186
+ } else {
187
+ const out = installOut.join('').slice(-300);
188
+ notify(session, `❌ Chromium 安装失败(exit ${code})。\n请手动运行以下命令后重试:\n npx playwright install chromium\n\n安装日志末尾:\n${out}`);
189
+ resolve(undefined);
190
+ }
191
+ });
192
+ });
134
193
  }
135
- });
194
+
195
+ notify(session, scan.notify({ type: 'failed', err: rawErr }));
196
+ return undefined;
197
+ }
198
+
199
+ handleSuccess(scriptResult.output);
200
+ return 'retry';
136
201
  }
@@ -5,6 +5,12 @@
5
5
  * getArg() — 命令行参数解析
6
6
  * localDateStamp() — 本地日期戳(YYYYMMDD)
7
7
  * runCli() — CLI 入口包装(含进程超时和信号处理)
8
+ *
9
+ * 首次使用注意:
10
+ * playwright-core 需要 Chromium 二进制。
11
+ * 若报错 "Executable doesn't exist",运行一次以下命令下载:
12
+ * node -e "require('playwright-core').chromium.launch().then(b => b.close())"
13
+ * Chromium 会缓存到 ~/.cache/ms-playwright/(Linux)或对应平台缓存目录。
8
14
  */
9
15
 
10
16
  /** 解析 --xxx 参数 */
@@ -49,6 +49,17 @@ export const scan = {
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
+
52
63
  notify({ type, platform, length, files, receipt, topicFile, projectRoot, err }) {
53
64
  const lenStr = length === 'long' ? '长篇' : '短篇';
54
65
  const pLabel = platformLabel(platform);