diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 572d5af759..3b2f5c2781 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -13,7 +13,8 @@ future `packages/codev-*`) never conflict and are not tracked here. | --- | --- | --- | --- | | `packages/core/src/global.ts` | `const app = "opencode"` → `"codev-code"` | Config/data/cache/state dirs under `~/.config/codev-code` etc. | Keep ours (the one constant); take upstream's surrounding changes. | | `packages/opencode/src/index.ts` | `scriptName("opencode")` → `"codev"`; `show()` prefix check `"opencode "` → `"codev "` | Help/usage text shows the real user-facing command (the `codev` hub forwards unknown commands to this binary). 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. | +| `package.json` (root) | Added `release` script delegating to packages/opencode | `bun run release` works from the repo root. | Keep our release line; take upstream's other fields. | +| `packages/opencode/package.json` | bin key `opencode` → `codev-code` (shim path `./bin/opencode` unchanged); added `release` script | Installed binary is named `codev-code`; `bun run release` publishes the version in this file (upstream-synced; script/release.ts is fork-only). | Keep our bin key and release line; take upstream's other fields including `version`. | | `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 `codev-code-` (dist dir names unchanged — the bun compile target derives from them) | npm publishing under the fork's names. | Keep our `name:` line; take upstream's surrounding changes. | | `packages/opencode/script/publish.ts` | `PUBLISH_NAME = "codev-code"` const; published-name→dir map; main pkg name + bin key; env-guarded exit before the Docker/AUR/Homebrew section | npm-only publishing under the fork's names (unscoped, public on npmjs); upstream's other channels target anomalyco infra. | Keep our names + early exit; take upstream's npm-section changes above the exit. | @@ -31,11 +32,12 @@ future `packages/codev-*`) never conflict and are not tracked here. direct `codev-code upgrade` would still install stock opencode. Patch or disable the upgrade command before distributing outside the team. - **Publishing**: npm packages publish as `codev-code` / `codev-code-` — unscoped, - therefore PUBLIC on npmjs (like `codev-ai`). Local-only for now (no CI workflow), run from - `packages/opencode` with npm publish auth in place: - `OPENCODE_VERSION= OPENCODE_CHANNEL=latest bun run script/build.ts && bun run script/publish.ts`. - Docker/AUR/Homebrew/GitHub-release channels are upstream-only and disabled. The binary - inside platform packages is still named `opencode` (cosmetic). + therefore PUBLIC on npmjs (like `codev-ai`). Local-only for now (no CI workflow): run + `bun run release [channel] [--skip-build]` from `packages/opencode` with npm publish + auth in place. The version comes from package.json (upstream-synced — releases track + the upstream version the fork was built from; suffix like `-codev.1` for fork-only + re-releases). Docker/AUR/Homebrew/GitHub-release channels are upstream-only and + disabled. The binary inside platform packages is still named `opencode` (cosmetic). - **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. diff --git a/README.md b/README.md index bb958055d2..540b484ba0 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,28 @@ bun test Upstream's [AGENTS.md](AGENTS.md) and [CONTRIBUTING.md](CONTRIBUTING.md) conventions (commit style, code style, package layout) apply here unchanged. +## Publishing to npm + +Releases publish the unscoped — therefore **public** — [`codev-code`](https://www.npmjs.com/package/codev-code) package plus one `codev-code--` binary package per platform. The version comes from `packages/opencode/package.json` (single source of truth). That field is upstream-synced, so each release carries the upstream version the fork was built from. + +```sh +npm login # once per machine +bun run release # from the repo root: builds all platforms (~15-20 min), then publishes +``` + +Variants: + +```sh +bun run release beta # publish under the `beta` dist-tag instead of `latest` +bun run release --skip-build # reuse an existing dist/ (must match the package.json version) +``` + +Notes: + +- With 2FA-on-publish, npm may prompt once per package (13 of them). A granular automation token avoids that: `npm config set //registry.npmjs.org/:_authToken `. +- The wrapper refuses to run when the version is already on npm. For a fork-only re-release between upstream syncs, set a suffixed version in `packages/opencode/package.json` (e.g. `1.17.13-codev.1`); the next sync takes upstream's value back. +- Verify after publishing: `npm view codev-code` and `npm install -g codev-code && codev-code --version`. + ## Syncing with upstream A scheduled GitHub Action ([`sync-upstream.yml`](.github/workflows/sync-upstream.yml)) merges upstream `dev` into `main` weekly and opens a PR. It can also be run on demand (Actions → sync-upstream → Run workflow) against any upstream branch or tag. diff --git a/package.json b/package.json index fd86ad3dec..a6cd9fc00e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "packageManager": "bun@1.3.14", "scripts": { "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts", + "release": "bun run --cwd packages/opencode release", "dev:desktop": "bun --cwd packages/desktop dev", "dev:web": "bun --cwd packages/app dev", "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 3f0e2637e2..bca039295f 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -12,6 +12,7 @@ "bench:test": "bun run script/bench-test-suite.ts", "profile:test": "bun run script/profile-test-files.ts", "build": "bun run script/build.ts", + "release": "bun run script/release.ts", "dev": "bun run --conditions=browser ./src/index.ts", "dev:temporary": "bun run --conditions=browser ./src/temporary.ts" }, diff --git a/packages/opencode/script/release.ts b/packages/opencode/script/release.ts new file mode 100644 index 0000000000..feccc78194 --- /dev/null +++ b/packages/opencode/script/release.ts @@ -0,0 +1,65 @@ +#!/usr/bin/env bun +import { $ } from "bun" +import pkg from "../package.json" + +// Local release wrapper (fork-only file): `bun run release [channel] [--skip-build]`. +// +// The version is read from package.json — the single source of truth. That +// field stays upstream-synced (their release pipeline rewrites it), so fork +// releases track the upstream version they were built from. For a fork-only +// re-release between upstream syncs, bump with a suffix (e.g. 1.17.13-codev.1) +// and let the next sync take upstream's value back. +// +// Channel is the npm dist-tag and defaults to `latest`; pass e.g. `beta` for +// pre-releases. `--skip-build` publishes an existing dist/ after verifying it +// was built with the same version (the build stamps the version into the +// binaries and manifests). +const skipBuild = process.argv.includes("--skip-build") +const args = process.argv.slice(2).filter((a) => a !== "--skip-build") +const version = pkg.version +const channel = args[0] ?? "latest" + +if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(version)) { + console.error(`package.json version ${JSON.stringify(version)} is not a valid release version.`) + process.exit(1) +} + +console.log(`Releasing codev-code@${version} (dist-tag: ${channel}${skipBuild ? ", skipping build" : ""})`) + +if (skipBuild) { + const manifests = [...new Bun.Glob("*/package.json").scanSync({ cwd: `${import.meta.dirname}/../dist` })] + const built = manifests.length + ? ((await Bun.file(`${import.meta.dirname}/../dist/${manifests[0]}`).json()) as { version: string }).version + : undefined + if (built !== version) { + console.error( + built + ? `dist/ was built for version ${built}, but package.json says ${version} — rerun without --skip-build.` + : "No dist/ found — rerun without --skip-build.", + ) + process.exit(1) + } +} + +// Fail fast on auth before a 15+ minute build. With 2FA-on-publish npm may +// still prompt per published package (13 of them); a granular token +// (`npm config set //registry.npmjs.org/:_authToken `) avoids that. +if ((await $`npm whoami`.nothrow().quiet()).exitCode !== 0) { + console.error("Not logged in to npm. Run `npm login` (or configure an auth token) first.") + process.exit(1) +} + +// publish.ts skips already-published versions, so a forgotten bump (or an +// unsynced upstream version) would otherwise "succeed" while publishing +// nothing. Catch it up front. +if ((await $`npm view codev-code@${version} version`.nothrow().quiet()).exitCode === 0) { + console.error(`codev-code@${version} is already published — bump the version in packages/opencode/package.json.`) + process.exit(1) +} + +const env = { ...process.env, OPENCODE_VERSION: version, OPENCODE_CHANNEL: channel } +if (!skipBuild) await $`bun ${import.meta.dirname}/build.ts`.env(env) +await $`bun ${import.meta.dirname}/publish.ts`.env(env) + +console.log(`\nReleased codev-code@${version} (dist-tag: ${channel})`) +console.log(`Verify: npm view codev-code@${version} && npm install -g codev-code`)