Files
qiming/qimingcode/packages/opencode/script/publish.ts

230 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bun
import { $ } from "bun"
import pkg from "../package.json"
import { Script } from "@opencode-ai/script"
import { fileURLToPath } from "url"
const dir = fileURLToPath(new URL("..", import.meta.url))
process.chdir(dir)
// 单一版本源规则:发布脚本上下文版本必须与 package.json 完全一致。
// 这样可以防止环境变量或分支上下文误导导致“主包/二进制版本错位发布”。
if (pkg.version !== Script.version) {
console.error(
`Version mismatch: package.json=${pkg.version}, publish-context=${Script.version}. ` +
"Please sync package.json version before publish.",
)
process.exit(1)
}
async function published(name: string, version: string) {
return (await $`npm view ${name}@${version} version`.nothrow()).exitCode === 0
}
async function publish(dir: string, name: string, version: string) {
// GitHub artifact downloads can drop the executable bit, and Docker uses the
// unpacked dist binaries directly rather than the published tarball.
if (process.platform !== "win32") await $`chmod -R 755 .`.cwd(dir)
if (await published(name, version)) {
console.log(`already published ${name}@${version}`)
return
}
// 避免目录中历史打包产物干扰本次发布npm publish *.tgz 期望单一目标包)。
// Bun shell 对未匹配的 glob 会报错,这里改为通过 bash 执行,确保“无 tgz 可删”也不失败。
await $`bash -lc "rm -f ./*.tgz"`.cwd(dir)
await $`bun pm pack`.cwd(dir)
await $`npm publish *.tgz --access public --tag ${Script.channel}`.cwd(dir)
}
const binaries: Record<string, string> = {}
for (const filepath of new Bun.Glob("*/package.json").scanSync({ cwd: "./dist" })) {
const packageJsonPath = `./dist/${filepath}`
const distPkg = await Bun.file(packageJsonPath).json()
// 统一使用本次发布版本,确保所有平台二进制子包与主包版本号一致。
// 这允许我们在“主包修复补丁”场景下,显式推进整套 npm 包版本(例如 1.1.85 全量对齐)。
if (distPkg.version !== Script.version) {
distPkg.version = Script.version
await Bun.file(packageJsonPath).write(JSON.stringify(distPkg, null, 2) + "\n")
}
binaries[distPkg.name] = Script.version
}
console.log("binaries", binaries)
// 主包版本应由发布上下文Script.version决定而不是被 dist 二进制版本反向驱动。
// 这样当仅修复 JS/postinstall 逻辑时,可以发布新的主包版本并继续复用已发布的二进制包版本。
const version = Script.version
await $`mkdir -p ./dist/${pkg.name}`
await $`cp -r ./bin ./dist/${pkg.name}/bin`
// 始终提供实体入口 bin/qimingcode避免依赖软链或不存在的文件映射。
await $`cp ./dist/${pkg.name}/bin/opencode ./dist/${pkg.name}/bin/${pkg.name}`
await $`cp ./script/postinstall.mjs ./dist/${pkg.name}/postinstall.mjs`
await Bun.file(`./dist/${pkg.name}/LICENSE`).write(await Bun.file("../../LICENSE").text())
await Bun.file(`./dist/${pkg.name}/package.json`).write(
JSON.stringify(
{
// 发布主聚合包qimingcode保持与历史发版行为一致。
// optionalDependencies 继续引用各平台二进制子包qimingcode-*)。
name: pkg.name,
bin: {
[pkg.name]: `./bin/${pkg.name}`,
},
scripts: {
postinstall: "bun ./postinstall.mjs || node ./postinstall.mjs",
},
version: version,
license: pkg.license,
optionalDependencies: binaries,
},
null,
2,
),
)
const tasks = Object.entries(binaries).map(async ([name]) => {
await publish(`./dist/${name}`, name, binaries[name])
})
await Promise.all(tasks)
await publish(`./dist/${pkg.name}`, `${pkg.name}`, version)
const image = "ghcr.io/anomalyco/opencode"
const platforms = "linux/amd64,linux/arm64"
const tags = [`${image}:${version}`, `${image}:${Script.channel}`]
const tagFlags = tags.flatMap((t) => ["-t", t])
// 本地/紧急发版场景下,允许只执行 npm 发布,跳过 docker/aur/homebrew 这类
// 依赖外部守护进程或凭据的后续步骤,避免因为环境问题阻塞 npm 交付。
const skipDockerPublish = process.env.SKIP_DOCKER_PUBLISH === "1"
// registries
if (!Script.preview) {
if (skipDockerPublish) {
console.log("SKIP_DOCKER_PUBLISH=1跳过 docker 与后续镜像/仓库发布步骤。")
process.exit(0)
}
await $`docker buildx build --platform ${platforms} ${tagFlags} --push .`
// Calculate SHA values
const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
const [pkgver, _subver = ""] = Script.version.split(/(-.*)/, 2)
// arch
const binaryPkgbuild = [
"# Maintainer: dax",
"# Maintainer: adam",
"",
"pkgname='opencode-bin'",
`pkgver=${pkgver}`,
`_subver=${_subver}`,
"options=('!debug' '!strip')",
"pkgrel=1",
"pkgdesc='The AI coding agent built for the terminal.'",
"url='https://github.com/anomalyco/opencode'",
"arch=('aarch64' 'x86_64')",
"license=('MIT')",
"provides=('opencode')",
"conflicts=('opencode')",
"depends=('ripgrep')",
"",
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.tar.gz::https://github.com/anomalyco/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.tar.gz")`,
`sha256sums_aarch64=('${arm64Sha}')`,
`source_x86_64=("\${pkgname}_\${pkgver}_x86_64.tar.gz::https://github.com/anomalyco/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-x64.tar.gz")`,
`sha256sums_x86_64=('${x64Sha}')`,
"",
"package() {",
' install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"',
"}",
"",
].join("\n")
for (const [pkg, pkgbuild] of [["opencode-bin", binaryPkgbuild]]) {
for (let i = 0; i < 30; i++) {
try {
await $`rm -rf ./dist/aur-${pkg}`
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
await $`cd ./dist/aur-${pkg} && git checkout master`
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild)
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
if ((await $`cd ./dist/aur-${pkg} && git diff --cached --quiet`.nothrow()).exitCode === 0) break
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${Script.version}"`
await $`cd ./dist/aur-${pkg} && git push`
break
} catch {
continue
}
}
}
// Homebrew formula
const homebrewFormula = [
"# typed: false",
"# frozen_string_literal: true",
"",
"# This file was generated by GoReleaser. DO NOT EDIT.",
"class Opencode < Formula",
` desc "The AI coding agent built for the terminal."`,
` homepage "https://github.com/anomalyco/opencode"`,
` version "${Script.version.split("-")[0]}"`,
"",
` depends_on "ripgrep"`,
"",
" on_macos do",
" if Hardware::CPU.intel?",
` url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-darwin-x64.zip"`,
` sha256 "${macX64Sha}"`,
"",
" def install",
' bin.install "opencode"',
" end",
" end",
" if Hardware::CPU.arm?",
` url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-darwin-arm64.zip"`,
` sha256 "${macArm64Sha}"`,
"",
" def install",
' bin.install "opencode"',
" end",
" end",
" end",
"",
" on_linux do",
" if Hardware::CPU.intel? and Hardware::CPU.is_64_bit?",
` url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-linux-x64.tar.gz"`,
` sha256 "${x64Sha}"`,
" def install",
' bin.install "opencode"',
" end",
" end",
" if Hardware::CPU.arm? and Hardware::CPU.is_64_bit?",
` url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-linux-arm64.tar.gz"`,
` sha256 "${arm64Sha}"`,
" def install",
' bin.install "opencode"',
" end",
" end",
" end",
"end",
"",
"",
].join("\n")
const token = process.env.GITHUB_TOKEN
if (!token) {
console.error("GITHUB_TOKEN is required to update homebrew tap")
process.exit(1)
}
const tap = `https://x-access-token:${token}@github.com/anomalyco/homebrew-tap.git`
await $`rm -rf ./dist/homebrew-tap`
await $`git clone ${tap} ./dist/homebrew-tap`
await Bun.file("./dist/homebrew-tap/opencode.rb").write(homebrewFormula)
await $`cd ./dist/homebrew-tap && git add opencode.rb`
if ((await $`cd ./dist/homebrew-tap && git diff --cached --quiet`.nothrow()).exitCode !== 0) {
await $`cd ./dist/homebrew-tap && git commit -m "Update to v${Script.version}"`
await $`cd ./dist/homebrew-tap && git push`
}
}