本文首发于 xlog,可跳转获得更佳阅读体验

如果你只是想找一个利用 Homebrew 安装 pnpm v7 的方案,执行 brew install ImSingee/pnpm/pnpm@7 即可

pnpm v8 在一个月前发布了,作为大版本更新之一,它引入了 lock file V6,同时停止支持了 V5。然而在给一些使用老版本的项目提 pr 的情况下,如果需要引入新的依赖就势必需要更新 lock file —— 这是不可被接受的,不能直接期望所有协作者都升级其 pnpm。

这引入了一个 pnpm V7 和 V8 共存的问题。这本不是难事,有着 Corepack 或者 pnvm 等工具。然而其对我而言都太重了 —— 一个 Homebrew 似乎就够了。

Homebrew 一个问题是,不支持安装旧版本,曾经引入过的 homebrew/versions 也早已被弃用,官方唯一建议的方案是自行托管 —— 当然,目前 pnpm 是没有维护官方旧版本的 tap 的,因此,只能自己动手喽。

创建 tap

根据官方指引,执行下面的命令即可创建一个空的 tap 库

1
brew tap-new ImSingee/homebrew-pnpm

注:这里 tap-new 后面的参数格式必须为 <repo>/homebrew-<name> ,这样可以通过 brew tap <repo>/<name> 来启用这个 tag,后续可以通过 brew install <repo>/<name>/<formula> 来直接安装相关包【另外实际上,如果 repo 名称不以 homebrew- 开头,这个命令会自动帮你加上这个前缀的】

然后会打印出类似下面的信息

1
2
3
4
5
6
7
8
9
10
11
12
Initialized empty Git repository in /opt/homebrew/Library/Taps/imsingee/homebrew-pnpm/.git/
[main (root-commit) 1b89b92] Create imsingee/pnpm tap
3 files changed, 90 insertions(+)
create mode 100644 .github/workflows/publish.yml
create mode 100644 .github/workflows/tests.yml
create mode 100644 README.md
==> Created imsingee/pnpm
/opt/homebrew/Library/Taps/imsingee/homebrew-pnpm

When a pull request making changes to a formula (or formulae) becomes green
(all checks passed), then you can publish the built bottles.
To do so, label your PR as `pr-pull` and the workflow will be triggered.

大体意思就是,帮你在 /opt/homebrew/Library/Taps/imsingee/homebrew-pnpm 下创建了一个项目,并配置好了 GitHub Action 帮你测试、配置 bottle

增加一个 formula

我们新增一个 pnpm@7 的 Formula,这里是官方的 pnpm.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Pnpm < Formula
require "language/node"

desc "📦🚀 Fast, disk space efficient package manager"
homepage "https://pnpm.io/"
url "https://registry.npmjs.org/pnpm/-/pnpm-8.3.1.tgz"
sha256 "ce038ba2617f7a93d0b1f24b733b9d64258b15c97a14c6f37673c8d49e033d9a"
license "MIT"

livecheck do
url "https://registry.npmjs.org/pnpm/latest"
regex(/["']version["']:\s*?["']([^"']+)["']/i)
end

bottle do
sha256 cellar: :any_skip_relocation, arm64_ventura: "78ecd13f60c3baf6913933c8494ca17fc4e5b9f93c46bbc131312ffe41fe7f88"
sha256 cellar: :any_skip_relocation, arm64_monterey: "78ecd13f60c3baf6913933c8494ca17fc4e5b9f93c46bbc131312ffe41fe7f88"
sha256 cellar: :any_skip_relocation, arm64_big_sur: "78ecd13f60c3baf6913933c8494ca17fc4e5b9f93c46bbc131312ffe41fe7f88"
sha256 cellar: :any_skip_relocation, ventura: "2f4f18876a3e2823f86f5500b7c47c173695e7f21eba007c2b7689dd12301145"
sha256 cellar: :any_skip_relocation, monterey: "2f4f18876a3e2823f86f5500b7c47c173695e7f21eba007c2b7689dd12301145"
sha256 cellar: :any_skip_relocation, big_sur: "4be656f6ff04e145810fb6e19f08fb01030798cec610c9d618b1fb01121d9f64"
sha256 cellar: :any_skip_relocation, x86_64_linux: "78ecd13f60c3baf6913933c8494ca17fc4e5b9f93c46bbc131312ffe41fe7f88"
end

depends_on "node" => :test

conflicts_with "corepack", because: "both installs `pnpm` and `pnpx` binaries"

def install
libexec.install buildpath.glob("*")
bin.install_symlink "#{libexec}/bin/pnpm.cjs" => "pnpm"
bin.install_symlink "#{libexec}/bin/pnpx.cjs" => "pnpx"
end

def caveats
<<~EOS
pnpm requires a Node installation to function. You can install one with:
brew install node
EOS
end

test do
system "#{bin}/pnpm", "init"
assert_predicate testpath/"package.json", :exist?, "package.json must exist"
end
end

我们要进行几个小的修改

  • 文件名官方为 pnpm.rb,我们这里要改名为 [email protected] 以示区分
  • class 名称 Pnpm 我们需要修改成 PnpmAT7
  • url 这里我们要修改成我们所需的版本,目前 7 最新的版本是 7.32.2
  • sha256 我们这里需要修改成对应版本的 hash,目前 7.32.2 对应的是 f4b40caa0c6368da2f50b8ef891f225c24f14e7d60e42a703c84d3a9db8efede
  • livecheck 节下的 url 我们修改成 https://registry.npmjs.org/pnpm、regex 为 /["']latest-7["']:\s*?"'["']/i
  • 移除 bottle

最终修改好的版本是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class PnpmAT7 < Formula
require "language/node"

desc "📦🚀 Fast, disk space efficient package manager"
homepage "https://pnpm.io/"
url "https://registry.npmjs.org/pnpm/-/pnpm-7.32.2.tgz"
sha256 "f4b40caa0c6368da2f50b8ef891f225c24f14e7d60e42a703c84d3a9db8efede"
license "MIT"

livecheck do
url "https://registry.npmjs.org/pnpm"
regex(/["']latest-7["']:\s*?["']([^"']+)["']/i)
end

depends_on "node" => :test

conflicts_with "corepack", because: "both installs `pnpm` and `pnpx` binaries"

def install
libexec.install buildpath.glob("*")
bin.install_symlink "#{libexec}/bin/pnpm.cjs" => "pnpm"
bin.install_symlink "#{libexec}/bin/pnpx.cjs" => "pnpx"
end

def caveats
<<~EOS
pnpm requires a Node installation to function. You can install one with:
brew install node
EOS
end

test do
system "#{bin}/pnpm", "init"
assert_predicate testpath/"package.json", :exist?, "package.json must exist"
end
end

修改点的一些小解释

命名(文件名、也是 formula 名字)方面,首先必须要和官方的 pnpm 做出区分,而选择 pnpm@{VERSION} 这种格式则是和官方的其他多版本包格式保持一致(例如 postgresql、llvm 等均采用这种命名)。class 的名称和文件名要保持对应,pnpm@7 所对应的是 PnpmAT7

另外,我们的 [email protected] 文件可以选择存储在前面 tap-new 命令帮我们创建好的 Formula 目录下,也可以直接放在根目录下(还可以放在 HomebrewFormula 下)

url 和 sha256 是对应的包下载地址和对应包的下载文件哈希,我们可以利用 NPM Registry API 来获取最新的版本,并下载该版本对应的包来获取哈希

1
2
3
4
5
# 获取当前的 V7 最新版本
curl -s https://registry.npmjs.org/pnpm | jq '."dist-tags"."latest-7"'

# 获取这一版本对应的哈希
curl -s https://registry.npmjs.org/pnpm/-/pnpm-7.32.2.tgz | sha256sum

livecheck 是用来检测当前是否是最新版本的,pnpm 官方的 formula 是取的 latest,我们这里修改成取 latest-7

移除 bottle,因为这是自动化构建的内容,新的 formula 不应该包括

发布

在发布前,我们需要先执行下 brew style --fix [email protected] 来确认下 style 符合规范。

我们需要利用 Github Action 来触发测试和 bottle 的构建。在提交代码后不要直接 push 到主分支,而是提交一个 PR ,在通过所有测试后给其添加 pr-pull 标签。

然后任何人就都可以直接安装我们的 pnpm@7 啦!

1
2
3
4
5
6
# case 1: enable tap
brew tap ImSingee/pnpm
brew install pnpm@7

# case 2: simple install single
brew install ImSingee/pnpm/pnpm@7

发布后的仓库在 https://github.com/ImSingee/homebrew-pnpm,欢迎使用和 star :-)

安装后的一个小 tips

命名使用 <name>@{VERSION} 的一个好处在于,其不会污染我们目前在使用的环境,这可以在安装后有一个提示看出

1
2
3
4
5
pnpm@7 is keg-only, which means it was not symlinked into /opt/homebrew,
because this is an alternate version of another formula.

If you need to have pnpm@7 first in your PATH, run:
echo 'export PATH="/opt/homebrew/opt/pnpm@7/bin:$PATH"' >> ~/.zshrc

与一般的 formula buts,这种会被检测到是另一个程序的不同版本,因此不会安装到系统的 PATH 下,而是独立的放在额外的路径。

根据提示,如果我们想要执行 7 版本的 pnpm,则需要使用全路径 /opt/homebrew/opt/pnpm@7/bin/pnpm ,如果想要让这个作为主版本的话,可以将 /opt/homebrew/opt/pnpm@7/bin 加入至 PATH 中。

当然,我的做法是做一个 symlink

1
2
ln -s /opt/homebrew/opt/pnpm@7/bin/pnpm /usr/local/bin/pnpm7
ln -s /opt/homebrew/opt/pnpm@7/bin/pnpx /usr/local/bin/pnpx7

参考

https://docs.brew.sh/How-to-Create-and-Maintain-a-Tap

https://stackoverflow.com/questions/3987683/homebrew-install-specific-version-of-formula