tracup-bug 1.0.0 → 1.0.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/README.md CHANGED
@@ -36,7 +36,10 @@ tracup login
36
36
  ```
37
37
 
38
38
  会弹出一个 Chrome 窗口,正常登录 Tracup(账密或扫码都行)。
39
- 登录成功它自己检测到并关闭,终端打印 `✓ 登录态已保存`。
39
+ 登录成功它自己检测到并关闭,终端打印 `✓ 登录成功`。
40
+
41
+ - 如果会话还没过期,`tracup login` 会直接告诉你“无需重新登录”,不弹窗口。
42
+ 要换号登录:`tracup login --force`。
40
43
 
41
44
  - 登录态存在你电脑的 `~/.cache/tracup/profile`,**通常能用数周**,过期重跑一次 `tracup login`。
42
45
  - 这等价于你的 Tracup 会话:**别发给别人、别提交到 git**。
package/SKILL.md CHANGED
@@ -38,7 +38,11 @@ node $BIN/tracup_web.mjs status
38
38
 
39
39
  登录态存在 `~/.cache/tracup/profile`(持久化浏览器用户目录),通常可维持数周。
40
40
 
41
+ `login` 会先无头预检:会话仍有效就直接返回不弹窗口;失效才弹浏览器让你重登。
42
+ 要切换账号:`login --force`(跳过预检、清掉旧 cookie)。
43
+
41
44
  `status` 同时做自检:playwright 能否加载、本机 Chrome 是否可用、配置文件是否存在,一行一行打印,排错先看它。
45
+ (`status` 的“有效”结论经过真接口探活,过期 cookie 骗不了它。)
42
46
 
43
47
  ## Step 1 · 确认项目与字段名
44
48
 
@@ -9,7 +9,8 @@
9
9
  * ✅ 浏览器会话 + POST /api2/issue/create(前端自己用的接口,字段最全)
10
10
  *
11
11
  * 用法:node tracup_web.mjs <login|status|projects|create|delete|capture|api>
12
- * login 打开有头浏览器,你手动登录一次(登录态存 ~/.cache/tracup/profile,可用数周)
12
+ * login [--force] 打开有头浏览器,你手动登录一次(登录态存 ~/.cache/tracup/profile,可用数周)
13
+ * 已有有效会话会直接返回不弹窗口;--force 强制清掉重登(切换账号用)
13
14
  * status 检查登录态 + 自检(playwright / 浏览器 / 配置是否就位)
14
15
  * projects 列出项目及其类型、状态、模块、成员名
15
16
  * create 建单(--title/--body/--priority/--type/--module/--assignee/--status/--dry-run/--json)
@@ -35,11 +36,13 @@ const SKILL_DIR = path.resolve(__dirname, "..");
35
36
  const CONFIG_DIR = path.join(os.homedir(), ".config", "tracup");
36
37
  const RUNTIME_DIR = path.join(CONFIG_DIR, "runtime");
37
38
  const CACHE_DIR = path.join(os.homedir(), ".cache", "tracup");
38
- const PROFILE_DIR = path.join(CACHE_DIR, "profile");
39
+ const PROFILE_DIR = process.env.TRACUP_PROFILE_DIR || path.join(CACHE_DIR, "profile");
39
40
  const WEB_CONFIG_PATH = path.join(CONFIG_DIR, "web.json");
40
41
  const APP_URL = "https://app.tracup.com/";
41
42
  const API_ORIGIN = "https://www.tracup.com";
42
43
  const LOGIN_HINT = /login|signin|sign-in|passport|auth/i;
44
+ // 等待用户完成登录的最长毫秒数(默认 5 分钟;自动化/测试可调小)
45
+ const LOGIN_TIMEOUT_MS = Number(process.env.TRACUP_LOGIN_TIMEOUT_MS || 300000);
43
46
 
44
47
  const require = createRequire(import.meta.url);
45
48
 
@@ -119,8 +122,18 @@ function waitEnter(promptText) {
119
122
  });
120
123
  }
121
124
 
122
- /** 轮询判断是否已进入应用(不是登录页且有会话 cookie)。 */
123
- async function waitForLogin(page, timeoutMs = 300000) {
125
+ /** 调一次需要登录态的接口:200 才算真登录(只看 URL/cookie 会被过期 cookie 骗)。 */
126
+ async function sessionIsValid(ctx) {
127
+ try {
128
+ const resp = await ctx.request.get(`${API_ORIGIN}/api2/workspace/config`);
129
+ return resp.status() === 200;
130
+ } catch {
131
+ return false;
132
+ }
133
+ }
134
+
135
+ /** 轮询等待用户完成登录:URL 已进应用 _且_ 接口探活通过才算数。 */
136
+ async function waitForLogin(page, ctx, timeoutMs = LOGIN_TIMEOUT_MS) {
124
137
  const started = Date.now();
125
138
  while (Date.now() - started < timeoutMs) {
126
139
  await page.waitForTimeout(1500);
@@ -131,36 +144,62 @@ async function waitForLogin(page, timeoutMs = 300000) {
131
144
  continue;
132
145
  }
133
146
  if (!url || LOGIN_HINT.test(url.replace(APP_URL, ""))) continue;
134
- const cookies = await page.context().cookies();
135
- const hasSession = cookies.some((c) => c.value && c.value.length > 20 && !/^(1|0|true|false)$/i.test(c.value));
136
- if (hasSession) return "auto";
147
+ if (await sessionIsValid(ctx)) return "auto";
137
148
  }
138
149
  return "timeout";
139
150
  }
140
151
 
141
- async function cmdLogin() {
152
+ async function cmdLogin(args) {
142
153
  const headless = process.env.TRACUP_WEB_HEADLESS === "1";
154
+ const force = args.force || args.fresh;
155
+
156
+ // 预检:已有会话且接口探活通过就直接返回,不弹浏览器
157
+ //(否则过期 cookie 会让检测瞬间通过、窗口一闪而过,还误报“已保存”)。
158
+ if (!force) {
159
+ const pre = await openContext({ headless: true });
160
+ try {
161
+ const prePage = pre.pages()[0] || (await pre.newPage());
162
+ await prePage.goto(APP_URL, { waitUntil: "domcontentloaded" });
163
+ await prePage.waitForTimeout(2000);
164
+ if (await sessionIsValid(pre)) {
165
+ log("✓ 登录态仍有效,无需重新登录。");
166
+ log(" (要切换账号请加 --force:tracup login --force)");
167
+ await pre.close();
168
+ return;
169
+ }
170
+ log("· 已有会话已失效,清掉过期 cookie 后请重新登录。");
171
+ } catch {
172
+ /* 预检失败就走正常登录流程 */
173
+ }
174
+ await pre.close();
175
+ }
176
+
143
177
  const ctx = await openContext({ headless });
178
+ await ctx.clearCookies();
144
179
  const page = ctx.pages()[0] || (await ctx.newPage());
145
180
  await page.goto(APP_URL, { waitUntil: "domcontentloaded" });
146
181
 
147
182
  log("浏览器已打开,请在窗口里完成 Tracup 登录(支持扫码/账密)。");
148
183
  log("登录成功后会自动继续;也可以回到这里按回车。\n");
149
184
 
150
- const result = process.stdin.isTTY
151
- ? await Promise.race([waitForLogin(page), waitEnter("登录完成后按回车继续… ")])
152
- : await waitForLogin(page);
185
+ let result = process.stdin.isTTY
186
+ ? await Promise.race([waitForLogin(page, ctx), waitEnter("登录完成后按回车继续… ")])
187
+ : await waitForLogin(page, ctx);
188
+ // 回车不代表真登上:探活没过就继续等,而不是误报成功
189
+ if (result === "enter" && !(await sessionIsValid(ctx))) {
190
+ log("✗ 按了回车但接口探活没通过(登录可能没完成),继续自动等待…");
191
+ result = await waitForLogin(page, ctx);
192
+ }
153
193
  if (result === "timeout") {
154
- log("⚠️ 5 分钟内没检测到登录态,已放弃。请重跑 login。");
194
+ log(`⚠️ ${Math.round(LOGIN_TIMEOUT_MS / 1000)} 秒内没检测到登录态,已放弃。请重跑 login。`);
155
195
  await ctx.close();
156
196
  process.exit(1);
157
197
  }
158
198
 
159
199
  await page.waitForTimeout(1000);
160
- log(`✓ 登录态已保存到 ${PROFILE_DIR}`);
200
+ log(`✓ 登录成功,会话已保存到 ${PROFILE_DIR}`);
161
201
  log(` 当前页面:${page.url()}`);
162
- if (!headless) await ctx.close();
163
- else await ctx.close();
202
+ await ctx.close();
164
203
  }
165
204
 
166
205
  /** 自检:依赖 / 配置 / 登录态。排错时先跑这个,比猜快。 */
@@ -193,8 +232,9 @@ async function cmdStatus() {
193
232
  await page.goto(APP_URL, { waitUntil: "domcontentloaded" });
194
233
  await page.waitForTimeout(3000);
195
234
  const url = page.url();
196
- const ok = !LOGIN_HINT.test(url.replace(APP_URL, ""));
197
- log(ok ? `✓ 登录态有效(${url})` : `✗ 登录态已失效(${url})`);
235
+ // URL 通过只是必要条件:过期 cookie 下 SPA 壳照样落在 /,必须再调一次真接口
236
+ const ok = !LOGIN_HINT.test(url.replace(APP_URL, "")) && (await sessionIsValid(ctx));
237
+ log(ok ? `✓ 登录态有效(${url})` : `✗ 登录态已失效${url ? `(${url})` : ""},请重跑 login`);
198
238
  await ctx.close();
199
239
 
200
240
  if (!ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracup-bug",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Tracup 缺陷提单 CLI(免费版无开放 API,走浏览器登录态调网页同款内部接口)",
5
5
  "type": "module",
6
6
  "license": "MIT",