用 WorkBuddy 写了个 ImageKit 上传工具

我平时用 ImageKit 存图。后台那个上传框一次拖几十张就开始排队,进度条挤成一列,还得一直盯着别断了。忍了几次之后决定写个桌面工具,要求也不高:能批量拖进去、能看见每张的状态、失败了能重试。
问题是我不想再从头配一遍 Electron。之前弄过一次,vite、主进程、预加载脚本、打包配置,光让窗口正常出来就花了一下午,最后还卡在 dmg 上。所以这回换了个法子:用 WorkBuddy,把想要的东西说清楚,让它去搭。
头一个小时我还在写"需求文档"
刚上手时我不太确定该怎么跟它说话。第一段我写得跟 PRD 一样,分了好几条,还特意加了"技术要求"和"验收标准",生怕漏掉什么。它照着搭出来了,但我很快就发现没必要------直接说人话就行,说"这个按钮我用着别扭,改一下"它也跟得上。
真正让我改观的是它不只是给我代码片段。它能自己去读文件、跑命令、翻日志、改配置。前两天 mac 的构建挂了,我习惯性地准备去把报错复制出来,结果试了句"mac 的安装包构建失败了,去看一下",它自己把 CI 的日志拉回来,落到了具体哪一行。这跟我以前用过的补全类工具完全不是一回事。
密钥这块我没敢马虎
第一段里我特地加了一句:ImageKit 的 private key 绝对不能进渲染进程。以前踩过坑,所以这条写得很死。
它把结构做成了这样:所有跟 key 沾边的活------签名、发起上传、测试连通性------全部留在主进程,渲染进程只能通过 preload 里白名单放出去的几个 IPC 调用,碰不到密钥。这个结构我认,后面一直没动过。配置存在 userData 下的 config.json 里,仓库里只留一份带假值的 config.example.json。
改得最久的是云端目录
拖拽区、上传队列、历史记录、设置弹窗基本都是一遍过的,来回改得最多的是云端目录浏览那一块。
一开始做成单击目录就进去。自己用两次就觉得别扭:大多数时候你只是想挑一个目录当上传目标,并不想跳进去。改成"单击选中、双击(或者点那个箭头)进入",才顺手起来。
加英文这件事比我想的折腾
界面起初全是中文。后来想想还是得让英文用户能用,于是加了英文,而且默认英文。代价比预想中要大。
文案原本是硬写在组件里的,翻到一半我就知道要出事------改一个词得满项目去搜。
改成字典之后舒服多了:以英文那份为准,中文声明成 Record<MessageKey, string>,少翻一条 npm run typecheck 直接报错,想漏都漏不掉。渲染层包一层 Provider,组件里用 useT() 取。
主进程那边是后来才想起来补的。报错信息、dialog 的标题、文件过滤器的名字,一开始还是中文。中文界面下看不出问题,一切成英文就露馅了。
还有个坑,现在想起来挺好笑的。验证语言切换到底生效没有,我一开始靠截图。有几次截出来还是英文,我以为切换失败了,来回查了半天。后来才反应过来:capturePage() 在状态刚变化的时候会返回上一帧。改成就直接读 DOM 里的文本,一眼就对了。截图现在只留给"给你看看长什么样"这个用途。
顺手还定了个规矩:不能拿翻译后的文案去做判断。比如判断对话框是不是被用户取消了,不能去比"已取消"这个字符串,得走一个专门的函数。
环境比代码难缠
代码上的问题好歹能想明白,环境上的不行。
npm 的缓存目录属主是 root,装依赖直接报错,后来改成指定项目内的缓存目录才过去。这个环境还预设了 ELECTRON_RUN_AS_NODE=1,直接跑起不来,得先把这个变量摘掉。
最阴的一个是"窗口假死"。新开一个实例,看起来毫无反应。查了很久才发现是上次没清干净的单实例锁文件还躺在那儿,新进程一启动就自己退出了,我盯着的一直是那个旧窗口。
ps 和 pkill 在这个环境里也用不了,列不出进程,只能拿已知的 pid 一个个 kill -0 去试。那两天我的排查方式基本是"猜一个 pid,试一下"。
打 tag 那天晚上,mac 挂了
CI 分了三条线:平时 push 和提 PR 只跑类型检查加构建;打 v* 的 tag 才出安装包,dmg 和 exe,然后自动建 Release。
前两天打了个 v1.0.0。Windows 那边一百秒出头就完事了,mac 这边 85 秒退出,倒在打包 dmg 上。日志最后是这样:
⨯ unable to execute hdiutil args=["detach","-quiet","/Volumes/ImageKit Uploader"]
error=Exit code: 16. Above command failed, retrying 5 more times
卸载挂载卷失败,重试 6 次全挂,然后整个 job 断掉。
我一开始怀疑是 runner 环境的问题,macos-14 是 arm64 机器,Spotlight 又爱去啃刚挂上来的卷。后来把日志往前翻才看到关键的地方:arm64 和 x64 两个 dmg 是同时开始打的,而它们的挂载卷名一模一样,都叫 ImageKit Uploader------卷名取自 dmg.title,也就是 productName。两个同名卷撞在一起,谁先卸载谁失败。
修的时候还多踩了一脚。我以为在命令后面加 --arm64 / --x64 就能把两个架构拆开,试了才发现没用:electron-builder.yml 里 mac.target 下面写死的 arch 列表会盖掉命令行的参数。得先把那段删掉,架构全部交给命令行指定,再拆成两次串行调用。
现在 CI 里是这么写的:
yaml
- name: 关闭 Spotlight 索引
run: sudo mdutil -a -i off || true
- name: 打包 dmg(arm64 + x64)
run: |
build_dmg() {
npx electron-builder --mac --arm64 --publish never &&
npx electron-builder --mac --x64 --publish never
}
build_dmg || {
hdiutil detach -force "/Volumes/ImageKit Uploader" || true
sleep 10
build_dmg
}
到底是同名卷的锅还是 Spotlight 的锅,说实话我没百分之百确定,干脆两边一起堵,外面再套一次重试。能稳定跑就行。
现在的状态
Release 里躺着两个 dmg(Apple Silicon 和 Intel)加一个 exe,都没签名。mac 上第一次打开要右键,Windows 会弹 SmartScreen 提示。
还欠着两件:图标到现在还是 Electron 默认那个原子球,被朋友吐槽过一次;自动更新也没接,每次发版都得到处去下载手动装。
先这样,功能上是能用了,剩下的慢慢补。