diff --git a/README.md b/README.md index 7dc103adb1..5bd367a4ec 100644 --- a/README.md +++ b/README.md @@ -636,17 +636,21 @@ The **DCO check** blocks merges until every commit in the PR carries the footer ### Releasing `codex` -To publish a new version of the CLI, run the following in the `codex-cli` folder to stage the release in a temporary directory: +To publish a new version of the CLI you first need to stage the npm package. A +helper script in `codex-cli/scripts/` does all the heavy lifting. Inside the +`codex-cli` folder run: -``` +```bash +# Classic, JS implementation that includes small, native binaries for Linux sandboxing. pnpm stage-release -``` -Note you can specify the folder for the staged release: - -``` +# Optionally specify the temp directory to reuse between runs. RELEASE_DIR=$(mktemp -d) -pnpm stage-release "$RELEASE_DIR" +pnpm stage-release --tmp "$RELEASE_DIR" + +# "Fat" package that additionally bundles the native Rust CLI binaries for +# Linux. End-users can then opt-in at runtime by setting CODEX_RUST=1. +pnpm stage-release --native ``` Go to the folder where the release is staged and verify that it works as intended. If so, run the following from the temp folder: diff --git a/codex-cli/bin/codex.js b/codex-cli/bin/codex.js old mode 100755 new mode 100644 index 1df18d1fa3..e01b014318 --- a/codex-cli/bin/codex.js +++ b/codex-cli/bin/codex.js @@ -1,11 +1,79 @@ #!/usr/bin/env node +// Unified entry point for the Codex CLI. +/* + * Behavior + * ========= + * 1. By default we import the JavaScript implementation located in + * dist/cli.js (exactly what the original entry point did). + * + * 2. Developers can opt-in to a pre-compiled Rust binary by setting the + * environment variable CODEX_RUST to a truthy value (`1`, `true`, etc.). + * When that variable is present we resolve the correct binary for the + * current platform / architecture and execute it via child_process. + * + * At the moment the npm package only bundles Linux binaries that were + * added when the release was staged with + * + * pnpm stage-release --native + * + * On unsupported systems (or if the binary is missing) we fall back to + * the JS implementation so that the CLI remains functional everywhere. + */ -// Unified entry point for Codex CLI on all platforms -// Dynamically loads the compiled ESM bundle in dist/cli.js - +import { spawnSync } from 'child_process'; +import fs from 'fs'; import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; +// Determine whether the user explicitly wants the Rust CLI. + +const wantsNative = (() => { + if (!process.env.CODEX_RUST) {return false;} + const val = process.env.CODEX_RUST.toLowerCase(); + return ['1', 'true', 'yes'].includes(val); +})(); + +// Try native binary first (only when requested). + +if (wantsNative) { + const platform = process.platform; // 'linux', 'darwin', etc. + const arch = process.arch; // 'x64', 'arm64', etc. + + let targetTriple; + if (platform === 'linux') { + if (arch === 'x64') {targetTriple = 'x86_64-unknown-linux-musl';} + if (arch === 'arm64') {targetTriple = 'aarch64-unknown-linux-gnu';} + } else if (platform === 'darwin') { + if (arch === 'x64') {targetTriple = 'x86_64-apple-darwin';} + if (arch === 'arm64') {targetTriple = 'aarch64-apple-darwin';} + } else { + throw new Error(`Unsupported platform: ${platform} (${arch})`); + } + + if (targetTriple) { + // __dirname equivalent in ESM + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + + const binaryPath = path.join(__dirname, '..', 'bin', `codex-${targetTriple}`); + + if (fs.existsSync(binaryPath)) { + const result = spawnSync(binaryPath, process.argv.slice(2), { + stdio: 'inherit', + }); + + const exitCode = typeof result.status === 'number' ? result.status : 0; + process.exit(exitCode); + } else { + console.warn(`[codex-cli] Native binary not found at ${binaryPath}. Falling back to JS implementation...`); + } + } else { + console.warn('[codex-cli] Platform not yet supported by native binary. Falling back to JS implementation...'); + } +} + +// Fallback: execute the original JavaScript CLI. + // Determine this script's directory const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); diff --git a/codex-cli/scripts/install_native_deps.sh b/codex-cli/scripts/install_native_deps.sh index 2b2768af88..736cf0089e 100755 --- a/codex-cli/scripts/install_native_deps.sh +++ b/codex-cli/scripts/install_native_deps.sh @@ -1,20 +1,44 @@ -#!/bin/bash +#!/usr/bin/env bash -# Copy the Linux sandbox native binaries into the bin/ subfolder of codex-cli/. +# Install native runtime dependencies for codex-cli. # -# Usage: -# ./scripts/install_native_deps.sh [CODEX_CLI_ROOT] +# By default the script copies the sandbox binaries that are required at +# runtime. When called with the flag --rust (or --native) it additionally +# bundles pre-built Rust CLI binaries so that the resulting npm package can run +# the native implementation when users set CODEX_RUST=1. # -# Arguments -# [CODEX_CLI_ROOT] – Optional. If supplied, it should be the codex-cli -# folder that contains the package.json for @openai/codex. +# Usage +# install_native_deps.sh [RELEASE_ROOT] [--full-native] # -# When no argument is given we assume the script is being run directly from a -# development checkout. In that case we install the binaries into the -# repository’s own `bin/` directory so that the CLI can run locally. +# The optional RELEASE_ROOT is the path that contains package.json. Omitting +# it installs the binaries into the repository's own bin/ folder to support +# local development. set -euo pipefail +# ------------------ +# Parse arguments +# ------------------ + +DEST_DIR="" +INCLUDE_RUST=0 + +for arg in "$@"; do + case "$arg" in + --full-native) + INCLUDE_RUST=1 + ;; + *) + if [[ -z "$DEST_DIR" ]]; then + DEST_DIR="$arg" + else + echo "Unexpected argument: $arg" >&2 + exit 1 + fi + ;; + esac +done + # ---------------------------------------------------------------------------- # Determine where the binaries should be installed. # ---------------------------------------------------------------------------- @@ -41,7 +65,7 @@ mkdir -p "$BIN_DIR" # Until we start publishing stable GitHub releases, we have to grab the binaries # from the GitHub Action that created them. Update the URL below to point to the # appropriate workflow run: -WORKFLOW_URL="https://github.com/openai/codex/actions/runs/14763725716" +WORKFLOW_URL="https://github.com/openai/codex/actions/runs/14872557396" WORKFLOW_ID="${WORKFLOW_URL##*/}" ARTIFACTS_DIR="$(mktemp -d)" @@ -50,12 +74,26 @@ trap 'rm -rf "$ARTIFACTS_DIR"' EXIT # NB: The GitHub CLI `gh` must be installed and authenticated. gh run download --dir "$ARTIFACTS_DIR" --repo openai/codex "$WORKFLOW_ID" -# Decompress the two target architectures. +# Decompress the artifacts for Linux sandboxing. zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/codex-linux-sandbox-x86_64-unknown-linux-musl.zst" \ -o "$BIN_DIR/codex-linux-sandbox-x64" zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-gnu/codex-linux-sandbox-aarch64-unknown-linux-gnu.zst" \ -o "$BIN_DIR/codex-linux-sandbox-arm64" -echo "Installed native dependencies into $BIN_DIR" +if [[ "$INCLUDE_RUST" -eq 1 ]]; then + # x64 Linux + zstd -d "$ARTIFACTS_DIR/x86_64-unknown-linux-musl/codex-x86_64-unknown-linux-musl.zst" \ + -o "$BIN_DIR/codex-x86_64-unknown-linux-musl" + # ARM64 Linux + zstd -d "$ARTIFACTS_DIR/aarch64-unknown-linux-gnu/codex-aarch64-unknown-linux-gnu.zst" \ + -o "$BIN_DIR/codex-aarch64-unknown-linux-gnu" + # x64 macOS + zstd -d "$ARTIFACTS_DIR/x86_64-apple-darwin/codex-x86_64-apple-darwin.zst" \ + -o "$BIN_DIR/codex-x86_64-apple-darwin" + # ARM64 macOS + zstd -d "$ARTIFACTS_DIR/aarch64-apple-darwin/codex-aarch64-apple-darwin.zst" \ + -o "$BIN_DIR/codex-aarch64-apple-darwin" +fi +echo "Installed native dependencies into $BIN_DIR" diff --git a/codex-cli/scripts/stage_release.sh b/codex-cli/scripts/stage_release.sh index e92b113179..23022b44e6 100755 --- a/codex-cli/scripts/stage_release.sh +++ b/codex-cli/scripts/stage_release.sh @@ -1,28 +1,141 @@ -#!/bin/bash +#!/usr/bin/env bash +# ----------------------------------------------------------------------------- +# stage_release.sh +# ----------------------------------------------------------------------------- +# Stages an npm release for @openai/codex. +# +# The script used to accept a single optional positional argument that indicated +# the temporary directory in which to stage the package. We now support a +# flag-based interface so that we can extend the command with further options +# without breaking the call-site contract. +# +# --tmp