@glittering0717/ai-browser 2.1.7 → 2.1.9

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": "@glittering0717/ai-browser",
3
- "version": "2.1.7",
3
+ "version": "2.1.9",
4
4
  "description": "AI-native browser — renders pixels for humans, structured semantic-tree API for agents. No screenshots, no OCR, no coordinate guessing.",
5
5
  "main": "src/main/index.js",
6
6
  "bin": {
@@ -80,7 +80,8 @@
80
80
  "build": "node scripts/build_dmg.cjs",
81
81
  "verify:build": "node scripts/verify_build.cjs",
82
82
  "install:app": "node scripts/install_app.cjs",
83
- "dialog": "node e2e/dialog_ws.cjs"
83
+ "dialog": "node e2e/dialog_ws.cjs",
84
+ "verify:app": "node e2e/shipped_ws.cjs"
84
85
  },
85
86
  "dependencies": {
86
87
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -27,7 +27,11 @@ const args = process.argv.slice(2);
27
27
  const dry = args.includes('--dry');
28
28
  const bumpKind = args.find((a) => ['patch', 'minor', 'major'].includes(a)) || 'patch';
29
29
  const notesIdx = args.indexOf('--notes');
30
- const notesFile = notesIdx >= 0 ? args[notesIdx + 1] : null;
30
+ const notesArg = notesIdx >= 0 ? args[notesIdx + 1] : null;
31
+ // --notes 既接受仓库内相对路径,也接受绝对路径(写手稿时通常放在 /tmp)。
32
+ // path.join(ROOT, '/tmp/x') 会拼出 ROOT/tmp/x 这种不存在的路径,所以先判绝对。
33
+ const notesPath = notesArg ? (path.isAbsolute(notesArg) ? notesArg : path.join(ROOT, notesArg)) : null;
34
+ const notesFile = notesPath && fs.existsSync(notesPath) ? notesPath : null;
31
35
  // 全量测试 4 分钟起,发版时默认只跑"会拦住错误发版"的那几个:
32
36
  // 版本一致性、导入完整性、schema 预算。其余靠 CI / 平时。
33
37
  const fullTests = args.includes('--full-test');
@@ -111,7 +115,7 @@ mark('版本号');
111
115
  step(fullTests ? '全量测试' : '发版闸门测试');
112
116
  const testFiles = fullTests
113
117
  ? []
114
- : ['tests/test_version.js', 'tests/test_imports.js', 'tests/test_api_budget.js', 'tests/test_mcp_tools.js'];
118
+ : ['tests/test_version.js', 'tests/test_imports.js', 'tests/test_api_budget.js', 'tests/test_mcp_tools.js', 'tests/test_release_loop.js'];
115
119
  const t = spawnSync('npx', ['vitest', 'run', ...testFiles, '--no-file-parallelism'], {
116
120
  cwd: ROOT, encoding: 'utf8',
117
121
  });
@@ -125,8 +129,8 @@ mark('测试');
125
129
 
126
130
  // ---- 3. commit + tag ------------------------------------------------------
127
131
  step('提交并打 tag');
128
- const notes = notesFile && fs.existsSync(path.join(ROOT, notesFile))
129
- ? fs.readFileSync(path.join(ROOT, notesFile), 'utf8').trim()
132
+ const notes = notesFile
133
+ ? fs.readFileSync(notesFile, 'utf8').trim()
130
134
  : `v${to}`;
131
135
  git('add', 'package.json', 'server.json', 'src/shared/version.js');
132
136
  git('commit', '-m', `chore(release): v${to}`);
@@ -160,6 +164,51 @@ try {
160
164
  warn(`本地 commit 与 tag 都在,网络恢复后执行:git push origin main dev v${to}`);
161
165
  }
162
166
 
167
+ // ---- 4.5 GitHub Release ---------------------------------------------------
168
+ // **tag ≠ Release**。git tag 只是仓库里的一个 ref,GitHub 的 /releases 页面
169
+ // 读的是 Release 对象;只打 tag 不建 Release,页面会一直停在很久以前的版本
170
+ // (2026-10-24 用户就是在这里发现"releases 还是 2.1.1",而 tag 已经有 2.1.7)。
171
+ //
172
+ // 用 --generate-notes:GitHub 按 commit 自动生成 changelog,比手写更容易保持。
173
+ // 幂等:重复跑发版时该 tag 的 Release 已存在就跳过,不报错。
174
+ step('创建 GitHub Release');
175
+ let releaseOk = false;
176
+ {
177
+ const hasGh = spawnSync('gh', ['--version'], { encoding: 'utf8' }).status === 0;
178
+ if (!hasGh) {
179
+ warn('未找到 gh CLI,跳过 Release 创建。补救:gh release create v' + to + ' --generate-notes');
180
+ } else {
181
+ const exists = spawnSync('gh', ['release', 'view', `v${to}`], { cwd: ROOT, encoding: 'utf8' }).status === 0;
182
+ if (exists) {
183
+ ok(`v${to} 的 Release 已存在,跳过`);
184
+ releaseOk = true;
185
+ mark('github release');
186
+ } else {
187
+ // 标题尽量带上人话摘要:notes 的第一行通常写成 "# v2.1.8 — xxx",
188
+ // 直接用它,releases 列表就不会是一串光秃秃的版本号。
189
+ let releaseTitle = `v${to}`;
190
+ const headLine = String(notes || '').split('\n')[0].replace(/^#+\s*/, '').trim();
191
+ if (headLine.startsWith(`v${to}`) && headLine.length > `v${to}`.length) releaseTitle = headLine;
192
+ const argv = ['release', 'create', `v${to}`, '--verify-tag', '--latest', '--title', releaseTitle];
193
+ if (notesFile) {
194
+ argv.push('--notes-file', notesFile);
195
+ } else {
196
+ argv.push('--generate-notes');
197
+ }
198
+ const r = spawnSync('gh', argv, { cwd: ROOT, encoding: 'utf8' });
199
+ const out = `${r.stdout || ''}${r.stderr || ''}`.trim();
200
+ if (r.status === 0) {
201
+ ok('Release 已创建:' + out.split('\n').filter(Boolean).pop());
202
+ releaseOk = true;
203
+ mark('github release');
204
+ } else {
205
+ fail('创建 Release 失败:' + out.split('\n').filter(Boolean).slice(-3).join(' | '));
206
+ warn(`补救:gh release create v${to} --verify-tag --latest --generate-notes`);
207
+ }
208
+ }
209
+ }
210
+ }
211
+
163
212
  // ---- 5. 打包(桌面 App)---------------------------------------------------
164
213
  // 放在 npm 之前:打包失败就不该把版本发出去(npm 上发出去的版本撤不回来,
165
214
  // 只能靠再发一个版本补救)。但 MCP registry 又要求 npm 先有版本,所以顺序
@@ -201,6 +250,24 @@ step('打包桌面 App');
201
250
  console.log((v.stdout || '').split('\n').map((l) => ' ' + l).join('\n'));
202
251
  process.exit(1);
203
252
  }
253
+
254
+ // 把 dmg 挂到 Release 上 —— 否则 /releases 页面只有一段 changelog,用户还得
255
+ // 自己去找 App。上传放在校验之后:不一致的产物绝对不能对外发。
256
+ const dmg = path.join(ROOT, 'dist', `AI-Browser-${to}-arm64.dmg`);
257
+ if (releaseOk && fs.existsSync(dmg)) {
258
+ const up = spawnSync('gh', ['release', 'upload', `v${to}`, dmg, '--clobber'], {
259
+ cwd: ROOT, encoding: 'utf8',
260
+ });
261
+ if (up.status === 0) {
262
+ ok(`dmg 已挂到 Release(${(fs.statSync(dmg).size / 1024 / 1024).toFixed(1)} MB)`);
263
+ mark('release asset');
264
+ } else {
265
+ fail('dmg 上传失败:' + `${up.stdout || ''}${up.stderr || ''}`.trim().split('\n').slice(-2).join(' | '));
266
+ warn(`补救:gh release upload v${to} ${dmg} --clobber`);
267
+ }
268
+ } else if (!fs.existsSync(dmg)) {
269
+ warn('dmg 不存在,跳过上传');
270
+ }
204
271
  }
205
272
 
206
273
  // ---- 6. npm ---------------------------------------------------------------
@@ -316,10 +383,37 @@ if (process.platform !== 'darwin') {
316
383
  }
317
384
  }
318
385
 
386
+ // ---- 8. 验收"装好的那个 App" ----------------------------------------------
387
+ // 前面每一步验的都是"仓库里的东西":测试跑 dev 树、verify_build 比 asar 里的
388
+ // 字节。但用户双击的是 /Applications 里那个包,**没有人跑过它**。
389
+ // 2026-10-24 两次事故(App 落后 4 个版本 / 对话框修复只在 dev 生效)都出在这一层。
390
+ // 现在装完立刻用 e2e/shipped_ws.cjs 直接 spawn 这个包,跑对话框和多标签关键路径。
391
+ step('验收已安装的 App(在打包产物上跑,不是 dev 树)');
392
+ if (process.platform !== 'darwin' || args.includes('--no-install')) {
393
+ warn('跳过(非 macOS 或 --no-install)');
394
+ } else {
395
+ const vEnv = { ...process.env, AI_BROWSER_PORT: '9233' };
396
+ delete vEnv.ELECTRON_RUN_AS_NODE;
397
+ delete vEnv.NODE_OPTIONS;
398
+ const vr = spawnSync(process.execPath, ['e2e/shipped_ws.cjs'], {
399
+ cwd: ROOT, encoding: 'utf8', env: vEnv,
400
+ });
401
+ const vrOut = `${vr.stdout || ''}${vr.stderr || ''}`;
402
+ const tally = (vrOut.match(/PASS=(\d+) FAIL=(\d+)/) || []).slice(1);
403
+ if (vr.status === 0) {
404
+ ok(`已安装的 App 通过验收(PASS=${tally[0] || '?'})`);
405
+ mark('产物验收');
406
+ } else {
407
+ fail(`已安装的 App 验收失败(PASS=${tally[0] || '?'} FAIL=${tally[1] || '?'})—— 桌面上那个包的行为和源码不一致:`);
408
+ console.log((vrOut.split('\n').filter((l) => /FAIL \||ERR|失败明细/.test(l)).slice(0, 12)).map((l) => ' ' + l).join('\n'));
409
+ warn('补救:npm run verify:app (默认验 /Applications/AI Browser.app,可用 AIB_APP= 指定)');
410
+ }
411
+ }
412
+
319
413
  // ---- 摘要 -----------------------------------------------------------------
320
414
  console.log(`\n${c(36)}── v${to} 发版摘要 ──${c(0)}`);
321
415
  console.log(' 已完成: ' + (done.length ? done.join(' → ') : '(无)'));
322
- const skipped = ['版本号', '测试', 'commit', 'tag', 'git push', '打包', '打包校验', 'npm publish', 'mcp registry', '安装']
416
+ const skipped = ['版本号', '测试', 'commit', 'tag', 'git push', 'github release', '打包', '打包校验', 'release asset', 'npm publish', 'mcp registry', '安装', '产物验收']
323
417
  .filter((s) => !done.includes(s));
324
418
  if (skipped.length) console.log(` 未完成: ${skipped.join(', ')}`);
325
419
  process.exit(skipped.length ? 2 : 0);
@@ -2,4 +2,4 @@
2
2
  // Kept in JS (not imported from package.json) to avoid Node import-attribute
3
3
  // (JSON module) compatibility issues inside Electron. package.json "version"
4
4
  // is manually synced to this value; tests assert they match.
5
- export const VERSION = '2.1.7';
5
+ export const VERSION = '2.1.9';