chore: bump to 1.5.15, publish full package + hot update

- 版本号 1.5.14 → 1.5.15
- 新增 scripts/publish-full.js:一键上传完整包到 latest 与 v<version> release
- 清理未引用的 session_sync_enhanced.js 与临时 test-sync.js

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
mac
2026-07-16 19:50:33 +08:00
parent 02fb553c9e
commit 3f27682e78
5 changed files with 90 additions and 237 deletions
-187
View File
@@ -1,187 +0,0 @@
// session_sync_enhanced.js —— 增强版会话同步:确保本地所有会话一个不剩地同步到新 Codex 账号。
// 在 provider_sync.js 的基础上增强,确保:
// 1. 扫描所有 sessions/ 和 archived_sessions/ 下的会话
// 2. 强制插入到当前账号的数据库(UPSERT 逻辑)
// 3. 重建 session_index.jsonl
// 4. 更新全局项目索引
// 5. 提供详细的同步报告
const fs = require("fs");
const path = require("path");
const os = require("os");
const providerSync = require("./provider_sync");
const CODEX_HOME = path.join(os.homedir(), ".codex");
const SESSION_INDEX = path.join(CODEX_HOME, "session_index.jsonl");
// 完整同步方案:确保所有本地会话都进入新账号
async function syncAllSessionsToNewAccount(opts = {}) {
const log = opts.log || (() => {});
const onProgress = opts.onProgress || (() => {});
log("=== 开始全量会话同步 ===");
log("目标:将本地所有会话同步到当前 Codex 账号");
// 1. 检查 Codex 是否在运行
if (providerSync.codexRunning()) {
throw new Error("检测到 Codex 正在运行,请先完全退出 Codex 再同步。");
}
// 2. 获取当前 provider(从 config.toml)
const targetProvider = providerSync.targetProvider();
log(`当前账号 provider:${targetProvider}`);
// 3. 执行核心同步(provider_sync 已经做了 UPSERT)
log("开始同步本地会话到数据库...");
const result = await providerSync.syncProviderToCurrent(log, onProgress);
// 4. 重建 session_index.jsonl(确保索引完整)
log("重建会话索引...");
const indexResult = rebuildSessionIndex(log);
// 5. 生成同步报告
const report = {
success: true,
provider: result.target,
totalSessions: result.totalConversations,
syncedToDatabase: result.insertedRows,
updatedProvider: result.provRows,
updatedUserEvent: result.ueRows,
indexRebuilt: indexResult.total,
projects: result.projectRows,
backup: result.backup,
details: {
changedFiles: result.changedFiles,
userEventSessions: result.userEvents,
archivedSessions: indexResult.archived,
activeSessions: indexResult.active,
}
};
log("\n=== 同步完成 ===");
log(`✅ 总会话数:${report.totalSessions}`);
log(`✅ 新增到数据库:${report.syncedToDatabase} 条`);
log(`✅ 更新 provider:${report.updatedProvider} 条`);
log(`✅ 重建索引:${report.indexRebuilt} 条`);
log(`✅ 项目分组:${report.projects} 个`);
log(`✅ 备份位置:${report.backup}`);
log("\n现在可以启动 Codex,所有会话应该都能看到了。");
return report;
}
// 重建 session_index.jsonl:扫描所有会话文件,生成完整索引
function rebuildSessionIndex(log = () => {}) {
const sessions = require("./sessions");
// 列出所有会话(包括 archived)
const allConversations = sessions.listConversations({
includeSubagent: true,
includeArchived: true,
});
log(`扫描到 ${allConversations.length} 个会话文件`);
// 备份旧索引
if (fs.existsSync(SESSION_INDEX)) {
const backup = SESSION_INDEX + ".bak-" + Date.now();
fs.copyFileSync(SESSION_INDEX, backup);
log(`已备份旧索引:${path.basename(backup)}`);
}
// 重建索引:每个会话一行 JSON
const lines = [];
let active = 0;
let archived = 0;
for (const conv of allConversations) {
if (!conv.id) continue;
const indexEntry = {
id: conv.id,
thread_name: conv.title || "(无标题对话)",
project_path: conv.cwd || null,
created_at: conv.ts || new Date().toISOString(),
source: conv.source || "vscode",
archived: conv.archived || false,
};
lines.push(JSON.stringify(indexEntry));
if (conv.archived) archived++;
else active++;
}
// 写入新索引
fs.writeFileSync(SESSION_INDEX, lines.join("\n") + "\n", "utf-8");
log(`已重建索引:活跃 ${active} 条,归档 ${archived} 条`);
return { total: lines.length, active, archived };
}
// 诊断:检查会话同步状态
function diagnoseSessionSync(log = () => {}) {
const sessions = require("./sessions");
log("=== 会话同步诊断 ===");
// 1. 统计本地会话文件
const allConversations = sessions.listConversations({
includeSubagent: true,
includeArchived: true,
});
const activeSessions = allConversations.filter(c => !c.archived);
const archivedSessions = allConversations.filter(c => c.archived);
log(`本地会话文件:${allConversations.length} 个`);
log(` - 活跃:${activeSessions.length} 个`);
log(` - 归档:${archivedSessions.length} 个`);
// 2. 检查数据库
const dbFiles = fs.readdirSync(CODEX_HOME).filter(f => /^state_.*\.sqlite$/.test(f));
log(`数据库文件:${dbFiles.length} 个`);
for (const db of dbFiles) {
log(` - ${db}`);
}
// 3. 检查索引文件
const indexExists = fs.existsSync(SESSION_INDEX);
log(`session_index.jsonl:${indexExists ? "存在" : "不存在"}`);
if (indexExists) {
const indexContent = fs.readFileSync(SESSION_INDEX, "utf-8");
const indexLines = indexContent.split("\n").filter(l => l.trim());
log(` - 索引条目:${indexLines.length} 条`);
}
// 4. 检查 provider
const currentProvider = providerSync.targetProvider();
log(`当前 provider:${currentProvider}`);
// 5. 检查会话文件中的 provider 分布
const providerStats = {};
for (const conv of allConversations) {
const p = conv.provider || "(未设置)";
providerStats[p] = (providerStats[p] || 0) + 1;
}
log("会话文件 provider 分布:");
for (const [p, count] of Object.entries(providerStats)) {
log(` - ${p}: ${count} 个`);
}
return {
totalSessions: allConversations.length,
activeSessions: activeSessions.length,
archivedSessions: archivedSessions.length,
databases: dbFiles.length,
indexExists,
currentProvider,
providerStats,
};
}
module.exports = {
syncAllSessionsToNewAccount,
rebuildSessionIndex,
diagnoseSessionSync,
};
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "codex-market", "name": "codex-market",
"version": "1.5.14", "version": "1.5.15",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "codex-market", "name": "codex-market",
"version": "1.5.14", "version": "1.5.15",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"electron-updater": "^6.8.9", "electron-updater": "^6.8.9",
+2 -2
View File
@@ -1,11 +1,11 @@
{ {
"name": "codex-market", "name": "codex-market",
"version": "1.5.14", "version": "1.5.15",
"description": "TD工具箱 / 技能商店 / 备份工具 —— 跨平台桌面客户端", "description": "TD工具箱 / 技能商店 / 备份工具 —— 跨平台桌面客户端",
"author": "Codex Market", "author": "Codex Market",
"license": "MIT", "license": "MIT",
"main": "electron/bootstrap.js", "main": "electron/bootstrap.js",
"hotContentVersion": "1.5.14", "hotContentVersion": "1.5.15",
"scripts": { "scripts": {
"start": "electron .", "start": "electron .",
"dist": "electron-builder", "dist": "electron-builder",
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env node
// publish-full.js —— 发布完整安装包到自建 gitea。
// 把 dist/ 下的 dmg/zip/blockmap/latest-*.yml 上传到:
// - latest release(electron-updater 自动更新目标,文件名固定,覆盖上传)
// - v<version> release(版本存档,供手动下载)
"use strict";
const fs = require("fs");
const os = require("os");
const path = require("path");
const { execFileSync } = require("child_process");
const ROOT = path.join(__dirname, "..");
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8"));
const version = pkg.version;
let token = process.env.GIT_TOKEN || "";
if (!token) {
try { token = fs.readFileSync(path.join(os.homedir(), ".tokendun-git-token"), "utf8").trim(); } catch {}
}
if (!token) { console.error("缺少 gitea token:设置 GIT_TOKEN 或写入 ~/.tokendun-git-token"); process.exit(1); }
const OWNER = "mhgmh", REPO = "codex-market-app";
const API = `https://up.tokendun.cc/api/v1/repos/${OWNER}/${REPO}`;
const DIST = path.join(ROOT, "dist");
// 要上传的产物:dmg/zip/blockmap + latest-mac.yml
const files = fs.readdirSync(DIST).filter((f) =>
/\.(dmg|zip)(\.blockmap)?$/.test(f) || /^latest.*\.yml$/.test(f)
);
if (!files.length) { console.error("dist/ 下没有可上传的产物"); process.exit(1); }
console.log(`待上传 ${files.length} 个文件:\n ` + files.join("\n "));
function api(method, urlPath, body) {
const args = ["-s", "-X", method, "-H", `Authorization: token ${token}`, `${API}${urlPath}`];
if (body !== undefined) { args.push("-H", "Content-Type: application/json", "-d", JSON.stringify(body)); }
return execFileSync("curl", args, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
}
function apiJson(method, urlPath, body) {
try { return JSON.parse(api(method, urlPath, body)); } catch { return null; }
}
// 确保 release 存在(按 tag 取,缺则建)
function ensureRelease(tag, name, prerelease) {
let rel = apiJson("GET", `/releases/tags/${encodeURIComponent(tag)}`);
if (!rel || !rel.id) {
console.log(`创建 release ${tag} …`);
rel = apiJson("POST", `/releases`, {
tag_name: tag, target_commitish: "main", name, body: `TD工具箱 ${version} 完整安装包`,
draft: false, prerelease: !!prerelease,
});
}
if (!rel || !rel.id) { console.error(`无法创建/获取 release ${tag}`); process.exit(1); }
return rel;
}
function uploadTo(rel, tag) {
// 删同名旧 asset,再上传
const existing = new Map((rel.assets || []).map((a) => [a.name, a.id]));
for (const name of files) {
if (existing.has(name)) {
api("DELETE", `/releases/${rel.id}/assets/${existing.get(name)}`);
console.log(` [${tag}] 删除旧 asset: ${name}`);
}
}
for (const name of files) {
const file = path.join(DIST, name);
const code = execFileSync("curl", ["-s", "-o", "/dev/null", "-w", "%{http_code}",
"-X", "POST", "-H", `Authorization: token ${token}`,
"-F", `attachment=@${file};type=application/octet-stream`,
`${API}/releases/${rel.id}/assets?name=${encodeURIComponent(name)}`],
{ encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
console.log(` [${tag}] 上传 ${name} → HTTP ${code}`);
if (code !== "201") { console.error(` 上传 ${name} 失败`); process.exit(1); }
}
}
// 1) latest release —— electron-updater 自动更新目标
const latest = ensureRelease("latest", "自动更新通道", true);
uploadTo(latest, "latest");
// 2) v<version> release —— 版本存档
const verRel = ensureRelease(`v${version}`, `TD工具箱 ${version}`, false);
uploadTo(verRel, `v${version}`);
console.log(`\n✅ 完整包 v${version} 已发布到 latest 与 v${version} 两个 release。`);
console.log(` 自动更新地址:https://update.tokendun.cc/${OWNER}/${REPO}/releases/download/latest`);
-46
View File
@@ -1,46 +0,0 @@
// test-sync.js —— 测试增强后的会话同步功能
const providerSync = require("./electron/lib/provider_sync");
console.log("=== 会话同步测试 ===\n");
// 1. 检查 Codex 是否在运行
console.log("1. 检查 Codex 运行状态...");
const isRunning = providerSync.codexRunning();
if (isRunning) {
console.error("❌ Codex 正在运行,请先完全退出 Codex(Cmd+Q 或 Quit)再测试");
process.exit(1);
}
console.log("✅ Codex 未运行,可以同步\n");
// 2. 检查当前 provider
console.log("2. 检查当前 provider...");
const targetProvider = providerSync.targetProvider();
console.log(`✅ 当前 provider:${targetProvider}\n`);
// 3. 执行同步
console.log("3. 开始同步会话...");
const log = (msg) => console.log(` ${msg}`);
const onProgress = (p) => {
if (p.phase) console.log(` [进度] ${p.phase} ${p.done || ""}/${p.total || ""}`);
};
providerSync.syncProviderToCurrent(log, onProgress)
.then((result) => {
console.log("\n=== 同步完成 ===");
console.log(`✅ 目标 provider: ${result.target}`);
console.log(`✅ 总会话数: ${result.totalConversations}`);
console.log(`✅ 修改文件: ${result.changedFiles}`);
console.log(`✅ 用户消息会话: ${result.userEvents}`);
console.log(`✅ 更新 provider: ${result.provRows} 行`);
console.log(`✅ 更新 has_user_event: ${result.ueRows} 行`);
console.log(`✅ 新增线程: ${result.insertedRows} 行`);
console.log(`✅ 项目分组: ${result.projectRows} 个`);
console.log(`✅ 索引重建: ${result.indexRebuilt} 条`);
console.log(`✅ 备份位置: ${result.backup}`);
console.log("\n现在可以打开 Codex,所有会话应该都能在侧边栏看到了 🎉");
})
.catch((err) => {
console.error("\n❌ 同步失败:", err.message || err);
console.error(err.stack);
process.exit(1);
});