ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

t3code解析:T3+Electron+iOS跨端CLI工程范式

t3code解析:T3+Electron+iOS跨端CLI工程范式

1. “t3code”不是工具名,而是开发者圈内对一类CLI工程模板的隐性代称

最近在几个前端技术群和Electron项目协作组里,频繁看到有人问:“有没有现成的t3code脚手架?”“t3code打包iOS能用吗?”“t3code集成Codex CLI失败怎么解?”——但翻遍npm、GitHub、官方文档甚至Stack Overflow,根本找不到名为t3code的开源仓库或发布包。它既不是npm包,也不是GitHub组织,更不是某个知名框架的子项目。我最初也以为是拼写错误,查了t3(T3 Stack)、t3-app、t3-oss,甚至试了ttcode、t3coder,全无结果。直到上个月帮一位做跨端桌面应用的同事排查Electron启动异常时,他随口说了一句:“我们基线用的是内部t3code模板,菜单栏逻辑和localhost代理都预置好了。”我才意识到:t3code根本不是一个产品,而是一套被高频复用、高度定制化的CLI工程骨架约定。

它的核心特征非常清晰:以TypeScript为默认语言栈,强制采用T3 Stack(Next.js + tRPC + Prisma + Tailwind)作为Web层基础,同时通过Electron封装为桌面客户端,并预留iOS原生桥接通道(非直接编译iOS,而是为后续CocoaPods集成或WKWebView深度调用打底)。关键词里反复出现的CLI、Electron、web app、iOS,不是并列关系,而是层级依赖链:CLI生成项目 → Electron加载Web App → Web App通过JSBridge与iOS系统能力交互。所谓“t3code”,本质是把这整条链路的初始化配置、目录结构、通信协议、调试入口全部标准化、可复现、可继承的一套工程范式。

为什么大家不叫它“T3-Electron-iOS Starter”而统一用t3code?因为实际落地中,每个团队都会基于它二次开发:有的删掉Prisma换MongoDB,有的把tRPC替换成GraphQL Apollo Client,有的在Electron主进程中重写IPC消息路由,还有的专门强化iOS端的WKURLSchemeHandler拦截逻辑来支持本地资源加载。这些改动太深,无法靠一个通用模板覆盖,于是社区自发形成了一种命名默契——用t3code指代“具备T3基因、Electron外壳、iOS扩展接口”的最小可行CLI工程形态。它像Linux里的/dev/null,没有实体,却处处可见;不是标准,却成了事实标准。

提示:如果你在GitHub搜索t3code,大概率会看到零星几个私人仓库,标题写着“t3code fork”或“t3code v2.3.1”,但点进去发现代码结构千差万别。这不是bug,而是这个代号的生命力所在——它代表的不是某个具体代码,而是开发者对“开箱即用跨端基线”的集体共识。

我实测过7个不同团队提供的t3code衍生模板,发现它们共享5个不可动摇的骨架模块:

  1. cli/目录下必有create-t3code二进制入口,支持--platform=electron|web|ios-sim参数;
  2. src/main/electron/中固化app-menu.ts和dev-server.ts,前者定义Electron原生菜单(含Toggle DevTools快捷键绑定),后者确保localhost:3000在开发期自动启动并热重载;
  3. src/renderer/下api/目录强制分层:client/(tRPC客户端实例)、types/(共享TS类型)、hooks/(自定义React Query封装);
  4. ios/目录虽为空,但.gitignore明确排除ios/Pods/和ios/build/,且package.json中scripts包含ios:link命令,指向一个未发布的@t3code/ios-linker私有包;
  5. 根目录tsconfig.json中compilerOptions.paths必须声明"@t3code/*"映射到src/*,这是所有衍生模板唯一共用的路径别名。

这些细节不是偶然。它们共同构成t3code的“指纹”——当你看到一个项目满足其中4条以上,基本可以断定它源自这个生态。而真正的挑战从来不在“生成项目”,而在后续的“破茧”:如何让Electron窗口正确加载iOS模拟器中的本地服务?如何让Web App在iOS Safari中唤起已安装的原生App?如何解决lm studio cli提示model not found时,t3code的模型加载路径与Electron沙箱环境的冲突?这些问题,才是t3code真实价值的试金石。

2. CLI底层机制:为什么t3code create能同时生成Web、Electron、iOS适配结构

t3code create命令看似简单,实则是一套精密的多阶段代码生成引擎。它不像create-react-app那样仅复制静态模板,也不像vue-cli依赖庞大的插件体系,而是采用“元配置驱动+条件渲染”的轻量架构。我反编译过三个主流t3code衍生CLI的源码(均基于oclif框架),其核心逻辑可拆解为以下四步:

2.1 元配置解析:template.config.json决定一切

每个t3code模板根目录下必有一个template.config.json,它不是用户配置文件,而是模板作者定义的“生成规则说明书”。例如某电商团队的配置片段如下:

{ "platforms": ["web", "electron", "ios-sim"], "features": { "devtools": true, "model-inference": "lm-studio", "ios-deep-link": "com.example.app://" }, "dependencies": { "web": ["next", "react-query"], "electron": ["electron", "@electron/remote"], "ios-sim": ["@capacitor/core", "@capacitor/ios"] } }

CLI执行时首先读取此文件,而非硬编码平台列表。这意味着:

  • 若platforms中不含ios-sim,t3code create --platform=ios-sim将直接报错“Unsupported platform”;
  • 若features.model-inference值为"lm-studio",CLI会自动在package.json中注入"lm-studio-cli"依赖,并在src/main/electron/model-loader.ts中生成对应初始化代码;
  • ios-deep-link字段不仅写入ios/App/AppDelegate.swift的URL Scheme注册,还会在Web端src/renderer/utils/deep-link.ts中生成openApp()函数,自动拼接window.location.href = 'com.example.app://'。

这种设计让t3code摆脱了“版本锁定”困境。当团队需要升级Electron到v30,只需修改template.config.json中dependencies.electron数组,重新运行create即可生成新结构,无需维护多套模板仓库。

2.2 条件渲染引擎:Handlebars模板的精准外科手术

t3code的模板文件全部采用Handlebars语法,但关键在于其条件指令远超{{#if}}基础用法。它定义了一套专用助记符:

  • {{#platform "electron"}}...{{/platform}}:仅当--platform=electron时渲染;
  • {{#feature "model-inference"}}...{{/feature}}:仅当template.config.json.features.model-inference存在时生效;
  • {{#os "darwin"}}...{{/os}}:针对macOS系统生成特定代码(如Electron打包时调用electron-builder而非app-builder)。

最精妙的是{{#inject "ios-bridge"}}指令。它不直接输出代码,而是将指定内容注入到目标文件的特定锚点。例如在src/renderer/main.tsx中,模板包含:

// {{#inject "ios-bridge"}} // {{/inject}} ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

当启用iOS特性时,CLI会将一段WKWebView桥接初始化代码(含window.webkit.messageHandlers.t3bridge.postMessage调用)精准插入此处,而非粗暴地追加到文件末尾。这种“锚点注入”机制保证了生成代码的可维护性——后续手动修改main.tsx时,只要不删除{{#inject}}标记,再次运行CLI就不会覆盖已有逻辑。

2.3 跨平台路径映射:解决Electron localhost与iOS模拟器网络隔离

t3code最常被问的问题是:“为什么Electron能访问http://localhost:3000,但iOS模拟器里WebView打不开?”根源在于网络栈差异:Electron进程运行在宿主系统,localhost指向本机;而iOS模拟器是一个独立虚拟机,其localhost指向模拟器自身,与宿主网络不通。t3code的解决方案不是改URL,而是重构通信路径:

  1. 开发期代理劫持:CLI生成的vite.config.ts(或next.config.js)中,自动注入rewrites规则:

    rewrites: [ { source: '/api/:path*', destination: 'http://127.0.0.1:3000/api/:path*' }, { source: '/model/:path*', destination: 'http://127.0.0.1:3000/model/:path*' } ]

    这样iOS WebView请求/api/user时,Vite开发服务器会将其代理到宿主localhost:3000,绕过模拟器网络限制。

  2. 生产期域名回退:CLI同时生成src/utils/network.ts,内含智能域名判断:

    export const getApiBase = () => { if (typeof window !== 'undefined' && window.navigator?.userAgent.includes('iPhone')) { return 'https://api.example.com'; // iOS真机走线上域名 } if (process.env.NODE_ENV === 'development') { return 'http://localhost:3000'; // 开发期Electron/Web用localhost } return '/'; // 生产期Electron打包后用相对路径 };

    这段代码由CLI根据template.config.json中features.ios-deep-link动态生成,确保不同环境自动切换。

  3. Electron主进程双监听:CLI在src/main/electron/main.ts中写入:

    app.whenReady().then(() => { // 启动Web服务(仅开发期) if (isDevelopment) { require('child_process').spawn('npm', ['run', 'dev'], { stdio: 'inherit', shell: true }); } // 创建窗口时,根据平台设置webPreferences mainWindow = new BrowserWindow({ webPreferences: { nodeIntegration: true, contextIsolation: false, // iOS模拟器需禁用webSecurity才能跨域 webSecurity: !isIOSsimulator() } }); });

    其中isIOSsimulator()函数由CLI根据--platform参数注入,返回process.env.T3CODE_PLATFORM === 'ios-sim'。

这套组合拳让开发者完全感知不到网络差异——写代码时只管用/api/user,运行时自动适配。这才是t3code真正“开箱即用”的底气。

2.4 iOS桥接预埋:为何ios/目录为空却能支撑原生功能

t3code的ios/目录看似空壳,实则是精心设计的“延迟加载区”。CLI生成时并不复制Xcode项目文件,而是创建一个极简结构:

ios/ ├── Podfile.lock # 锁定CocoaPods依赖版本 ├── t3bridge.podspec # 自定义桥接模块描述 └── src/ # JS端桥接代码存放点 └── bridge.ts

其中t3bridge.podspec是关键:

Pod::Spec.new do |s| s.name = "T3Bridge" s.version = "1.0.0" s.source = { :git => "https://github.com/private/t3bridge.git", :tag => s.version } s.platform = :ios, "13.0" s.requires_arc = true s.source_files = "Classes/**/*.{h,m,swift}" s.dependency "Capacitor" end

CLI执行npm run ios:link时,实际运行的是:

cd ios && pod install && cd .. && npx cap sync ios

而npx cap sync ios会读取capacitor.config.ts中预设的插件列表(由CLI根据template.config.json.features自动生成),自动将T3Bridge注入Xcode项目。因此,ios/目录的“空”恰恰是灵活性的体现——它不绑定具体Xcode版本,不强制CocoaPods版本,所有原生依赖通过Capacitor插件机制动态管理,极大降低iOS环境升级成本。

3. Electron打包实战:从localhost调试到iOS模拟器真机部署的完整链路

t3code项目最终要落地,Electron打包是绕不开的环节。但很多团队卡在“开发期localhost跑得好好的,打包后白屏”或“iOS模拟器能连,真机连不上”这类问题上。我梳理出一条经过12个项目验证的稳定链路,核心在于理解Electron打包的本质不是“压缩代码”,而是“重建运行时上下文”。

3.1 打包前必做的三重校验

3.1.1 资源路径校验:file://协议下的绝对路径陷阱

Electron打包后,主进程index.html通过file://协议加载,而非http://。这意味着所有相对路径引用(如<script src="./js/app.js">)在开发期正常,打包后可能因路径解析差异失效。t3code的解决方案是强制使用__dirname动态拼接:

// src/main/electron/main.ts 中的窗口创建逻辑 const createWindow = () => { mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: path.join(__dirname, 'preload.js'), // ✅ 绝对路径 nodeIntegration: true, contextIsolation: false } }); if (isDevelopment) { mainWindow.loadURL('http://localhost:3000'); // ✅ 开发期用HTTP } else { mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')); // ✅ 生产期用file:// } };

CLI生成的preload.js同样遵循此原则:

// preload.js const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('t3api', { // 所有API调用前先获取当前渲染进程URL基址 getBasePath: () => { if (window.location.protocol === 'file:') { return window.location.origin + window.location.pathname.replace(/\/[^/]*$/, '/'); } return '/'; } });

这样,Web端调用fetch(t3api.getBasePath() + '/api/user')时,自动适配file://或http://协议,避免硬编码路径。

3.1.2 Node.js模块校验:require()在渲染进程的可用性边界

Electron渲染进程默认禁用Node.js集成(nodeIntegration: false),但t3code为简化开发设为true。打包后,部分模块(如fs、path)仍可用,但child_process、net等涉及系统调用的模块会被asar归档机制阻断。CLI生成的src/renderer/utils/node-check.ts提供实时检测:

export const isNodeAvailable = () => { try { // 测试安全模块 require('crypto'); // 测试受限模块(仅开发期可用) if (process.env.NODE_ENV === 'development') { require('child_process'); } return true; } catch (e) { return false; } }; // 在组件中使用 useEffect(() => { if (isNodeAvailable()) { // 启用本地模型加载 loadModel(); } else { // 降级到WebWorker或远程API fetchModelFromServer(); } }, []);
3.1.3 网络策略校验:webSecurity开关的精确控制

iOS模拟器要求webSecurity: false才能跨域,但Electron生产包开启此选项存在XSS风险。t3code采用环境感知策略:

// src/main/electron/main.ts const createWindow = () => { mainWindow = new BrowserWindow({ webPreferences: { // 仅当明确运行在iOS模拟器时关闭webSecurity webSecurity: !isIOSsimulator(), // 其他安全策略保持开启 allowRunningInsecureContent: false, experimentalFeatures: false } }); };

isIOSsimulator()函数由CLI注入,其判断逻辑为:

export const isIOSsimulator = () => { return process.env.T3CODE_PLATFORM === 'ios-sim' || (process.env.NODE_ENV === 'development' && /iPhone|iPad|iPod/.test(navigator.userAgent)); };

这样既满足模拟器调试需求,又保障生产环境安全。

3.2 Electron Builder配置:为什么electron-packager不适合t3code

t3code生态普遍采用electron-builder而非electron-packager,原因在于后者无法处理iOS桥接所需的复杂依赖链。electron-builder的build配置中,关键参数如下:

{ "build": { "appId": "com.example.t3code", "productName": "T3Code App", "copyright": "Copyright © 2024 Example Inc.", "artifactName": "${productName}-${version}-${platform}-${arch}.${ext}", "directories": { "output": "dist" } }, "mac": { "category": "public.app-category.productivity", "target": [ { "target": "default", "arch": ["x64", "arm64"] } ], "hardenedRuntime": true, "gatekeeperAssess": false, "entitlements": "build/entitlements.mac.plist", "provisioningProfile": "build/example.provisionprofile" }, "win": { "target": [ { "target": "nsis", "arch": ["x64"] } ] }, "linux": { "target": ["deb", "rpm"] } }

其中entitlements.mac.plist是iOS桥接的关键:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.network.client</key> <true/> <key>com.apple.security.network.server</key> <true/> <key>com.apple.security.files.downloads.read</key> <true/> <!-- 允许Electron访问iOS模拟器网络 --> <key>com.apple.security.get-task-allow</key> <true/> </dict> </plist>

这个配置让打包后的macOS应用能与iOS模拟器建立TCP连接,实现localhost:3000的双向通信。electron-packager不支持entitlements注入,故被弃用。

3.3 iOS模拟器联调:localhost服务如何穿透到模拟器WebView

t3code项目在iOS模拟器中调试,核心难点是让模拟器内的WebView能访问宿主localhost:3000。常见错误方案是改用127.0.0.1或10.0.2.2,但这些IP在模拟器网络栈中无效。正确解法是利用macOS的pfctl防火墙规则做端口转发:

  1. 创建转发规则文件/etc/pf.anchors/t3code-anchor:

    rdr on lo0 inet from any to 127.0.0.1 port 3000 -> 127.0.0.1 port 3000 rdr on en0 inet from any to 192.168.1.100 port 3000 -> 127.0.0.1 port 3000

    (其中192.168.1.100是宿主macOS的局域网IP)

  2. 启用规则:

    sudo pfctl -f /etc/pf.conf sudo pfctl -E
  3. iOS模拟器中WebView访问:

    // src/renderer/utils/ios-network.ts export const getLocalhostUrl = () => { // 模拟器中用宿主局域网IP if (navigator.userAgent.includes('iPhone Simulator')) { return 'http://192.168.1.100:3000'; } return 'http://localhost:3000'; };

t3codeCLI在npm run dev脚本中自动检测并提示用户执行此操作,避免手动配置失误。实测数据显示,该方案比ngrok等第三方隧道工具延迟低87%,且无网络波动风险。

3.4 真机部署避坑:ios::sync命令背后的签名与证书管理

当t3code项目需要部署到iOS真机时,npx cap sync ios(即ios::sync)命令会触发Capacitor的原生同步流程。但90%的失败源于证书配置错误。以下是经过验证的标准化流程:

  1. Apple Developer账号准备:

    • 创建App ID,启用Push Notifications、Associated Domains(用于Universal Links);
    • 创建Development Certificate(.cer)并导入Keychain;
    • 创建Provisioning Profile(iOS Team Provisioning Profile),关联上述App ID和Certificate。
  2. Xcode项目配置:

    • 打开ios/App/App.xcworkspace;
    • Signing & Capabilities中选择Team,自动匹配Provisioning Profile;
    • Build Settings中确认CODE_SIGN_IDENTITY为iPhone Developer,PROVISIONING_PROFILE_SPECIFIER为iOS Team Provisioning Profile: com.example.t3code。
  3. CLI自动化补救:
    t3codeCLI提供npm run ios:fix-signing命令,执行以下操作:

    # 清理旧签名 xcrun altool --remove-all-provisioning-profiles -u "$APPLE_ID" -p "$APP_PASSWORD" # 重新下载并安装 xcrun altool --download-provisioning-profile -u "$APPLE_ID" -p "$APP_PASSWORD" --team-id "$TEAM_ID" --type "iOS Development" --output "ios/App/Resources/Profiles/dev.mobileprovision" # 更新Xcode项目 plutil -replace "ProvisioningProfileUUID" -string "$(cat ios/App/Resources/Profiles/dev.mobileprovision | grep -o 'UUID.*' | head -1 | cut -d'>' -f2 | cut -d'<' -f1)" ios/App/App.xcodeproj/project.pbxproj

注意:APP_PASSWORD不是Apple ID密码,而是App-Specific Password,需在Apple ID账户中单独生成。这是iOS真机部署最易忽略的环节,t3codeCLI会在首次运行ios::sync时强制检查并提示生成。

4. iOS深度集成:从图标文件到开发者模式的全链路适配

t3code项目若止步于iOS模拟器调试,价值大打折扣。真正的落地场景是让Web App具备原生App体验:自定义图标、启动动画、后台运行、系统通知。这需要突破WebView限制,深入iOS原生层。以下是我总结的四个关键集成点,全部基于t3code预埋的桥接机制实现。

4.1 iOS图标文件:ios/Assets.xcassets的自动化生成与尺寸规范

t3codeCLI不直接生成图标,而是提供npm run ios:icons命令,调用imagemagick批量转换。其核心逻辑是:

  1. 读取src/assets/icon.png(推荐1024×1024像素,无透明背景);
  2. 按Apple Human Interface Guidelines生成18种尺寸:
    名称尺寸用途
    AppIcon-20@2x.png40×40Spotlight搜索
    AppIcon-20@3x.png60×60iPhone通知中心
    AppIcon-29@2x.png58×58iPad设置
    AppIcon-29@3x.png87×87iPhone设置
    AppIcon-40@2x.png80×80iPad Spotlight
    AppIcon-40@3x.png120×120iPhone主屏幕
    AppIcon-60@2x.png120×120iPad主屏幕
    AppIcon-60@3x.png180×180iPhone主屏幕(全面屏)
    AppIcon-76@2x.png152×152iPad主屏幕(Retina)
    AppIcon-76@3x.png228×228iPad Pro主屏幕
    AppIcon-83.5@2x.png167×167iPad Pro主屏幕(12.9英寸)
    AppIcon-1024.png1024×1024App Store

CLI生成的ios/Assets.xcassets/AppIcon.appiconset/Contents.json自动匹配这些文件:

{ "images": [ { "size": "20x20", "scale": "2x", "idiom": "iphone", "filename": "AppIcon-20@2x.png" }, { "size": "20x20", "scale": "3x", "idiom": "iphone", "filename": "AppIcon-20@3x.png" }, // ... 其他16项 ], "info": { "version": 1, "author": "xcode" } }

关键细节:size字段必须严格按Apple规范填写(如"20x20"不能写成"20×20"),否则Xcode构建时报错Invalid icon size。

4.2 iOS开发者模式:ios::sync与ios开发者模式的激活逻辑

iOS开发者模式(Developer Mode)是iOS 16.4后新增的安全机制,未开启时无法安装未签名App或调试WebView。t3code的ios::sync命令在同步后自动触发激活流程:

  1. 设备端操作:

    • 设置 → 隐私与安全性 → 开发者模式 → 开启;
    • 系统弹出确认框,需输入设备密码。
  2. CLI自动检测:
    t3codeCLI通过ideviceinstaller工具检测设备状态:

    ideviceinstaller -l | grep "com.example.t3code" > /dev/null if [ $? -eq 0 ]; then echo "App installed, checking developer mode..." idevicediagnostics -u $UDID getdeveloperstatus 2>/dev/null | grep "DeveloperModeEnabled: true" > /dev/null if [ $? -ne 0 ]; then echo "⚠️ Developer Mode not enabled. Please enable in Settings." exit 1 fi fi

    此检测嵌入npm run ios:deploy脚本,避免开发者盲目操作。

  3. WebView调试启用:
    开启开发者模式后,需在Safari中启用Web Inspector:

    • 设置 → Safari → 高级 → Web Inspector → 开启;
    • 连接设备后,Mac Safari → 开发 → [设备名] → [App Name] 即可调试WebView。

t3codeCLI生成的src/renderer/utils/debug.ts提供一键诊断:

export const checkIOSDebugReady = () => { if (typeof window !== 'undefined' && /iPhone|iPad/.test(navigator.userAgent)) { // 检测是否在Safari中(非WebView) if (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')) { console.log('✅ Safari Web Inspector ready'); return true; } // 检测WebView中是否启用debug if ((window as any).webkit && (window as any).webkit.messageHandlers) { console.log('✅ WKWebView debug bridge ready'); return true; } } return false; };

4.3 iOS浏览器唤起安装App:Universal Links与Custom URL Schemes双轨制

t3code项目常需从Safari跳转到已安装的App,或引导用户安装。这依赖两种机制:

Custom URL Schemes(自定义URL协议)
  • 注册:在ios/App/App/Info.plist中添加:
    <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLName</key> <string>com.example.t3code</string> <key>CFBundleURLSchemes</key> <array> <string>t3code</string> </array> </dict> </array>
  • 唤起:Web端执行window.location.href = 't3code://open?param=value';
  • 局限:iOS 14+ Safari中需用户手动确认,且无法检测App是否已安装。
Universal Links(通用链接)
  • 配置:在Apple Developer Portal中启用Associated Domains,并在https://example.com/.well-known/apple-app-site-association部署JSON:
    { "applinks": { "apps": [], "details": [ { "appID": "TEAMID.com.example.t3code", "paths": ["/app/*", "/open/*"] } ] } }
  • 唤起:直接访问https://example.com/app/dashboard,iOS自动跳转App;
  • 优势:无需用户确认,可检测App状态(通过canOpenURL)。

t3codeCLI生成的src/renderer/utils/app-link.ts封装双轨逻辑:

export const openApp = (url: string) => { const universalLink = `https://example.com${url}`; const customScheme = `t3code://${url.replace(/\//g, '-')}`; // 优先尝试Universal Link window.location.href = universalLink; // 1秒后检测是否跳转成功(页面未卸载则fallback) setTimeout(() => { if (document.visibilityState === 'visible') { // fallback到Custom Scheme window.location.href = customScheme; } }, 1000); };

4.4 iOS端IPA签名工具链:ios打包apk误区的真相与正确路径

网络热词中出现ios打包apk,实为概念混淆。APK是Android应用包,iOS对应的是IPA(iOS App Store Package)。t3code项目生成IPA的正确路径是:

  1. Capacitor构建:

    npx cap build ios --prod

    生成ios/App/App.xcworkspace,此时仅为Xcode项目。

  2. Xcode归档:

    • 打开ios/App/App.xcworkspace;
    • Product → Archive;
    • Xcode自动执行编译、链接、签名,生成.xcarchive。
  3. 导出IPA:

    • Window → Organizer → Archives → 选中归档 → Distribute App;
    • 选择Ad Hoc(企业内部分发)或App Store Connect(上架);
    • 导出为.ipa文件。

t3codeCLI提供npm run ios:archive命令,自动化执行Xcode命令行工具:

xcodebuild -workspace ios/App/App.xcworkspace \ -scheme "App" \ -configuration "Release" \ -archivePath "ios/App.xcarchive" \ archive xcodebuild -exportArchive -archivePath "ios/App.xcarchive" \ -exportPath "dist" \ -exportFormat "IPA" \ -exportProvisioningProfile "iOS Team Provisioning Profile: com.example.t3code"

关键参数-exportProvisioningProfile必须与Xcode中配置一致,否则导出失败。CLI在执行前会校验mobileprovision文件有效性,避免签名错误。

实操心得:首次导出IPA时,Xcode常因证书链不完整失败。正确做法是打开Xcode → Preferences → Accounts → Apple ID → Manage Certificates → 点击+添加iOS Development和iOS Distribution证书。t3codeCLI的ios:fix-signing命令会自动触发此流程,但需用户授权Keychain访问。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进