@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 +3 -2
- package/scripts/release.cjs +99 -5
- package/src/shared/version.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glittering0717/ai-browser",
|
|
3
|
-
"version": "2.1.
|
|
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",
|
package/scripts/release.cjs
CHANGED
|
@@ -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
|
|
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
|
|
129
|
-
? fs.readFileSync(
|
|
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);
|
package/src/shared/version.js
CHANGED
|
@@ -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.
|
|
5
|
+
export const VERSION = '2.1.9';
|