openyida 0.1.2 → 1.0.0-beta.0
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/README.md +68 -38
- package/bin/yida.js +164 -761
- package/lib/babel-transform/index.js +244 -0
- package/lib/babel-transform/jsx-utils.js +89 -0
- package/lib/check-update.js +72 -0
- package/lib/copy.js +258 -0
- package/lib/create-app.js +174 -0
- package/lib/create-form.js +2244 -0
- package/lib/create-page.js +89 -0
- package/lib/env.js +164 -0
- package/lib/get-page-config.js +102 -0
- package/lib/get-schema.js +76 -0
- package/lib/login.js +323 -0
- package/lib/publish.js +610 -0
- package/lib/save-share-config.js +268 -0
- package/lib/update-form-config.js +237 -0
- package/lib/utils.js +443 -0
- package/lib/verify-short-url.js +279 -0
- package/package.json +20 -7
- package/project/.cache/demo-schema.json +2353 -0
- package/project/pages/src/demo-birthday-game.js +833 -0
- package/project/pages/src/demo-future-vision-2026.js +1102 -0
- package/project/pages/src/demo-salary-calculator.js +904 -0
- package/project/prd/demo-birthday-game.md +39 -0
- package/project/prd/demo-future-vision-2026.md +78 -0
- package/project/prd/demo-salary-calculator.md +101 -0
- package/scripts/postinstall.js +114 -0
- package/yida-skills/SKILL.md +273 -0
- package/yida-skills/reference/association-form-field.md +469 -0
- package/yida-skills/reference/employee-field.md +17 -0
- package/yida-skills/reference/model-api.md +73 -0
- package/yida-skills/reference/serial-number-field.md +132 -0
- package/yida-skills/reference/yida-api.md +1208 -0
- package/yida-skills/skills/yida-app/SKILL.md +394 -0
- package/yida-skills/skills/yida-create-app/SKILL.md +158 -0
- package/yida-skills/skills/yida-create-form-page/SKILL.md +598 -0
- package/yida-skills/skills/yida-create-page/SKILL.md +103 -0
- package/yida-skills/skills/yida-custom-page/SKILL.md +533 -0
- package/yida-skills/skills/yida-get-schema/SKILL.md +90 -0
- package/yida-skills/skills/yida-login/SKILL.md +200 -0
- package/yida-skills/skills/yida-logout/SKILL.md +58 -0
- package/yida-skills/skills/yida-page-config/SKILL.md +261 -0
- package/yida-skills/skills/yida-publish-page/SKILL.md +113 -0
- package/.eslintrc.json +0 -25
- package/.github/workflows/ci.yml +0 -123
- package/.github/workflows/publish.yml +0 -105
- package/.github/workflows/update-contributors.yml +0 -151
- package/.openclaw/skills/yida-issue/SKILL.md +0 -27
- package/.openclaw/skills/yida-issue/scripts/create-issue.js +0 -317
- package/CLAUDE.md +0 -168
- package/CONTRIBUTING.md +0 -59
- package/install-skills.ps1 +0 -162
- package/install-skills.sh +0 -175
- package/pages/dist/.gitkeep +0 -0
- package/pages/src/.gitkeep +0 -0
- package/prd/salary-calculator.md +0 -15
- package/tests/cli.test.js +0 -930
- package/tests/install.test.js +0 -277
- package/tests/yida-issue.test.js +0 -314
- /package/{config.json → project/config.json} +0 -0
- /package/{.cache → project/pages/dist}/.gitkeep +0 -0
package/lib/login.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* login.js - 宜搭登录态管理(Playwright 扫码登录)
|
|
3
|
+
*
|
|
4
|
+
* 导出函数:
|
|
5
|
+
* ensureLogin() - 确保有效登录态(优先缓存,否则扫码)
|
|
6
|
+
* checkLoginOnly() - 仅检查登录态,不触发登录
|
|
7
|
+
* refreshCsrfFromCache() - 从缓存 Cookie 重新提取 csrf_token
|
|
8
|
+
* interactiveLogin() - 打开浏览器扫码登录(需要 playwright)
|
|
9
|
+
* logout() - 退出登录,清空 Cookie 缓存
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const os = require("os");
|
|
17
|
+
const { execSync } = require("child_process");
|
|
18
|
+
const { findProjectRoot, extractInfoFromCookies, loadCookieData, resolveBaseUrl } = require("./utils");
|
|
19
|
+
|
|
20
|
+
const DEFAULT_BASE_URL = "https://www.aliwork.com";
|
|
21
|
+
const DEFAULT_LOGIN_URL = "https://www.aliwork.com/workPlatform";
|
|
22
|
+
|
|
23
|
+
// ── 配置读取 ──────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function loadConfig() {
|
|
26
|
+
const projectRoot = findProjectRoot();
|
|
27
|
+
const configPath = path.join(projectRoot, "config.json");
|
|
28
|
+
if (fs.existsSync(configPath)) {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
31
|
+
} catch {
|
|
32
|
+
// ignore
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
loginUrl: DEFAULT_LOGIN_URL,
|
|
37
|
+
defaultBaseUrl: DEFAULT_BASE_URL,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Cookie 持久化 ─────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
function saveCookieCache(cookies, baseUrl) {
|
|
44
|
+
const projectRoot = findProjectRoot();
|
|
45
|
+
const cacheDir = path.join(projectRoot, ".cache");
|
|
46
|
+
const cookieFile = path.join(cacheDir, "cookies.json");
|
|
47
|
+
|
|
48
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
49
|
+
fs.writeFileSync(cookieFile, JSON.stringify({ cookies, base_url: baseUrl }, null, 2), "utf-8");
|
|
50
|
+
console.error(` Cookie 已保存到 ${cookieFile}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── 仅检查登录态 ──────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 仅检查登录态,不触发登录。
|
|
57
|
+
* @returns {object} 含 can_auto_use 字段的状态对象
|
|
58
|
+
*/
|
|
59
|
+
function checkLoginOnly() {
|
|
60
|
+
const cookieData = loadCookieData();
|
|
61
|
+
|
|
62
|
+
if (!cookieData || !cookieData.cookies) {
|
|
63
|
+
return {
|
|
64
|
+
status: "not_logged_in",
|
|
65
|
+
can_auto_use: false,
|
|
66
|
+
message: "本地无 Cookie 缓存,需要扫码登录",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(cookieData.cookies);
|
|
71
|
+
|
|
72
|
+
if (!csrfToken) {
|
|
73
|
+
return {
|
|
74
|
+
status: "not_logged_in",
|
|
75
|
+
can_auto_use: false,
|
|
76
|
+
message: "Cookie 中无 tianshu_csrf_token,需要重新登录",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const baseUrl = resolveBaseUrl(cookieData);
|
|
81
|
+
return {
|
|
82
|
+
status: "ok",
|
|
83
|
+
can_auto_use: true,
|
|
84
|
+
csrf_token: csrfToken,
|
|
85
|
+
corp_id: corpId,
|
|
86
|
+
user_id: userId,
|
|
87
|
+
base_url: baseUrl,
|
|
88
|
+
cookies: cookieData.cookies,
|
|
89
|
+
message: `✅ 已有有效登录态,可直接使用\n 组织: ${corpId}\n 用户: ${userId}\n 域名: ${baseUrl}`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── 从缓存刷新 csrf_token ─────────────────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 从本地缓存 Cookie 中重新提取 csrf_token,无需重新扫码。
|
|
97
|
+
* @returns {object} loginResult
|
|
98
|
+
*/
|
|
99
|
+
function refreshCsrfFromCache() {
|
|
100
|
+
const cookieData = loadCookieData();
|
|
101
|
+
|
|
102
|
+
if (!cookieData || !cookieData.cookies) {
|
|
103
|
+
console.error(" ❌ 本地无有效 Cookie,无法刷新,需要重新登录。");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(cookieData.cookies);
|
|
108
|
+
|
|
109
|
+
if (!csrfToken) {
|
|
110
|
+
console.error(" ❌ Cookie 中无 tianshu_csrf_token,需要重新登录。");
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const baseUrl = resolveBaseUrl(cookieData);
|
|
115
|
+
console.error(` ✅ csrf_token 提取成功: ${csrfToken.slice(0, 16)}...`);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
csrf_token: csrfToken,
|
|
119
|
+
corp_id: corpId,
|
|
120
|
+
user_id: userId,
|
|
121
|
+
base_url: baseUrl,
|
|
122
|
+
cookies: cookieData.cookies,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── 确保登录态(优先缓存) ────────────────────────────
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 确保拥有有效的登录态。优先从本地缓存 Cookie 中提取,否则触发扫码登录。
|
|
130
|
+
* @returns {object} loginResult
|
|
131
|
+
*/
|
|
132
|
+
function ensureLogin() {
|
|
133
|
+
const cookieData = loadCookieData();
|
|
134
|
+
|
|
135
|
+
if (cookieData && cookieData.cookies) {
|
|
136
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(cookieData.cookies);
|
|
137
|
+
if (csrfToken) {
|
|
138
|
+
console.error("🔍 检测到本地 Cookie,直接使用...");
|
|
139
|
+
console.error(` ✅ csrf_token: ${csrfToken.slice(0, 16)}...`);
|
|
140
|
+
if (corpId) console.error(` ✅ corpId: ${corpId}`);
|
|
141
|
+
const baseUrl = resolveBaseUrl(cookieData);
|
|
142
|
+
return {
|
|
143
|
+
csrf_token: csrfToken,
|
|
144
|
+
corp_id: corpId,
|
|
145
|
+
user_id: userId,
|
|
146
|
+
base_url: baseUrl,
|
|
147
|
+
cookies: cookieData.cookies,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return interactiveLogin();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Playwright 扫码登录 ───────────────────────────────
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 获取 playwright 模块的绝对路径,用于临时脚本中 require。
|
|
159
|
+
*
|
|
160
|
+
* 查找策略(按优先级):
|
|
161
|
+
* 1. yidacli 包自身的 node_modules/playwright(最可靠)
|
|
162
|
+
* 2. require.resolve 标准解析
|
|
163
|
+
* 3. 全局 npm root 路径
|
|
164
|
+
*
|
|
165
|
+
* @returns {string|null} playwright index.js 的绝对路径
|
|
166
|
+
*/
|
|
167
|
+
function getPlaywrightPath() {
|
|
168
|
+
// 策略1:从 __dirname 向上找到 yidacli 包根目录下的 node_modules/playwright
|
|
169
|
+
// __dirname 是 dist/ 或 src/,向上一级是包根目录
|
|
170
|
+
const candidates = [
|
|
171
|
+
path.join(__dirname, "..", "node_modules", "playwright", "index.js"),
|
|
172
|
+
path.join(__dirname, "..", "..", "node_modules", "playwright", "index.js"),
|
|
173
|
+
];
|
|
174
|
+
for (const candidate of candidates) {
|
|
175
|
+
if (fs.existsSync(candidate)) {
|
|
176
|
+
return candidate;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 策略2:require.resolve 标准解析
|
|
181
|
+
try {
|
|
182
|
+
return require.resolve("playwright");
|
|
183
|
+
} catch {
|
|
184
|
+
// ignore
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 策略3:全局 npm root 路径
|
|
188
|
+
try {
|
|
189
|
+
const globalRoot = execSync("npm root -g", { encoding: "utf-8" }).trim();
|
|
190
|
+
const globalPlaywright = path.join(globalRoot, "playwright", "index.js");
|
|
191
|
+
if (fs.existsSync(globalPlaywright)) {
|
|
192
|
+
return globalPlaywright;
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
// ignore
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 打开有头浏览器让用户扫码登录(需要安装 playwright)。
|
|
203
|
+
* @returns {object} loginResult
|
|
204
|
+
*/
|
|
205
|
+
function interactiveLogin() {
|
|
206
|
+
const config = loadConfig();
|
|
207
|
+
const loginUrl = config.loginUrl || DEFAULT_LOGIN_URL;
|
|
208
|
+
|
|
209
|
+
// 检查 playwright 是否可用
|
|
210
|
+
const playwrightPath = getPlaywrightPath();
|
|
211
|
+
if (!playwrightPath) {
|
|
212
|
+
console.error("\n❌ 未找到 playwright 模块,请先安装:");
|
|
213
|
+
console.error(" npm install -g playwright");
|
|
214
|
+
console.error(" npx playwright install chromium");
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.error("\n🔐 正在打开浏览器,请扫码登录...");
|
|
219
|
+
console.error(` 登录地址: ${loginUrl}`);
|
|
220
|
+
|
|
221
|
+
// 通过子进程运行异步 playwright 逻辑,避免顶层 await 兼容性问题
|
|
222
|
+
// 使用 require.resolve 获取的绝对路径,确保临时脚本能找到 playwright
|
|
223
|
+
const scriptContent = `
|
|
224
|
+
const playwright = require(${JSON.stringify(playwrightPath)});
|
|
225
|
+
const { chromium } = playwright;
|
|
226
|
+
const { URL } = require('url');
|
|
227
|
+
|
|
228
|
+
(async () => {
|
|
229
|
+
const browser = await chromium.launch({ headless: false });
|
|
230
|
+
const context = await browser.newContext();
|
|
231
|
+
const page = await context.newPage();
|
|
232
|
+
await page.goto(${JSON.stringify(loginUrl)}, { timeout: 120000 });
|
|
233
|
+
|
|
234
|
+
console.error(' 等待登录完成(最长等待 10 分钟)...');
|
|
235
|
+
try {
|
|
236
|
+
await page.waitForURL('**/workPlatform**', { timeout: 600000 });
|
|
237
|
+
} catch {
|
|
238
|
+
console.error(' ⏰ 登录超时(10分钟),请重试。');
|
|
239
|
+
await browser.close();
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
await page.waitForLoadState('networkidle');
|
|
244
|
+
console.error(' ✅ 登录成功!');
|
|
245
|
+
|
|
246
|
+
const postLoginParsed = new URL(page.url());
|
|
247
|
+
const baseUrl = postLoginParsed.origin;
|
|
248
|
+
const cookies = await context.cookies();
|
|
249
|
+
await browser.close();
|
|
250
|
+
|
|
251
|
+
console.log(JSON.stringify({ cookies, base_url: baseUrl }));
|
|
252
|
+
})();
|
|
253
|
+
`;
|
|
254
|
+
|
|
255
|
+
const tmpScript = path.join(os.tmpdir(), `yidacli-login-${Date.now()}.js`);
|
|
256
|
+
fs.writeFileSync(tmpScript, scriptContent, "utf-8");
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const stdout = execSync(`node "${tmpScript}"`, {
|
|
260
|
+
encoding: "utf-8",
|
|
261
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
262
|
+
timeout: 660000,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const lines = stdout.trim().split("\n");
|
|
266
|
+
const jsonLine = lines[lines.length - 1];
|
|
267
|
+
const result = JSON.parse(jsonLine);
|
|
268
|
+
|
|
269
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(result.cookies);
|
|
270
|
+
if (!csrfToken) {
|
|
271
|
+
console.error(" ❌ 登录成功但 Cookie 中无 tianshu_csrf_token,请重试。");
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
saveCookieCache(result.cookies, result.base_url);
|
|
276
|
+
|
|
277
|
+
console.error(` ✅ csrf_token: ${csrfToken.slice(0, 16)}...`);
|
|
278
|
+
if (corpId) console.error(` ✅ corpId: ${corpId}`);
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
csrf_token: csrfToken,
|
|
282
|
+
corp_id: corpId,
|
|
283
|
+
user_id: userId,
|
|
284
|
+
base_url: result.base_url,
|
|
285
|
+
cookies: result.cookies,
|
|
286
|
+
};
|
|
287
|
+
} finally {
|
|
288
|
+
try { fs.unlinkSync(tmpScript); } catch { /* ignore */ }
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── 退出登录 ──────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 退出登录:清空项目级 Cookie 文件。
|
|
296
|
+
*/
|
|
297
|
+
function logout() {
|
|
298
|
+
console.error("=".repeat(50));
|
|
299
|
+
console.error(" yidacli logout - 宜搭退出登录工具");
|
|
300
|
+
console.error("=".repeat(50));
|
|
301
|
+
|
|
302
|
+
const projectRoot = findProjectRoot();
|
|
303
|
+
const projectCookieFile = path.join(projectRoot, ".cache", "cookies.json");
|
|
304
|
+
console.error(`\n Cookie 文件: ${projectCookieFile}`);
|
|
305
|
+
|
|
306
|
+
if (fs.existsSync(projectCookieFile)) {
|
|
307
|
+
fs.writeFileSync(projectCookieFile, "", "utf-8");
|
|
308
|
+
console.error(" ✅ 已清空 Cookie,登录态已失效。");
|
|
309
|
+
console.error(" 下次调用 yidacli login 时将重新触发扫码登录。");
|
|
310
|
+
} else {
|
|
311
|
+
console.error(" ℹ️ Cookie 文件不存在,无需清空。");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.error("=".repeat(50));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
module.exports = {
|
|
318
|
+
ensureLogin,
|
|
319
|
+
checkLoginOnly,
|
|
320
|
+
refreshCsrfFromCache,
|
|
321
|
+
interactiveLogin,
|
|
322
|
+
logout,
|
|
323
|
+
};
|