# Use @openai/codex dist-tags for platform binaries instead of separate package names

https://github.com/openai/codex/pull/11318 introduced logic to publish platform artifacts as separate npm packages (for example,
`@openai/codex-darwin-arm64`, `@openai/codex-linux-x64`, etc.). That requires provisioning and
maintaining multiple package entries in npm, which would be nice to avoid.

We still need to keep the package-size mitigation (platform-specific payloads), but we want that
layout to live under a single npm package namespace (`@openai/codex`) using dist-tags.

We also need to preserve pre-release workflows where users install `@openai/codex@alpha` and get
platform-appropriate binaries.

## Release Strategy (New Scheme)

We publish **one npm package name for Codex binaries** (`@openai/codex`) and use **dist-tags** to select platform-specific payloads.
This avoids creating separate platform package names while keeping the package size split by platform.

### What gets published

#### Mainline release (`x.y.z`)

- `@openai/codex@latest` (meta package)
- `@openai/codex@darwin-arm64`
- `@openai/codex@darwin-x64`
- `@openai/codex@linux-arm64`
- `@openai/codex@linux-x64`
- `@openai/codex@win32-arm64`
- `@openai/codex@win32-x64`
- `@openai/codex-responses-api-proxy@latest`
- `@openai/codex-sdk@latest`

#### Alpha release (`x.y.z-alpha.N`)

- `@openai/codex@alpha` (meta package)
- `@openai/codex@alpha-darwin-arm64`
- `@openai/codex@alpha-darwin-x64`
- `@openai/codex@alpha-linux-arm64`
- `@openai/codex@alpha-linux-x64`
- `@openai/codex@alpha-win32-arm64`
- `@openai/codex@alpha-win32-x64`
- `@openai/codex-responses-api-proxy@alpha`
- `@openai/codex-sdk@alpha`

### Important note

**Note:** Because we never created the new packages on npm for, e.g., `@openai/codex-darwin-arm64`, since #11318 was merged, there are no npm packages to "clean up" as a result of this change.

## What changed

### 1. Stage platform tarballs as `@openai/codex` with platform-specific versions

File: `codex-cli/scripts/build_npm_package.py`

- Added `CODEX_NPM_NAME = "@openai/codex"` and platform metadata `npm_tag` values:
  - `darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64`, `win32-arm64`, `win32-x64`
- For platform package staging (`codex-<platform>` inputs), switched generated `package.json` from:
  - `name = @openai/codex-<platform>`
    to:
  - `name = @openai/codex`
- Added `compute_platform_package_version(version, platform_tag)` to make platform tarball versions unique
  (`<release-version>-<platform-tag>`), because npm does not allow publishing the same `name@version`
  repeatedly for multiple platform artifacts.

### 2. Point meta package optional dependencies at dist-tags on `@openai/codex`

File: `codex-cli/scripts/build_npm_package.py`

- Updated `optionalDependencies` generation for the main `codex` package to use npm alias syntax:
  - key remains alias package name (e.g. `@openai/codex-darwin-arm64`), preserving runtime lookup behavior
  - value now resolves to `@openai/codex` by dist-tag
- Stable releases now emit:
  - `npm:@openai/codex@darwin-arm64`, etc.
- Alpha releases (`x.y.z-alpha.N`) now emit:
  - `npm:@openai/codex@alpha-darwin-arm64`, etc.

### 3. Publish with per-tarball dist-tags in release CI

File: `.github/workflows/rust-release.yml`

- Reworked npm publish step to derive publish tag per tarball filename:
  - platform tarballs publish with `<platform>` tags for stable releases
  - platform tarballs publish with `alpha-<platform>` tags for alpha releases
  - top-level tarballs (`codex`, `codex-responses-api-proxy`, `codex-sdk`) continue using the release tag policy
    (`latest` implicit for stable, `alpha` for alpha)
- Added fail-fast behavior for unexpected tarball names to avoid silent mispublishes.

### 4. Documentation update

File: `codex-cli/scripts/README.md`

- Updated release staging description to reflect that platform-native variants are published as
  dist-tagged `@openai/codex` artifacts rather than separate npm package names.

## Resulting behavior

- Mainline release:
  - `@openai/codex@latest` resolves meta package
  - meta package optional deps reference `@openai/codex@<platform-tag>`
- Alpha release:
  - users can continue installing `@openai/codex@alpha`
  - alpha meta package optional deps reference `@openai/codex@alpha-<platform-tag>`

This preserves platform-specific payload distribution while eliminating the need to manage a separate npm
package per platform.

## Validation notes

- Verified staged `package.json` output for stable and alpha meta packages includes expected alias targets.
- Verified staged platform package manifests are `name=@openai/codex` with unique platform-suffixed versions.
- Verified release publish tag derivation logic maps tarball names to expected stable and alpha platform tags.
This commit is contained in:
Michael Bolin
2026-02-10 09:39:30 -08:00
parent 3322b99900
commit 3f8d7abe3c
4 changed files with 74 additions and 12 deletions

View File

@@ -15,7 +15,8 @@ This downloads the native artifacts once, hydrates `vendor/` for each package, a
tarballs to `dist/npm/`.
When `--package codex` is provided, the staging helper builds the lightweight
`@openai/codex` meta package plus all `@openai/codex-<platform>` native packages.
`@openai/codex` meta package plus all platform-native `@openai/codex` variants
that are later published under platform-specific dist-tags.
If you need to invoke `build_npm_package.py` directly, run
`codex-cli/scripts/install_native_deps.py` first and pass `--vendor-src` pointing to the

View File

@@ -3,6 +3,7 @@
import argparse
import json
import re
import shutil
import subprocess
import sys
@@ -14,40 +15,49 @@ CODEX_CLI_ROOT = SCRIPT_DIR.parent
REPO_ROOT = CODEX_CLI_ROOT.parent
RESPONSES_API_PROXY_NPM_ROOT = REPO_ROOT / "codex-rs" / "responses-api-proxy" / "npm"
CODEX_SDK_ROOT = REPO_ROOT / "sdk" / "typescript"
CODEX_NPM_NAME = "@openai/codex"
# `npm_name` is the local optional-dependency alias consumed by `bin/codex.js`.
# The underlying package published to npm is always `@openai/codex`.
CODEX_PLATFORM_PACKAGES: dict[str, dict[str, str]] = {
"codex-linux-x64": {
"npm_name": "@openai/codex-linux-x64",
"npm_tag": "linux-x64",
"target_triple": "x86_64-unknown-linux-musl",
"os": "linux",
"cpu": "x64",
},
"codex-linux-arm64": {
"npm_name": "@openai/codex-linux-arm64",
"npm_tag": "linux-arm64",
"target_triple": "aarch64-unknown-linux-musl",
"os": "linux",
"cpu": "arm64",
},
"codex-darwin-x64": {
"npm_name": "@openai/codex-darwin-x64",
"npm_tag": "darwin-x64",
"target_triple": "x86_64-apple-darwin",
"os": "darwin",
"cpu": "x64",
},
"codex-darwin-arm64": {
"npm_name": "@openai/codex-darwin-arm64",
"npm_tag": "darwin-arm64",
"target_triple": "aarch64-apple-darwin",
"os": "darwin",
"cpu": "arm64",
},
"codex-win32-x64": {
"npm_name": "@openai/codex-win32-x64",
"npm_tag": "win32-x64",
"target_triple": "x86_64-pc-windows-msvc",
"os": "win32",
"cpu": "x64",
},
"codex-win32-arm64": {
"npm_name": "@openai/codex-win32-arm64",
"npm_tag": "win32-arm64",
"target_triple": "aarch64-pc-windows-msvc",
"os": "win32",
"cpu": "arm64",
@@ -244,6 +254,8 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
package_json_path = CODEX_CLI_ROOT / "package.json"
elif package in CODEX_PLATFORM_PACKAGES:
platform_package = CODEX_PLATFORM_PACKAGES[package]
platform_npm_tag = platform_package["npm_tag"]
platform_version = compute_platform_package_version(version, platform_npm_tag)
readme_src = REPO_ROOT / "README.md"
if readme_src.exists():
@@ -253,8 +265,8 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
codex_package_json = json.load(fh)
package_json = {
"name": platform_package["npm_name"],
"version": version,
"name": CODEX_NPM_NAME,
"version": platform_version,
"license": codex_package_json.get("license", "Apache-2.0"),
"os": [platform_package["os"]],
"cpu": [platform_package["cpu"]],
@@ -294,7 +306,10 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
if package == "codex":
package_json["files"] = ["bin"]
package_json["optionalDependencies"] = {
CODEX_PLATFORM_PACKAGES[platform_package]["npm_name"]: version
CODEX_PLATFORM_PACKAGES[platform_package]["npm_name"]: (
f"npm:{CODEX_NPM_NAME}@"
f"{compute_platform_dist_tag(version, CODEX_PLATFORM_PACKAGES[platform_package]['npm_tag'])}"
)
for platform_package in PACKAGE_EXPANSIONS["codex"]
if platform_package != "codex"
}
@@ -316,6 +331,20 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
out.write("\n")
def is_alpha_release(version: str) -> bool:
return re.match(r"^[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+$", version) is not None
def compute_platform_dist_tag(version: str, platform_tag: str) -> str:
return f"alpha-{platform_tag}" if is_alpha_release(version) else platform_tag
def compute_platform_package_version(version: str, platform_tag: str) -> str:
# npm forbids republishing the same package name/version, so each
# platform-specific tarball needs a unique version string.
return f"{version}-{platform_tag}"
def run_command(cmd: list[str], cwd: Path | None = None) -> None:
print("+", " ".join(cmd))
subprocess.run(cmd, cwd=cwd, check=True)