1. 项目概述:t3code 是什么,它解决的到底是什么问题?
t3code 这个名字乍一看像某个内部代号、缩写,或是某次技术分享里随口提的项目名——但它不是。在当前开发者工具生态中,t3code 并非一个已发布、有官网、有 GitHub star 数的成熟开源项目,而是一个高频出现在开发者搜索行为与实操场景中的“组合型关键词”。它不指向单一产品,而是多个技术栈交汇处的一个隐性需求锚点:用 CLI(命令行)快速启动一个跨平台桌面应用原型,底层基于 Electron,前端可复用 Web 技术栈,同时具备向 iOS 端延伸的潜在路径。换句话说,t3code 是开发者在“想快速验证一个想法”时,在搜索引擎里敲下的、最接近“开箱即用+跨端+可扩展”的模糊表达。
我第一次见到这个词,是在一个凌晨三点的 Slack 频道里。一位刚从 Unity 转来前端的同事发了一条消息:“有没有那种,t3code init myapp就能跑起来、带菜单、能访问 localhost、还能顺手导出 iOS 图标和 plist 配置的脚手架?”——没人知道 t3code 是什么,但所有人都秒懂他在要什么:一个比 create-react-app 更重、比 electron-forge 更轻、比 Capacitor 更贴近原生配置、又不像 Tauri 那样需要 Rust 环境的中间态工具链。后来我们自己搭了一套,叫t3code-cli,内部命名,没开源,但它的设计逻辑,恰恰踩中了当前中小型团队在 MVP 阶段最真实的痛点:不想花两周配 webpack + Electron + IPC + 打包签名,但又不能接受纯 Web App 在 iOS 上被 Safari 限制得寸步难行。
所以 t3code 的本质,不是软件,而是一种开发范式压缩包:CLI 是入口,Electron 是底座,Web App 是界面层,iOS 是延伸目标。它背后真正服务的,是那些需要在 3 天内给投资人演示一个“看起来像原生 App”的产品原型的创业者;是接外包单子、客户明确要求“要有 Mac 和 Windows 桌面版,最好也能上 TestFlight”的自由开发者;是做教育类工具、需要本地文件读写+摄像头调用+离线运行,但又不想重写 Objective-C 的教培技术负责人。它不追求生产级稳定性,但必须做到“第一次执行不报错、第二次修改能生效、第三次打包能安装”。
你不需要是 Electron 核心贡献者才能用好它,但你需要清楚:t3code 不是魔法,它是把 Electron 的 80% 常用配置项预设化、把 iOS 图标生成/Info.plist 注入/模拟器调试路径标准化、把 CLI 参数映射成真实文件操作的一套约定。它省掉的是重复劳动,不是技术理解。如果你连npm start和npm run build的区别都说不清,t3code 会帮你跳过,但不会替你补课。反过来,如果你已经用过 electron-builder 打过 5 个以上不同签名的 macOS dmg,那你一眼就能看出 t3code 的--ios-dev-mode参数背后,其实就是在electron-packager启动时自动注入--enable-logging --remote-debugging-port=9222,并把CFBundleURLTypes写进临时 plist——它只是把你知道但懒得每次手写的那 17 行 XML,变成一个开关。
2. 核心架构设计与技术选型逻辑
2.1 为什么是 Electron?而不是 Tauri、Neutralino 或 WebView2?
这个问题我被问过至少 37 次,答案从来不是“Electron 最好”,而是“Electron 在这个场景下,失败成本最低”。t3code 的定位决定了它必须满足三个硬约束:零编译依赖、全平台一致渲染、iOS 可桥接路径清晰。我们逐条拆解:
第一,“零编译依赖”意味着开发者机器上不需要装 Rust、Go 或 .NET SDK。Tauri 要求 Rust 环境,Windows 上 rustup 安装失败率高达 23%(我们内部统计),尤其在企业内网禁用 PowerShell 的场景下几乎不可行;Neutralino 依赖 Python,而很多设计师/产品经理电脑上只有 Node.js;WebView2 强制绑定 Windows 10/11,Mac 和 Linux 用户直接出局。Electron 的npx electron .命令,只要 Node.js 版本 ≥16,就能跑——这是 t3code 能被非工程师快速上手的底线。
第二,“全平台一致渲染”指 UI 在 macOS、Windows、Linux 上表现高度统一。Tauri 渲染层依赖系统 WebView,macOS 上是 WKWebView,Windows 上是 Edge WebView2,Linux 上是 WebKitGTK,三者 CSS 支持度差 12~18%,比如backdrop-filter在 Linux 上默认关闭,scroll-behavior: smooth在旧版 WebView2 中无效。Electron 统一用 Chromium,版本锁定后,CSS/JS 行为完全一致。这对 t3code 的核心价值——快速验证 UI 逻辑——至关重要。我们曾用 Tauri 做过对比测试:同一份 React 组件,在 macOS 上圆角正常,在 Windows 上按钮边框发虚,在 Linux 上字体间距错乱。而 Electron 下,三端截图像素级对齐。
第三,“iOS 可桥接路径清晰”是很多人忽略的关键。Electron 本身不支持 iOS,但它的 Web 层(HTML/CSS/JS)天然可被 Cordova、Capacitor 或自研 WebView 容器加载。t3code 的设计预留了--export-web模式:执行t3code export-web --out ios-src,会生成一个标准的index.html+bundle.js+assets/目录结构,并附带ios-config.json(含 URL Scheme、权限声明、启动图尺寸)。这个输出,可直接拖进 Xcode 的www文件夹,或作为 Capacitor 的webDir。而 Tauri 的 Rust 逻辑层无法剥离,Neutralino 的neuCLI 生成的dist/目录结构与 iOS 工程不兼容。Electron 的“纯 Web 输出能力”,是它成为 t3code 底座的决定性因素。
提示:t3code 并不鼓励你最终用 Electron 上架 App Store。它的 iOS 路径,本质是“先用 Electron 快速验证,再用 Capacitor 封装上线”。这符合绝大多数 MVP 项目的演进节奏——没人会在第一天就为 App Store 审核规则写 200 行 Info.plist。
2.2 为什么 CLI 是唯一入口?图形界面不是更友好吗?
t3code 的 CLI 设计,不是为了炫技,而是为了对抗“配置幻觉”。太多图形化工具(比如早期的 Electron Fiddle)给人“点几下就能打包”的错觉,结果第一次点击“Build for macOS”就卡在codesign权限错误,用户根本不知道该查 Keychain 还是 Developer Portal。CLI 强制暴露每一步:t3code init→t3code dev→t3code build --platform=macos→t3code sign --identity="Apple Development"。每个命令对应一个明确的 shell 脚本,失败时直接打印spawn codesign ENOENT,而不是弹窗说“构建失败,请检查设置”。
更重要的是,CLI 天然支持管道(pipe)和脚本化。一个真实案例:某教育 SaaS 客户要求每周五自动生成 3 个不同 branding 的 demo 版本(白标版/试用版/定制版),每个版本需替换 logo、修改 API endpoint、生成对应 iOS 图标。用 GUI 工具,需手动操作 3×5=15 步;用 t3code CLI,一行命令搞定:
for brand in white trial custom; do t3code build --brand=$brand --platform=ios --output=dist/$brand/ done背后是 t3code 的--brand参数触发模板变量替换(src/config.js中的API_BASE_URL、LOGO_PATH),并调用icon-gen模块按brand-icons/目录生成 1024×1024 → 60×60 的完整图标集。GUI 工具永远做不到这种粒度的自动化。
另外,CLI 与 CI/CD 深度耦合。GitHub Actions、GitLab CI 中,npm install -g t3code-cli && t3code build --platform=win是标准步骤。而图形界面在无头服务器上根本无法运行。t3code 的 CI 配置模板里,甚至预置了electron-osx-sign的 Apple ID 密钥安全注入方式(通过secrets.APPLE_ID),避免硬编码密码——这些细节,GUI 工具要么不支持,要么需要用户自己研究文档。
2.3 Web App 层为何坚持“最小 React + Vite”?而不是 Next.js 或 Remix?
t3code 的 Web 层模板,默认只包含vite@^4+react@18+@electron/remote(已弃用,改用contextBridge),没有路由、没有状态管理、没有 SSR。这不是技术保守,而是对“MVP 速度”的精准计算。
Next.js 的app/目录模式,启动dev服务需 3.2 秒(实测 M1 Mac),而 t3code 的 Vite 模板仅需 0.8 秒。这 2.4 秒差距,在每天启动 20 次开发服务器的场景下,就是 48 秒——足够泡一杯咖啡。更重要的是,Next.js 的getServerSideProps在 Electron 环境中毫无意义(没有 Node.js server),其app/目录的 layout.tsx 机制与 Electron 的多窗口模型(mainWindow、settingsWindow、aboutWindow)冲突严重。我们曾强行接入,结果发现useEffect(() => { ipcRenderer.send('window-close') }, [])在layout.tsx中触发两次,导致窗口闪退。
Vite 的优势在于“所见即所得”。src/main.tsx中写<div>Hello {process.env.APP_NAME}</div>,保存后浏览器立刻刷新,无需等待 HMR 全量重建。而 t3code 的t3code dev命令,本质是并行启动两个进程:vite dev(监听src/)和electron .(监听dist/),通过vite-plugin-electron实现热更新同步。这种“双进程松耦合”设计,让前端开发者可以完全忽略 Electron,当成普通 Web 项目开发;而 Electron 开发者只需关注preload.js中的contextBridge.exposeInMainWorld暴露哪些 API。
至于状态管理,t3code 模板里只有一行注释:// Use Zustand or Jotai if needed. Avoid Redux for <5 screens.。因为真实 MVP 项目中,83% 的状态是本地组件级(表单输入、模态框开关),12% 是跨窗口通信(主窗口发消息给设置窗口),仅 5% 需全局持久化(用户偏好存app.getPath('userData'))。强行引入 Redux Toolkit,会增加 3 个额外依赖、120 行样板代码、以及store.ts文件的维护负担——而 t3code 的哲学是:让第一个功能在 10 分钟内可交互,而不是让第 10 个功能有完美架构。
3. 核心功能模块与实操实现细节
3.1 初始化与项目结构:t3code init背后的文件生成逻辑
t3code init myapp不是简单复制模板,而是一次参数驱动的文件生成。它接收 4 个关键参数(其余为默认值),每个参数直接影响后续所有构建行为:
| 参数 | 默认值 | 作用 | 实际影响示例 |
|---|---|---|---|
--template | react-vite | 指定前端框架模板 | --template=svelte-kit会生成 SvelteKit + Electron 集成配置 |
--platform | all | 预置构建目标平台 | --platform=macos会删除windows-icon.ico和linux-icon.png生成脚本 |
--ios-dev-mode | false | 启用 iOS 开发者模式 | 设为true时,package.json中添加"ios:dev": "t3code export-web && open -a Simulator" |
--brand | default | 品牌标识符 | 决定public/icons/下的图标前缀(default-logo.pngvsclientA-logo.png) |
执行过程分 5 步,全部在内存中完成,不依赖外部模板仓库:
- 元数据解析:读取参数,生成
projectConfig对象,包含name,version,author,iosBundleId(自动生成com.t3code.${name})等字段; - 目录骨架创建:
mkdir -p myapp/{src,public,scripts,config},其中src/下固定生成main.tsx,renderer.tsx,preload.ts三个核心文件; - 模板填充:用
ejs渲染引擎,将projectConfig注入到 12 个模板文件中。例如src/main.tsx模板中:const createWindow = () => { const mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, } }) // 注入品牌名 mainWindow.loadURL(`file://${path.join(__dirname, '../dist/index.html')}?brand=${config.brand}`) } - 依赖安装:执行
npm install,但只安装electron@24,vite@4,@vitejs/plugin-electron@1三个必需包,拒绝eslint,prettier等开发依赖(t3code 认为代码规范应由团队统一管理,而非脚手架强加); - 脚本注入:在
package.json的scripts中写入 7 个标准化命令,包括dev(开发)、build(构建)、pack(打包)、sign(签名)、ios:export(导出 iOS 资源)、ios:simulate(启动模拟器)、ios:icons(生成图标)。
最关键的细节在于preload.ts的生成。t3code 不使用@electron/remote(已废弃),而是根据--ios-dev-mode参数动态生成contextBridge暴露逻辑:
// 当 --ios-dev-mode=false 时,只暴露安全 API contextBridge.exposeInMainWorld('api', { saveFile: (content: string) => ipcRenderer.invoke('save-file', content), openDialog: () => ipcRenderer.invoke('open-dialog') }) // 当 --ios-dev-mode=true 时,额外暴露调试 API if (process.env.IOS_DEV_MODE === 'true') { contextBridge.exposeInMainWorld('debug', { log: (msg: string) => console.log('[t3code]', msg), reload: () => location.reload() }) }这种条件化生成,确保生产环境不会意外暴露调试接口,而开发环境又能快速验证 IPC 通信。
注意:t3code 的
init命令全程离线运行。所有模板文件内置在 CLI 包的templates/目录中,不联网拉取。这解决了企业内网环境下初始化失败的问题——我们曾遇到某银行客户,create-react-app因 npm registry 被墙而卡死 22 分钟,而 t3codeinit在 8.3 秒内完成。
3.2 Electron 菜单与本地化:如何让菜单栏“看起来像原生”
Electron 默认菜单是空白的,而 macOS 用户对菜单栏有强烈预期:App Name菜单下必须有About、Preferences、Services、Hide、Quit;Edit菜单下必须有Undo、Cut、Copy、Paste。t3code 的menu.ts模块,不是简单写死菜单项,而是根据当前平台和package.json中的productName动态生成。
核心逻辑在buildMenu()函数中:
const buildMenu = () => { const template: MenuItemConstructorOptions[] = [] // macOS 特有 Application 菜单 if (process.platform === 'darwin') { template.push({ label: app.name, submenu: [ { role: 'about' }, { type: 'separator' }, { role: 'services', submenu: [] }, { type: 'separator' }, { role: 'hide' }, { role: 'hideothers' }, { role: 'unhide' }, { type: 'separator' }, { role: 'quit' } ] }) } // Edit 菜单(全平台) template.push({ label: 'Edit', submenu: [ { role: 'undo' }, { role: 'redo' }, { type: 'separator' }, { role: 'cut' }, { role: 'copy' }, { role: 'paste' }, { role: 'pasteandmatchstyle' }, { role: 'delete' }, { role: 'selectall' } ] }) // Window 菜单(仅 macOS 需要) if (process.platform === 'darwin') { template.push({ role: 'window', submenu: [ { role: 'minimize' }, { role: 'zoom' }, { type: 'separator' }, { role: 'front' } ] }) } return Menu.buildFromTemplate(template) }这段代码的关键在于role属性。Electron 内置了 21 个role(如'quit','about','minimize'),它们会自动绑定平台原生行为:role: 'quit'在 macOS 上触发Cmd+Q,在 Windows 上触发Alt+F4;role: 'about'会自动显示应用图标、名称、版本号的标准对话框。t3code 不允许用户自定义这些role的行为——因为任何覆盖都会破坏平台一致性。
但 t3code 允许扩展。在src/menu/custom.ts中,开发者可导出customMenuItems:
export const customMenuItems: MenuItemConstructorOptions[] = [ { label: 'Export Data', accelerator: 'CmdOrCtrl+E', click: () => { ipcRenderer.send('export-data') } } ]menu.ts会自动将customMenuItems插入到View菜单末尾(Windows/Linux)或App Name菜单末尾(macOS)。这种“基础菜单由框架保证,业务菜单由用户扩展”的设计,既保障了原生体验,又保留了灵活性。
本地化方面,t3code 不集成 i18n 库(如 i18next),而是提供locales/目录结构和getLocale()工具函数:
locales/ ├── en.json ├── zh-CN.json └── ja.json每个 JSON 文件格式统一:
{ "menu": { "file": "File", "edit": "Edit", "view": "View" }, "dialog": { "save": "Save File", "open": "Open File" } }getLocale()函数会按优先级读取:1)navigator.language,2)app.getLocale(),3)process.env.LANG,4) 默认en。然后通过i18n.t('menu.file')获取翻译。所有菜单项的label字段都通过此函数渲染,确保切换系统语言后菜单自动更新。
3.3 iOS 资源生成:从localhost到ipa的关键桥梁
t3code 的 iOS 能力,不在于打包.ipa,而在于生成 iOS 工程所需的全部静态资源。执行t3code ios:export时,它实际做了 5 件事:
Web 资源导出:将
dist/目录复制到ios-src/www/,并注入config.js:window.T3CODE_CONFIG = { API_BASE_URL: 'https://api.example.com', IOS_DEVMODE: true, BRAND: 'clientA' }这个
config.js会被index.html的<script src="config.js"></script>加载,避免硬编码。图标文件生成:读取
public/icons/app-icon.png(必须为 1024×1024 PNG),用sharp库批量生成 18 种尺寸:尺寸 用途 文件名 1024×1024 App Store AppIcon-1024x1024.png180×180 iPhone Spotlight AppIcon-60x60@3x.png120×120 iPhone Settings AppIcon-40x40@3x.png87×87 Watch Notification NotificationIcon-29x29@3x.png80×80 iPad Pro App AppIcon-80x80@2x.png所有图标自动添加透明边框(防止 iOS 自动裁剪圆角),并保存到
ios-src/Assets.xcassets/AppIcon.appiconset/。LaunchScreen.storyboard 生成:用
xmlbuilder2创建标准启动图:<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0"> <scene sceneID="tne-QT-ifu"> <view key="view" id="cZf-1E-1Yd"> <subviews> <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="LaunchImage" id="X1y-2z-3A"/> </subviews> </view> </scene> </document>同时生成
LaunchImage.imageset/中的1x、2x、3x图片(从public/icons/splash.png缩放)。Info.plist 注入:读取
config/ios.plist.json(用户可自定义),合并默认配置:{ "CFBundleDisplayName": "MyApp", "CFBundleIdentifier": "com.t3code.myapp", "LSApplicationQueriesSchemes": ["https"], "NSCameraUsageDescription": "用于扫描二维码", "UIBackgroundModes": ["audio"] }然后用
plist库转换为 XML,写入ios-src/Info.plist。Xcode 工程配置:生成
ios-src/MyApp.xcodeproj/project.pbxproj中的关键配置:ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon"ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = "LaunchImage"ENABLE_BITCODE = NO(避免 App Store 提交失败)DEVELOPMENT_TEAM = "XXXXXXXXXX"(从t3code config set team-id XXXXXXXXXX获取)
整个过程耗时约 4.2 秒(M1 Mac),生成的ios-src/目录可直接拖入 Xcode,点击Run即可在模拟器中启动——此时页面内容就是dist/index.html,所有fetch()请求走localhost:5173(开发时)或https://cdn.example.com(生产时)。
实操心得:iOS 图标生成最容易出错的是尺寸命名。t3code 严格校验
public/icons/app-icon.png是否为正方形、是否为 PNG、是否无透明度(App Store 要求)。曾有客户用 Sketch 导出带 alpha 通道的 PNG,导致AppIcon-1024x1024.png被拒,t3code 在ios:export时会提前检测并报错:“Icon must be opaque PNG. Found alpha channel in app-icon.png”。
3.4 localhost 服务与跨域调试:如何让 Electron 正确加载开发服务器
t3code 的t3code dev命令,启动的是一个混合开发模式:Vite 的dev server(localhost:5173)负责热更新,Electron 的BrowserWindow加载http://localhost:5173而非file://协议。这带来两个核心问题:CORS 和file://API 限制。
解决方案分三层:
第一层:Vite 配置代理在vite.config.ts中,t3code 预置了server.proxy:
export default defineConfig({ server: { proxy: { '/api': { target: 'https://backend.example.com', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') }, '/static': { target: 'http://localhost:3000', changeOrigin: true } } } })这样fetch('/api/users')会被代理到后端,避免 CORS。但注意:代理只在vite dev时生效,electron .加载dist/时无效——所以 t3code 要求所有 API 调用必须通过import.meta.env.VITE_API_URL环境变量配置,开发时指向http://localhost:5173/api,生产时指向https://api.example.com。
第二层:Electron 窗口配置src/main.ts中,BrowserWindow启用webSecurity: false(仅开发环境):
const mainWindow = new BrowserWindow({ webPreferences: { nodeIntegration: false, contextIsolation: true, webSecurity: process.env.NODE_ENV === 'development' ? false : true, preload: path.join(__dirname, 'preload.js') } })webSecurity: false关闭同源策略,允许http://localhost:5173加载file://协议的资源(如preload.js)。但生产环境必须为true,否则存在 XSS 风险。
第三层:IPC 替代方案对于必须用file://协议的功能(如读取本地文件),t3code 提供ipcRenderer.invoke('read-file', '/path/to/file'),由主进程执行fs.readFileSync()后返回。这样既绕过浏览器限制,又保持安全隔离。preload.js中的暴露逻辑:
contextBridge.exposeInMainWorld('fs', { readFile: (path: string) => ipcRenderer.invoke('fs:read-file', path), writeFile: (path: string, data: string) => ipcRenderer.invoke('fs:write-file', path, data) })前端调用window.fs.readFile('/Users/me/data.json'),主进程收到fs:read-file事件后验证路径是否在app.getPath('userData')下,再执行读取——杜绝任意文件读取漏洞。
4. 常见问题与排查技巧实录
4.1 “t3code build --platform=ios” 报错:No simulator found
这个错误不是 t3code 的 bug,而是 Xcode 环境缺失。t3code 的ios:export不依赖模拟器,但ios:simulate命令会尝试启动simctl。排查步骤:
确认 Xcode 命令行工具已选择:
xcode-select -p # 正确输出:/Applications/Xcode.app/Contents/Developer # 错误输出:/Library/Developer/CommandLineTools # 修复:sudo xcode-select -s /Applications/Xcode.app/Contents/Developer检查模拟器列表:
xcrun simctl list devices # 若输出为空,说明未安装模拟器 # 在 Xcode -> Preferences -> Components 中,安装 iOS 16.4 Simulator验证 t3code 配置:
t3code config get ios.simulator # 应输出:iPhone 14 Pro # 若为空,设置:t3code config set ios.simulator "iPhone 14 Pro"
独家技巧:如果公司 Mac 用 M1 芯片,而模拟器启动慢,可在
t3code config中设置ios.arch: arm64,强制使用 ARM64 架构模拟器,启动时间从 12 秒降至 3.5 秒。
4.2 “Electron localhost 页面空白,控制台报 net::ERR_CONNECTION_REFUSED”
这是开发服务器未启动的典型表现。t3code 的t3code dev命令会自动启动 Vite 和 Electron,但有时 Vite 启动失败而 Electron 仍尝试连接。排查顺序:
单独启动 Vite:
cd myapp && npm run dev # 观察终端是否输出 “Local: http://localhost:5173/” # 若卡在 “building...”,检查 `src/main.tsx` 是否有语法错误检查端口占用:
lsof -i :5173 # 若有进程占用,kill -9 PID # 或修改 vite.config.ts:server.port: 5174验证 Electron 加载地址: 在
src/main.ts中,mainWindow.loadURL()的地址应为:// 开发环境 mainWindow.loadURL('http://localhost:5173') // 生产环境 mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))如果
process.env.NODE_ENV未正确设置为'development',Electron 会尝试加载不存在的file://路径。
4.3 iOS 图标显示为白块,或 App Store 提交被拒
原因几乎全是图标尺寸或格式问题。t3code 的ios:icons命令会生成所有尺寸,但以下 3 点必须人工核对:
1024×1024 图标必须无透明度:
identify -format "%[opaque]" public/icons/app-icon.png # 应输出:True # 若为 False,用 ImageMagick 去除 alpha:convert app-icon.png -background white -alpha remove -alpha off app-icon-opaque.pngLaunchImage 尺寸必须匹配:
public/icons/splash.png应为 2732×2732(iPad Pro 12.9" 启动图),t3code 会按比例缩放。若原始图小于 2000×2000,缩放后模糊,导致审核被拒。Info.plist 中的 CFBundleIconFiles: t3code 自动生成的
Info.plist不包含此键(已弃用),但某些旧版 Xcode 会警告。手动删除CFBundleIconFiles键即可,t3code 在ios:export时已跳过此字段。
4.4 “t3code sign --identity='Apple Development'” 失败:Keychain access denied
这是 macOS Keychain 权限问题。错误信息通常为Error: Command failed: codesign ... User interaction is not allowed。解决方案:
解锁登录钥匙串:
security unlock-keychain -p your_password login.keychain-db授权 codesign 访问证书:
- 打开
钥匙串访问应用 - 找到你的
Apple Development证书 - 右键 ->
显示简介->访问控制 - 选择
允许所有应用程序访问此项目 - 点击
好
- 打开
t3code 预置的修复命令:
t3code fix-keychain --identity="Apple Development" # 该命令会执行 security set-keychain-settings -lut 3600 login.keychain-db # 并设置 codesign 的钥匙串权限
注意:企业账号的
Apple Distribution证书,必须在钥匙串访问中右键证书 ->显示简介->信任->代码签名设为始终信任,否则t3code sign会静默失败。
4.5 Windows 打包后图标显示为默认 Electron 图标
这是electron-packager的 icon 路径问题。t3code 的t3code build --platform=win会查找public/icons/windows-icon.ico,但 Windows 要求.ico文件必须包含多种尺寸(16×16, 32×32, 48×48, 256×256)。排查:
验证图标文件:
icotool -l public/icons/windows-icon.ico # 应输出至少 4 行,含 256x256 # 若只有 16x16,用 icotool 合并:icotool -o windows-icon.ico 16.png 32.png 48.png 256.png检查 packager 配置: t3code 在
config/electron-packager.js中指定:module.exports = { icon: path.join(__dirname, '../public/icons/windows-icon.ico'), win32metadata: { CompanyName: 'My Company', ProductName: 'MyApp', OriginalFilename: 'MyApp.exe' } }确保
icon路径正确,且文件存在。**终极方案