openyida 2026.4.2-beta.4 → 2026.4.2-beta.6

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.
@@ -0,0 +1,315 @@
1
+ /**
2
+ * login-cdp.js - 优先 CDP 协议登录,降级 Playwright 扫码登录
3
+ *
4
+ * 登录策略(按优先级):
5
+ * 1. CDP 模式:启动本地已安装的 Chrome,通过 CDP 协议连接,无需下载 Chromium
6
+ * 2. Playwright 模式:CDP 不可用时,降级为 playwright 内置 Chromium 扫码登录
7
+ *
8
+ * 导出函数:
9
+ * loginWithCdpOrPlaywright() - 执行完整登录流程(CDP 优先)
10
+ * findLocalChrome() - 查找本地 Chrome 可执行文件路径
11
+ * launchChromeWithCdp() - 启动 Chrome 并开启 CDP 调试端口
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+ const { execSync, spawn } = require('child_process');
20
+ const { extractInfoFromCookies } = require('../core/utils');
21
+ const { saveCookieCache, interactiveLogin } = require('./login');
22
+ const { t } = require('../core/i18n');
23
+
24
+ const DEFAULT_LOGIN_URL = 'https://www.aliwork.com/workPlatform';
25
+ const CDP_PORT = 19222;
26
+ const CDP_CONNECT_TIMEOUT_MS = 8000;
27
+ const LOGIN_POLL_INTERVAL_MS = 2000;
28
+ const LOGIN_TIMEOUT_MS = 600_000; // 10 分钟
29
+
30
+ // ── 本地 Chrome 路径查找 ──────────────────────────────
31
+
32
+ /**
33
+ * 按平台查找本地已安装的 Chrome / Chromium 可执行文件。
34
+ * @returns {string|null} 可执行文件路径,找不到返回 null
35
+ */
36
+ function findLocalChrome() {
37
+ const platform = os.platform();
38
+
39
+ const candidates = {
40
+ darwin: [
41
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
42
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
43
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
44
+ path.join(os.homedir(), 'Applications/Google Chrome.app/Contents/MacOS/Google Chrome'),
45
+ ],
46
+ win32: [
47
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
48
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
49
+ path.join(os.homedir(), 'AppData\\Local\\Google\\Chrome\\Application\\chrome.exe'),
50
+ ],
51
+ linux: [
52
+ '/usr/bin/google-chrome',
53
+ '/usr/bin/google-chrome-stable',
54
+ '/usr/bin/chromium-browser',
55
+ '/usr/bin/chromium',
56
+ '/snap/bin/chromium',
57
+ ],
58
+ };
59
+
60
+ const platformCandidates = candidates[platform] || candidates.linux;
61
+ for (const chromePath of platformCandidates) {
62
+ if (fs.existsSync(chromePath)) {
63
+ return chromePath;
64
+ }
65
+ }
66
+
67
+ // 尝试从 PATH 中查找
68
+ try {
69
+ const whichResult = execSync('which google-chrome || which chromium-browser || which chromium', {
70
+ encoding: 'utf-8',
71
+ stdio: ['pipe', 'pipe', 'pipe'],
72
+ }).trim().split('\n')[0];
73
+ if (whichResult && fs.existsSync(whichResult)) {
74
+ return whichResult;
75
+ }
76
+ } catch {
77
+ // 找不到,忽略
78
+ }
79
+
80
+ return null;
81
+ }
82
+
83
+ // ── CDP 连接工具 ──────────────────────────────────────
84
+
85
+ /**
86
+ * 等待 CDP 调试端口就绪(轮询 /json/version)。
87
+ * @param {number} port
88
+ * @param {number} timeoutMs
89
+ * @returns {Promise<boolean>}
90
+ */
91
+ function waitForCdpPort(port, timeoutMs) {
92
+ return new Promise((resolve) => {
93
+ const deadline = Date.now() + timeoutMs;
94
+ const http = require('http');
95
+
96
+ function poll() {
97
+ if (Date.now() > deadline) {
98
+ resolve(false);
99
+ return;
100
+ }
101
+ const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {
102
+ resolve(res.statusCode === 200);
103
+ });
104
+ req.on('error', () => {
105
+ setTimeout(poll, 300);
106
+ });
107
+ req.setTimeout(500, () => {
108
+ req.destroy();
109
+ setTimeout(poll, 300);
110
+ });
111
+ }
112
+
113
+ poll();
114
+ });
115
+ }
116
+
117
+ /**
118
+ * 启动本地 Chrome 并开启 CDP 调试端口。
119
+ * @param {string} chromePath - Chrome 可执行文件路径
120
+ * @param {string} loginUrl - 登录页 URL
121
+ * @returns {{ process: ChildProcess, userDataDir: string } | null}
122
+ */
123
+ function launchChromeWithCdp(chromePath, loginUrl) {
124
+ const userDataDir = path.join(os.tmpdir(), `yidacli-chrome-${Date.now()}`);
125
+ fs.mkdirSync(userDataDir, { recursive: true });
126
+
127
+ const args = [
128
+ `--remote-debugging-port=${CDP_PORT}`,
129
+ `--user-data-dir=${userDataDir}`,
130
+ '--no-first-run',
131
+ '--no-default-browser-check',
132
+ '--disable-default-apps',
133
+ loginUrl,
134
+ ];
135
+
136
+ try {
137
+ const chromeProcess = spawn(chromePath, args, {
138
+ detached: false,
139
+ stdio: 'ignore',
140
+ });
141
+ return { process: chromeProcess, userDataDir };
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+
147
+ // ── Cookie 轮询等待 ───────────────────────────────────
148
+
149
+ /**
150
+ * 通过 playwright CDP 连接,轮询等待 tianshu_csrf_token Cookie 出现。
151
+ * @param {object} context - playwright BrowserContext
152
+ * @returns {Promise<{ cookies: Array, baseUrl: string } | null>}
153
+ */
154
+ async function pollForLoginCookies(context) {
155
+ const deadline = Date.now() + LOGIN_TIMEOUT_MS;
156
+
157
+ while (Date.now() < deadline) {
158
+ await new Promise((resolve) => setTimeout(resolve, LOGIN_POLL_INTERVAL_MS));
159
+
160
+ const cookies = await context.cookies();
161
+ const csrfCookie = cookies.find((cookie) => cookie.name === 'tianshu_csrf_token' && cookie.value);
162
+
163
+ if (!csrfCookie) {
164
+ continue;
165
+ }
166
+
167
+ // 提取 baseUrl(优先用 yida_user_cookie 的 domain)
168
+ let baseUrl = 'https://www.aliwork.com';
169
+ const yidaCookie = cookies.find((cookie) => cookie.name === 'yida_user_cookie');
170
+ if (yidaCookie && yidaCookie.domain && yidaCookie.domain.includes('aliwork.com')) {
171
+ baseUrl = 'https://' + yidaCookie.domain.replace(/^\./, '');
172
+ } else if (csrfCookie.domain && csrfCookie.domain !== '.aliwork.com') {
173
+ baseUrl = 'https://' + csrfCookie.domain.replace(/^\./, '');
174
+ }
175
+
176
+ return { cookies, baseUrl };
177
+ }
178
+
179
+ return null;
180
+ }
181
+
182
+ // ── CDP 登录主流程 ────────────────────────────────────
183
+
184
+ /**
185
+ * 尝试通过 CDP 协议连接本地 Chrome 完成登录。
186
+ * @param {string} loginUrl
187
+ * @returns {Promise<object|null>} loginResult 或 null(失败时)
188
+ */
189
+ async function loginViaCdp(loginUrl) {
190
+ const chromePath = findLocalChrome();
191
+ if (!chromePath) {
192
+ console.error(' ℹ️ 未找到本地 Chrome,将使用 Playwright 内置 Chromium');
193
+ return null;
194
+ }
195
+
196
+ console.error(` 🌐 检测到本地 Chrome:${chromePath}`);
197
+ console.error(` 🔗 正在通过 CDP 协议连接(端口 ${CDP_PORT})...`);
198
+
199
+ // 检查端口是否已被占用(Chrome 已在运行)
200
+ const portAlreadyOpen = await waitForCdpPort(CDP_PORT, 500);
201
+ let chromeProcess = null;
202
+ let userDataDir = null;
203
+
204
+ if (!portAlreadyOpen) {
205
+ const launched = launchChromeWithCdp(chromePath, loginUrl);
206
+ if (!launched) {
207
+ console.error(' ⚠️ Chrome 启动失败,降级为 Playwright 模式');
208
+ return null;
209
+ }
210
+ chromeProcess = launched.process;
211
+ userDataDir = launched.userDataDir;
212
+
213
+ const ready = await waitForCdpPort(CDP_PORT, CDP_CONNECT_TIMEOUT_MS);
214
+ if (!ready) {
215
+ console.error(' ⚠️ CDP 端口未就绪,降级为 Playwright 模式');
216
+ if (chromeProcess) { chromeProcess.kill(); }
217
+ return null;
218
+ }
219
+ } else {
220
+ console.error(' ℹ️ 检测到 Chrome 已在运行,直接连接');
221
+ }
222
+
223
+ let playwright;
224
+ try {
225
+ playwright = require('playwright');
226
+ } catch {
227
+ console.error(' ⚠️ playwright 未安装,降级为扫码模式');
228
+ if (chromeProcess) { chromeProcess.kill(); }
229
+ return null;
230
+ }
231
+
232
+ let browser = null;
233
+ try {
234
+ browser = await playwright.chromium.connectOverCDP(`http://127.0.0.1:${CDP_PORT}`);
235
+ const contexts = browser.contexts();
236
+ const context = contexts.length > 0 ? contexts[0] : await browser.newContext();
237
+
238
+ // 导航到登录页(如果当前页面不是登录页)
239
+ const pages = context.pages();
240
+ const loginPage = pages.length > 0 ? pages[0] : await context.newPage();
241
+ const currentUrl = loginPage.url();
242
+ if (!currentUrl.includes('aliwork.com')) {
243
+ await loginPage.goto(loginUrl, { timeout: 30000 });
244
+ }
245
+
246
+ console.error(' ⏳ 请在浏览器中完成宜搭登录(最多等待 10 分钟)...');
247
+
248
+ const result = await pollForLoginCookies(context);
249
+ if (!result) {
250
+ console.error(' ⏰ 登录超时(10 分钟),请重试');
251
+ return null;
252
+ }
253
+
254
+ console.error(' ✅ CDP 登录成功!');
255
+ return result;
256
+ } catch (error) {
257
+ console.error(` ⚠️ CDP 连接失败(${error.message}),降级为 Playwright 模式`);
258
+ return null;
259
+ } finally {
260
+ if (browser) {
261
+ try { await browser.disconnect(); } catch { /* ignore */ }
262
+ }
263
+ if (chromeProcess) {
264
+ try { chromeProcess.kill(); } catch { /* ignore */ }
265
+ }
266
+ if (userDataDir) {
267
+ try { fs.rmSync(userDataDir, { recursive: true, force: true }); } catch { /* ignore */ }
268
+ }
269
+ }
270
+ }
271
+
272
+ // ── 对外入口 ──────────────────────────────────────────
273
+
274
+ /**
275
+ * 登录主入口:优先 CDP 连接本地 Chrome,失败时降级为 Playwright 扫码登录。
276
+ *
277
+ * @param {object} [options]
278
+ * @param {string} [options.loginUrl] - 登录页 URL,默认读取 config.json
279
+ * @returns {object} loginResult { csrf_token, corp_id, user_id, base_url, cookies }
280
+ */
281
+ async function loginWithCdpOrPlaywright(options = {}) {
282
+ const loginUrl = options.loginUrl || DEFAULT_LOGIN_URL;
283
+
284
+ console.error('\n🔐 正在打开浏览器,请扫码登录...');
285
+
286
+ // 策略 1:CDP 连接本地 Chrome
287
+ const cdpResult = await loginViaCdp(loginUrl);
288
+ if (cdpResult) {
289
+ const { csrfToken, corpId, userId } = extractInfoFromCookies(cdpResult.cookies);
290
+ if (!csrfToken) {
291
+ console.error(' ⚠️ CDP 登录后未找到 csrf_token,降级为 Playwright 模式');
292
+ } else {
293
+ saveCookieCache(cdpResult.cookies, cdpResult.baseUrl);
294
+ console.error(` 🔑 csrf_token: ${csrfToken.slice(0, 16)}...`);
295
+ if (corpId) { console.error(` 🏢 组织 ID: ${corpId}`); }
296
+ return {
297
+ csrf_token: csrfToken,
298
+ corp_id: corpId,
299
+ user_id: userId,
300
+ base_url: cdpResult.baseUrl,
301
+ cookies: cdpResult.cookies,
302
+ };
303
+ }
304
+ }
305
+
306
+ // 策略 2:降级为 Playwright 扫码登录
307
+ console.error(' 📱 使用 Playwright 内置 Chromium 登录...');
308
+ return interactiveLogin();
309
+ }
310
+
311
+ module.exports = {
312
+ loginWithCdpOrPlaywright,
313
+ findLocalChrome,
314
+ launchChromeWithCdp,
315
+ };
package/lib/auth/login.js CHANGED
@@ -1,11 +1,16 @@
1
1
  /**
2
- * login.js - 宜搭登录态管理(Playwright 扫码登录)
2
+ * login.js - 宜搭登录态管理
3
+ *
4
+ * 登录策略(按优先级):
5
+ * 1. 本地 Cookie 缓存(最快)
6
+ * 2. CDP 协议连接本地 Chrome(无需下载 Chromium)
7
+ * 3. Playwright 内置 Chromium 扫码登录(兜底)
3
8
  *
4
9
  * 导出函数:
5
- * ensureLogin() - 确保有效登录态(优先缓存,否则扫码)
10
+ * ensureLogin() - 确保有效登录态(优先缓存,否则触发浏览器登录)
6
11
  * checkLoginOnly() - 仅检查登录态,不触发登录
7
12
  * refreshCsrfFromCache() - 从缓存 Cookie 重新提取 csrf_token
8
- * interactiveLogin() - 打开浏览器扫码登录(需要 playwright)
13
+ * interactiveLogin() - 打开浏览器扫码登录(Playwright 模式,作为兜底)
9
14
  * saveCookieCache() - 保存 Cookie 到本地缓存(供 qr-login.js 使用)
10
15
  * logout() - 退出登录,清空 Cookie 缓存
11
16
  */
@@ -151,7 +156,9 @@ function ensureLogin() {
151
156
  }
152
157
  }
153
158
 
154
- return interactiveLogin();
159
+ // 优先 CDP 连接本地 Chrome,失败时降级为 Playwright 扫码登录
160
+ const { loginWithCdpOrPlaywright } = require('./login-cdp');
161
+ return loginWithCdpOrPlaywright({ loginUrl: loadConfig().loginUrl });
155
162
  }
156
163
 
157
164
  // ── Playwright 扫码登录 ───────────────────────────────
@@ -229,8 +229,8 @@ class EnvironmentChecker {
229
229
  passed: false,
230
230
  severity: Severity.ERROR,
231
231
  message: `Chromium 未安装(期望路径:${chromiumPath || '未知'})`,
232
- fixType: FixType.COMMAND,
233
- fixCommand: 'npx playwright install chromium',
232
+ fixType: FixType.AUTO,
233
+ fixAction: 'install-chromium',
234
234
  };
235
235
  } catch {
236
236
  // playwright API 不可用,降级为仅检查包安装
@@ -742,6 +742,28 @@ class FixEngine {
742
742
  };
743
743
  }
744
744
 
745
+ case 'install-chromium': {
746
+ try {
747
+ const { execSync } = require('child_process');
748
+ console.log('\n 🌐 正在安装 Chromium 浏览器(请稍候)...');
749
+ execSync('npx playwright install chromium', {
750
+ stdio: 'inherit',
751
+ timeout: 300_000,
752
+ });
753
+ return {
754
+ id: issue.id,
755
+ fixed: true,
756
+ message: 'Chromium 安装完成',
757
+ };
758
+ } catch (error) {
759
+ return {
760
+ id: issue.id,
761
+ fixed: false,
762
+ message: `Chromium 安装失败,请手动运行:npx playwright install chromium(${error.message})`,
763
+ };
764
+ }
765
+ }
766
+
745
767
  case 'init-project': {
746
768
  try {
747
769
  const copy = require('./copy');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.04.02-beta.4",
3
+ "version": "2026.04.02-beta.6",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "./bin/yida.js",
@@ -4,7 +4,7 @@ description: >
4
4
  宜搭低代码平台 AI 开发入口。一句话生成完整应用:创建应用、表单设计、自定义页面、流程配置、数据管理。
5
5
  当用户提到"宜搭"、"yida"、"低代码"、"创建应用"、"创建表单"、"发布页面"、"搭建"、"系统"、"应用"时触发。
6
6
  metadata:
7
- version: 2026.04.02-beta.4
7
+ version: 2026.04.02-beta.6
8
8
  ---
9
9
 
10
10
  # 宜搭 AI 应用开发指南