Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/publish-codev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: publish-codev

# Publishes @quickbeard/codev-code (+ platform binary packages) to the private
# npm registry. Manual-only: versioning is explicit, no auto-bumps.
#
# Required repo secrets:
# CODEV_NPM_TOKEN auth token (npm automation token for npmjs, or the
# self-hosted registry's token)
# Optional:
# CODEV_NPM_REGISTRY defaults to https://registry.npmjs.org (packages are
# published with restricted access)
on:
workflow_dispatch:
inputs:
version:
description: "Version to publish (e.g. 1.0.0)"
required: true
channel:
description: "npm dist-tag"
default: "latest"
required: false

concurrency: publish-codev

jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Install dependencies
run: bun install

- name: Configure npm for the private registry
env:
REGISTRY: ${{ secrets.CODEV_NPM_REGISTRY }}
TOKEN: ${{ secrets.CODEV_NPM_TOKEN }}
run: |
if [ -z "$TOKEN" ]; then
echo "::error::The CODEV_NPM_TOKEN secret must be set."
exit 1
fi
REGISTRY="${REGISTRY:-https://registry.npmjs.org}"
# Route the @quickbeard scope to the private registry and attach the
# auth token to that host (npmrc's //host/path:_authToken form).
hostpath="${REGISTRY#*://}"
hostpath="${hostpath%/}"
{
echo "@quickbeard:registry=${REGISTRY%/}/"
echo "//${hostpath}/:_authToken=${TOKEN}"
} >> ~/.npmrc

- name: Build all platform binaries
working-directory: packages/opencode
env:
OPENCODE_VERSION: ${{ inputs.version }}
OPENCODE_CHANNEL: ${{ inputs.channel }}
run: bun run script/build.ts

- name: Publish to private registry
working-directory: packages/opencode
env:
OPENCODE_VERSION: ${{ inputs.version }}
OPENCODE_CHANNEL: ${{ inputs.channel }}
run: bun run script/publish.ts
20 changes: 13 additions & 7 deletions DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,25 @@ future `packages/codev-*`) never conflict and are not tracked here.
| `packages/opencode/src/index.ts` | `scriptName("opencode")` → `"codev code"`; `show()` prefix check `"opencode "` → `"codev code "` | Help/usage text shows the real user-facing command. The `show()` check must always match the scriptName. | Keep our two strings; take upstream's surrounding changes. |
| `packages/opencode/package.json` | bin key `opencode` → `codev-code` (shim path `./bin/opencode` unchanged) | Installed binary is named `codev-code`. | Keep our bin key; take upstream's other fields. |
| `bun.lock` | Mirrors the bin key rename above | Lockfile records workspace bin mappings. | Regenerate with `bun install`; never hand-merge. |
| `packages/opencode/script/build.ts` | Platform packages' `package.json` name scoped to `@quickbeard/codev-code-<platform>` (dist dir names unchanged — the bun compile target derives from them) | Private-registry publishing. | Keep our `name:` line; take upstream's surrounding changes. |
| `packages/opencode/script/publish.ts` | `PUBLISH_NAME = "@quickbeard/codev-code"` const; scoped-name→dir map; main pkg name + bin key; `process.exit(0)` before the Docker/AUR/Homebrew section | npm-only publishing to the private registry; upstream's other channels target anomalyco infra. | Keep our names + early exit; take upstream's npm-section changes above the exit. |
| `packages/opencode/bin/opencode` | `base` prefix `opencode-` → `@quickbeard/codev-code-` | Runtime platform-binary lookup must match the published names. | Keep our prefix. |
| `packages/opencode/script/postinstall.mjs` | `base` prefix `opencode-` → `@quickbeard/codev-code-` | Install-time platform-binary fetch must match the published names. | Keep our prefix. |
| `README.md` | Replaced with fork README | Fork identity, sync + divergence policy. | Keep ours entirely. |
| `README.*.md` (21 translations) | Deleted | Upstream marketing translations, irrelevant to the fork. | Keep deleted (`git rm` the upstream side of modify/delete conflicts). |
| `screenshot-uk.png` | Deleted | Orphaned README-translation asset; referenced by nothing. | Keep deleted. |

## Known rebrand gaps (deliberate, revisit before external distribution)

- **Auto-update**: upstream's updater installs from upstream's release channel. A distributed
`codev-code` binary must not update itself into stock opencode. Ship with
`OPENCODE_DISABLE_AUTOUPDATE=1` (e.g. set by the `codev` wrapper) or patch the installation
module before building release binaries.
- **Build/publish pipeline** (`packages/opencode/script/build.ts`, publish workflows): still
produces `opencode`-named artifacts and publishes to upstream's npm names. Rework when we
set up internal distribution.
- **Auto-update**: upstream's updater (`packages/core/src/installation/`) still hardcodes
`opencode-ai` and upstream's release channels. The codev hub launches the agent with
`OPENCODE_DISABLE_AUTOUPDATE=1`, so the notify/auto path is off in normal use — but a
direct `codev-code upgrade` would still install stock opencode. Patch or disable the
upgrade command before distributing outside the team.
- **Publishing**: npm packages go to the private registry via `.github/workflows/publish-codev.yml`
(manual dispatch; requires `CODEV_NPM_REGISTRY` + `CODEV_NPM_TOKEN` secrets and an explicit
version). Docker/AUR/Homebrew/GitHub-release channels are upstream-only and disabled.
The binary inside platform packages is still named `opencode` (cosmetic, keeps diffs small).
- **UI branding**: TUI logo (`UI.logo()`), desktop/web app names and assets — all still
upstream-branded. Cosmetic; not worth the merge-conflict surface until the product ships
beyond the team.
Expand Down
5 changes: 4 additions & 1 deletion packages/opencode/bin/opencode
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ let arch = archMap[os.arch()]
if (!arch) {
arch = os.arch()
}
const base = "opencode-" + platform + "-" + arch
// Fork: platform binary packages are published under the private @quickbeard
// scope (see script/publish.ts). The scope becomes a directory segment in the
// node_modules lookups below.
const base = "@quickbeard/codev-code-" + platform + "-" + arch
const binary = platform === "windows" ? "opencode.exe" : "opencode"

function supportsAvx2() {
Expand Down
5 changes: 4 additions & 1 deletion packages/opencode/script/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,10 @@ for (const item of targets) {
await Bun.file(`dist/${name}/package.json`).write(
JSON.stringify(
{
name,
// Published under the private @quickbeard scope. The dist DIRECTORY
// keeps the pkg.name-derived name — the bun compile target string is
// derived from it above (name.replace(pkg.name, "bun")).
name: name.replace(pkg.name, "@quickbeard/codev-code"),
version: Script.version,
preferUnplugged: true,
os: [item.os],
Expand Down
4 changes: 3 additions & 1 deletion packages/opencode/script/postinstall.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ const archMap = {

const platform = platformMap[os.platform()] ?? os.platform()
const arch = archMap[os.arch()] ?? os.arch()
const base = `opencode-${platform}-${arch}`
// Fork: platform binary packages live under the private @quickbeard scope
// (must match bin/opencode and script/publish.ts).
const base = `@quickbeard/codev-code-${platform}-${arch}`
const sourceBinary = platform === "windows" ? "opencode.exe" : "opencode"
const targetBinary = path.join(__dirname, "bin", "opencode.exe")

Expand Down
33 changes: 25 additions & 8 deletions packages/opencode/script/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { fileURLToPath } from "url"
const dir = fileURLToPath(new URL("..", import.meta.url))
process.chdir(dir)

// Fork: published names live under the private @quickbeard scope so a single
// npmrc scope mapping (`@quickbeard:registry=<url>`) routes install AND the
// platform-binary optionalDependencies to the private registry. dist/
// directory names stay pkg.name-derived (build.ts derives the bun compile
// target from them); only package.json `name` fields are scoped.
const PUBLISH_NAME = "@quickbeard/codev-code"

async function published(name: string, version: string) {
return (await $`npm view ${name}@${version} version`.nothrow()).exitCode === 0
}
Expand All @@ -20,13 +27,18 @@ async function publish(dir: string, name: string, version: string) {
return
}
await $`bun pm pack`.cwd(dir)
await $`npm publish *.tgz --access public --tag ${Script.channel}`.cwd(dir)
// restricted: the fork is commercial — scoped packages must never land
// public on npmjs. (Self-hosted registries ignore the flag.)
await $`npm publish *.tgz --access restricted --tag ${Script.channel}`.cwd(dir)
}

const binaries: Record<string, string> = {}
// Scoped published name -> unscoped dist directory (they differ in this fork).
const dirs: Record<string, string> = {}
for (const filepath of new Bun.Glob("*/package.json").scanSync({ cwd: "./dist" })) {
const pkg = await Bun.file(`./dist/${filepath}`).json()
binaries[pkg.name] = pkg.version
dirs[pkg.name] = filepath.replace("/package.json", "")
}
console.log("binaries", binaries)
const version = Object.values(binaries)[0]
Expand All @@ -37,15 +49,15 @@ 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}/bin/${pkg.name}.exe`).write(
[
`echo "Error: ${pkg.name}-ai's postinstall script was not run." >&2`,
`echo "Error: ${PUBLISH_NAME}'s postinstall script was not run." >&2`,
'echo "" >&2',
'echo "This occurs when using --ignore-scripts during installation, or when using a" >&2',
'echo "package manager like pnpm that does not run postinstall scripts by default." >&2',
'echo "" >&2',
'echo "To fix this, run the postinstall script manually:" >&2',
`echo " cd node_modules/${pkg.name}-ai && node postinstall.mjs" >&2`,
`echo " cd node_modules/${PUBLISH_NAME} && node postinstall.mjs" >&2`,
'echo "" >&2',
`echo "Or reinstall ${pkg.name}-ai without the --ignore-scripts flag." >&2`,
`echo "Or reinstall ${PUBLISH_NAME} without the --ignore-scripts flag." >&2`,
"exit 1",
"",
].join("\n"),
Expand All @@ -54,9 +66,9 @@ await Bun.file(`./dist/${pkg.name}/bin/${pkg.name}.exe`).write(
await Bun.file(`./dist/${pkg.name}/package.json`).write(
JSON.stringify(
{
name: pkg.name + "-ai",
name: PUBLISH_NAME,
bin: {
[pkg.name]: `./bin/${pkg.name}.exe`,
"codev-code": `./bin/${pkg.name}.exe`,
},
scripts: {
postinstall: "node ./postinstall.mjs",
Expand All @@ -73,10 +85,15 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
)

const tasks = Object.entries(binaries).map(async ([name]) => {
await publish(`./dist/${name}`, name, binaries[name])
await publish(`./dist/${dirs[name]}`, name, binaries[name])
})
await Promise.all(tasks)
await publish(`./dist/${pkg.name}`, `${pkg.name}-ai`, version)
await publish(`./dist/${pkg.name}`, PUBLISH_NAME, version)

// Fork: npm packages only. Upstream's Docker/AUR/Homebrew distribution below
// targets anomalyco-owned registries and taps; keep the code for cheap merges
// but don't run it. (Env-guarded so tsc doesn't mark the rest unreachable.)
if (!process.env.CODEV_PUBLISH_UPSTREAM_CHANNELS) process.exit(0)

const image = "ghcr.io/anomalyco/opencode"
const platforms = "linux/amd64,linux/arm64"
Expand Down
Loading