diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 193bcd8591..d60dfbfc90 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -615,9 +615,9 @@ jobs: NPM_TAG: ${{ needs.release.outputs.npm_tag }} run: | set -euo pipefail - tag_args=() + prefix="" if [[ -n "${NPM_TAG}" ]]; then - tag_args+=(--tag "${NPM_TAG}") + prefix="${NPM_TAG}-" fi shopt -s nullglob @@ -628,7 +628,31 @@ jobs: fi for tarball in "${tarballs[@]}"; do - npm publish "${GITHUB_WORKSPACE}/${tarball}" "${tag_args[@]}" + filename="$(basename "${tarball}")" + tag="" + + case "${filename}" in + codex-linux-*-npm-"${VERSION}".tgz|codex-darwin-*-npm-"${VERSION}".tgz|codex-win32-*-npm-"${VERSION}".tgz) + platform="${filename#codex-}" + platform="${platform%-npm-${VERSION}.tgz}" + tag="${prefix}${platform}" + ;; + codex-npm-"${VERSION}".tgz|codex-responses-api-proxy-npm-"${VERSION}".tgz|codex-sdk-npm-"${VERSION}".tgz) + tag="${NPM_TAG}" + ;; + *) + echo "Unexpected npm tarball: ${filename}" + exit 1 + ;; + esac + + publish_cmd=(npm publish "${GITHUB_WORKSPACE}/${tarball}") + if [[ -n "${tag}" ]]; then + publish_cmd+=(--tag "${tag}") + fi + + echo "+ ${publish_cmd[*]}" + "${publish_cmd[@]}" done update-branch: diff --git a/codex-cli/scripts/README.md b/codex-cli/scripts/README.md index b9d55bad28..ca0d54b544 100644 --- a/codex-cli/scripts/README.md +++ b/codex-cli/scripts/README.md @@ -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-` 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 diff --git a/codex-cli/scripts/build_npm_package.py b/codex-cli/scripts/build_npm_package.py index 066f09e7e9..f927cafa3a 100755 --- a/codex-cli/scripts/build_npm_package.py +++ b/codex-cli/scripts/build_npm_package.py @@ -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)