字节笔记本
2026年6月4日
Swift CLI 项目改成 npm 分发的完整方案
可以,Swift CLI 项目完全可以改成 npm 分发。
推荐路线是:
Swift 负责真正的 CLI 能力,npm 只负责安装入口和分发。
也就是说,用户最终这样用:
npm i -g your-cli
your-cli或者:
npx your-clinpm 的关键能力是 package.json 里的 bin 字段,它可以把某个本地文件注册成命令行命令;全局安装后,这个命令会被链接到系统可执行路径里。Swift 这边继续用 Swift Package Manager 构建 executable target 即可,Swift 官方也支持 executable target 这种命令行目标。
最小可行方案
项目结构可以改成这样:
your-cli/
├── Package.swift
├── Sources/
│ └── YourCLI/
│ └── main.swift
├── bin/
│ └── your-cli.js
├── scripts/
│ └── build-swift.mjs
├── package.json
└── README.md核心思路:
npm install
↓
执行 postinstall
↓
swift build -c release
↓
生成 .build/release/your-cli
↓
bin/your-cli.js 调用这个 Swift 二进制npm 支持 postinstall、prepare 等生命周期脚本,可以在安装或发布阶段自动执行构建逻辑。
package.json 示例
{
"name": "your-cli",
"version": "0.1.0",
"description": "A Swift CLI distributed by npm",
"type": "module",
"bin": {
"your-cli": "bin/your-cli.js"
},
"scripts": {
"build": "swift build -c release",
"postinstall": "node scripts/build-swift.mjs",
"prepublishOnly": "npm run build"
},
"files": [
"bin",
"scripts",
"Sources",
"Package.swift",
"README.md"
],
"engines": {
"node": ">=18"
},
"os": [
"darwin",
"linux"
],
"license": "MIT"
}这里的 bin 就是 npm 分发 CLI 的关键。
bin/your-cli.js
这个 JS 文件只是一个壳,负责找到 Swift 编译出来的二进制并执行。
#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import fs from "node:fs";
const __dirname = dirname(fileURLToPath(import.meta.url));
const binaryPath = resolve(
__dirname,
"../.build/release/your-cli"
);
if (!fs.existsSync(binaryPath)) {
console.error("Swift binary not found. Try reinstalling the package.");
process.exit(1);
}
const result = spawnSync(binaryPath, process.argv.slice(2), {
stdio: "inherit"
});
process.exit(result.status ?? 1);注意文件要加执行权限:
chmod +x bin/your-cli.jsscripts/build-swift.mjs
import { spawnSync } from "node:child_process";
const result = spawnSync("swift", ["build", "-c", "release"], {
stdio: "inherit"
});
if (result.status !== 0) {
console.error("\nFailed to build Swift CLI.");
console.error("Please make sure Swift is installed:");
console.error("https://www.swift.org/install/");
process.exit(result.status ?? 1);
}这个方案的优点是简单,缺点是用户机器上必须安装 Swift。
Package.swift 示例
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "YourCLI",
platforms: [
.macOS(.v13)
],
products: [
.executable(
name: "your-cli",
targets: ["YourCLI"]
)
],
targets: [
.executableTarget(
name: "YourCLI"
)
]
)Swift CLI 的入口仍然是:
Sources/YourCLI/main.swift比如:
import Foundation
print("Hello from Swift CLI")发布流程
先登录 npm:
npm login本地测试:
npm pack
npm i -g ./your-cli-0.1.0.tgz
your-cli没问题后发布:
npm publish之后用户就可以:
npm i -g your-cli
your-cli或者:
npx your-cli更推荐的正式方案:预编译二进制
上面的方案适合 MVP,但正式分发最好不要让用户本地编译 Swift。
更好的方式是:
npm install your-cli
↓
根据系统判断平台
↓
下载或选择对应二进制
↓
直接运行例如拆成:
your-cli
@your-scope/your-cli-darwin-arm64
@your-scope/your-cli-darwin-x64
@your-scope/your-cli-linux-x64主包 your-cli 只负责入口,真正的 Swift 二进制放在不同平台包里。
这种方式体验最好:
npm i -g your-cli用户不需要安装 Swift,也不需要本地编译。
我建议你这样选
如果只是先跑通:
方案一:npm postinstall 里执行 swift build优点是最快,半小时就能改完。
如果准备公开发布:
方案二:GitHub Actions 编译多平台二进制,再用 npm 分发优点是用户体验接近 Node CLI,安装快,不依赖 Swift 环境。
你的项目如果主要面向 macOS 用户,我建议先只做:
darwin-arm64
darwin-x64也就是 Apple Silicon 和 Intel Mac 两个包。Linux 可以后面再加,Windows 暂时不建议优先支持,Swift CLI 在 Windows 上分发成本会明显高一些。
一句话总结:
Swift CLI 改 npm 分发,本质不是重写项目,而是给 Swift 二进制套一层 npm 安装壳。先用 postinstall 跑通,再升级到预编译二进制分发。