mirror of
https://github.com/openai/codex.git
synced 2026-04-28 02:11:08 +03:00
chore: split NPM packages (#11318)
This commit is contained in:
33
.github/workflows/rust-release.yml
vendored
33
.github/workflows/rust-release.yml
vendored
@@ -593,18 +593,20 @@ jobs:
|
||||
version="${{ needs.release.outputs.version }}"
|
||||
tag="${{ needs.release.outputs.tag }}"
|
||||
mkdir -p dist/npm
|
||||
patterns=(
|
||||
"codex-npm-${version}.tgz"
|
||||
"codex-linux-*-npm-${version}.tgz"
|
||||
"codex-darwin-*-npm-${version}.tgz"
|
||||
"codex-win32-*-npm-${version}.tgz"
|
||||
"codex-responses-api-proxy-npm-${version}.tgz"
|
||||
"codex-sdk-npm-${version}.tgz"
|
||||
)
|
||||
for pattern in "${patterns[@]}"; do
|
||||
gh release download "$tag" \
|
||||
--repo "${GITHUB_REPOSITORY}" \
|
||||
--pattern "codex-npm-${version}.tgz" \
|
||||
--dir dist/npm
|
||||
gh release download "$tag" \
|
||||
--repo "${GITHUB_REPOSITORY}" \
|
||||
--pattern "codex-responses-api-proxy-npm-${version}.tgz" \
|
||||
--dir dist/npm
|
||||
gh release download "$tag" \
|
||||
--repo "${GITHUB_REPOSITORY}" \
|
||||
--pattern "codex-sdk-npm-${version}.tgz" \
|
||||
--pattern "$pattern" \
|
||||
--dir dist/npm
|
||||
done
|
||||
|
||||
# No NODE_AUTH_TOKEN needed because we use OIDC.
|
||||
- name: Publish to npm
|
||||
@@ -618,14 +620,15 @@ jobs:
|
||||
tag_args+=(--tag "${NPM_TAG}")
|
||||
fi
|
||||
|
||||
tarballs=(
|
||||
"codex-npm-${VERSION}.tgz"
|
||||
"codex-responses-api-proxy-npm-${VERSION}.tgz"
|
||||
"codex-sdk-npm-${VERSION}.tgz"
|
||||
)
|
||||
shopt -s nullglob
|
||||
tarballs=(dist/npm/*-npm-"${VERSION}".tgz)
|
||||
if [[ ${#tarballs[@]} -eq 0 ]]; then
|
||||
echo "No npm tarballs found in dist/npm for version ${VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for tarball in "${tarballs[@]}"; do
|
||||
npm publish "${GITHUB_WORKSPACE}/dist/npm/${tarball}" "${tag_args[@]}"
|
||||
npm publish "${GITHUB_WORKSPACE}/${tarball}" "${tag_args[@]}"
|
||||
done
|
||||
|
||||
update-branch:
|
||||
|
||||
@@ -3,12 +3,23 @@
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import { existsSync } from "fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
// __dirname equivalent in ESM
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const PLATFORM_PACKAGE_BY_TARGET = {
|
||||
"x86_64-unknown-linux-musl": "@openai/codex-linux-x64",
|
||||
"aarch64-unknown-linux-musl": "@openai/codex-linux-arm64",
|
||||
"x86_64-apple-darwin": "@openai/codex-darwin-x64",
|
||||
"aarch64-apple-darwin": "@openai/codex-darwin-arm64",
|
||||
"x86_64-pc-windows-msvc": "@openai/codex-win32-x64",
|
||||
"aarch64-pc-windows-msvc": "@openai/codex-win32-arm64",
|
||||
};
|
||||
|
||||
const { platform, arch } = process;
|
||||
|
||||
@@ -59,9 +70,51 @@ if (!targetTriple) {
|
||||
throw new Error(`Unsupported platform: ${platform} (${arch})`);
|
||||
}
|
||||
|
||||
const vendorRoot = path.join(__dirname, "..", "vendor");
|
||||
const archRoot = path.join(vendorRoot, targetTriple);
|
||||
const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
|
||||
if (!platformPackage) {
|
||||
throw new Error(`Unsupported target triple: ${targetTriple}`);
|
||||
}
|
||||
|
||||
const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
|
||||
const localVendorRoot = path.join(__dirname, "..", "vendor");
|
||||
const localBinaryPath = path.join(
|
||||
localVendorRoot,
|
||||
targetTriple,
|
||||
"codex",
|
||||
codexBinaryName,
|
||||
);
|
||||
|
||||
let vendorRoot;
|
||||
try {
|
||||
const packageJsonPath = require.resolve(`${platformPackage}/package.json`);
|
||||
vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
|
||||
} catch {
|
||||
if (existsSync(localBinaryPath)) {
|
||||
vendorRoot = localVendorRoot;
|
||||
} else {
|
||||
const packageManager = detectPackageManager();
|
||||
const updateCommand =
|
||||
packageManager === "bun"
|
||||
? "bun install -g @openai/codex@latest"
|
||||
: "npm install -g @openai/codex@latest";
|
||||
throw new Error(
|
||||
`Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!vendorRoot) {
|
||||
const packageManager = detectPackageManager();
|
||||
const updateCommand =
|
||||
packageManager === "bun"
|
||||
? "bun install -g @openai/codex@latest"
|
||||
: "npm install -g @openai/codex@latest";
|
||||
throw new Error(
|
||||
`Missing optional dependency ${platformPackage}. Reinstall Codex: ${updateCommand}`,
|
||||
);
|
||||
}
|
||||
|
||||
const archRoot = path.join(vendorRoot, targetTriple);
|
||||
const binaryPath = path.join(archRoot, "codex", codexBinaryName);
|
||||
|
||||
// Use an asynchronous spawn instead of spawnSync so that Node is able to
|
||||
|
||||
@@ -14,6 +14,9 @@ example, to stage the CLI, responses proxy, and SDK packages for version `0.6.0`
|
||||
This downloads the native artifacts once, hydrates `vendor/` for each package, and writes
|
||||
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.
|
||||
|
||||
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
|
||||
directory that contains the populated `vendor/` tree.
|
||||
|
||||
@@ -15,14 +15,68 @@ 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_PLATFORM_PACKAGES: dict[str, dict[str, str]] = {
|
||||
"codex-linux-x64": {
|
||||
"npm_name": "@openai/codex-linux-x64",
|
||||
"target_triple": "x86_64-unknown-linux-musl",
|
||||
"os": "linux",
|
||||
"cpu": "x64",
|
||||
},
|
||||
"codex-linux-arm64": {
|
||||
"npm_name": "@openai/codex-linux-arm64",
|
||||
"target_triple": "aarch64-unknown-linux-musl",
|
||||
"os": "linux",
|
||||
"cpu": "arm64",
|
||||
},
|
||||
"codex-darwin-x64": {
|
||||
"npm_name": "@openai/codex-darwin-x64",
|
||||
"target_triple": "x86_64-apple-darwin",
|
||||
"os": "darwin",
|
||||
"cpu": "x64",
|
||||
},
|
||||
"codex-darwin-arm64": {
|
||||
"npm_name": "@openai/codex-darwin-arm64",
|
||||
"target_triple": "aarch64-apple-darwin",
|
||||
"os": "darwin",
|
||||
"cpu": "arm64",
|
||||
},
|
||||
"codex-win32-x64": {
|
||||
"npm_name": "@openai/codex-win32-x64",
|
||||
"target_triple": "x86_64-pc-windows-msvc",
|
||||
"os": "win32",
|
||||
"cpu": "x64",
|
||||
},
|
||||
"codex-win32-arm64": {
|
||||
"npm_name": "@openai/codex-win32-arm64",
|
||||
"target_triple": "aarch64-pc-windows-msvc",
|
||||
"os": "win32",
|
||||
"cpu": "arm64",
|
||||
},
|
||||
}
|
||||
|
||||
PACKAGE_EXPANSIONS: dict[str, list[str]] = {
|
||||
"codex": ["codex", *CODEX_PLATFORM_PACKAGES],
|
||||
}
|
||||
|
||||
PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = {
|
||||
"codex": ["codex", "rg"],
|
||||
"codex": [],
|
||||
"codex-linux-x64": ["codex", "rg"],
|
||||
"codex-linux-arm64": ["codex", "rg"],
|
||||
"codex-darwin-x64": ["codex", "rg"],
|
||||
"codex-darwin-arm64": ["codex", "rg"],
|
||||
"codex-win32-x64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"],
|
||||
"codex-win32-arm64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"],
|
||||
"codex-responses-api-proxy": ["codex-responses-api-proxy"],
|
||||
"codex-sdk": ["codex"],
|
||||
}
|
||||
WINDOWS_ONLY_COMPONENTS: dict[str, list[str]] = {
|
||||
"codex": ["codex-windows-sandbox-setup", "codex-command-runner"],
|
||||
|
||||
PACKAGE_TARGET_FILTERS: dict[str, str] = {
|
||||
package_name: package_config["target_triple"]
|
||||
for package_name, package_config in CODEX_PLATFORM_PACKAGES.items()
|
||||
}
|
||||
|
||||
PACKAGE_CHOICES = tuple(PACKAGE_NATIVE_COMPONENTS)
|
||||
|
||||
COMPONENT_DEST_DIR: dict[str, str] = {
|
||||
"codex": "codex",
|
||||
"codex-responses-api-proxy": "codex-responses-api-proxy",
|
||||
@@ -36,7 +90,7 @@ def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Build or stage the Codex CLI npm package.")
|
||||
parser.add_argument(
|
||||
"--package",
|
||||
choices=("codex", "codex-responses-api-proxy", "codex-sdk"),
|
||||
choices=PACKAGE_CHOICES,
|
||||
default="codex",
|
||||
help="Which npm package to stage (default: codex).",
|
||||
)
|
||||
@@ -98,6 +152,7 @@ def main() -> int:
|
||||
|
||||
vendor_src = args.vendor_src.resolve() if args.vendor_src else None
|
||||
native_components = PACKAGE_NATIVE_COMPONENTS.get(package, [])
|
||||
target_filter = PACKAGE_TARGET_FILTERS.get(package)
|
||||
|
||||
if native_components:
|
||||
if vendor_src is None:
|
||||
@@ -108,7 +163,12 @@ def main() -> int:
|
||||
"pointing to a directory containing pre-installed binaries."
|
||||
)
|
||||
|
||||
copy_native_binaries(vendor_src, staging_dir, package, native_components)
|
||||
copy_native_binaries(
|
||||
vendor_src,
|
||||
staging_dir,
|
||||
native_components,
|
||||
target_filter={target_filter} if target_filter else None,
|
||||
)
|
||||
|
||||
if release_version:
|
||||
staging_dir_str = str(staging_dir)
|
||||
@@ -125,6 +185,12 @@ def main() -> int:
|
||||
"Verify the responses API proxy:\n"
|
||||
f" node {staging_dir_str}/bin/codex-responses-api-proxy.js --help\n\n"
|
||||
)
|
||||
elif package in CODEX_PLATFORM_PACKAGES:
|
||||
print(
|
||||
f"Staged version {version} for release in {staging_dir_str}\n\n"
|
||||
"Verify native payload contents:\n"
|
||||
f" ls {staging_dir_str}/vendor\n\n"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Staged version {version} for release in {staging_dir_str}\n\n"
|
||||
@@ -160,6 +226,9 @@ def prepare_staging_dir(staging_dir: Path | None) -> tuple[Path, bool]:
|
||||
|
||||
|
||||
def stage_sources(staging_dir: Path, version: str, package: str) -> None:
|
||||
package_json: dict
|
||||
package_json_path: Path | None = None
|
||||
|
||||
if package == "codex":
|
||||
bin_dir = staging_dir / "bin"
|
||||
bin_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -173,6 +242,33 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
|
||||
shutil.copy2(readme_src, staging_dir / "README.md")
|
||||
|
||||
package_json_path = CODEX_CLI_ROOT / "package.json"
|
||||
elif package in CODEX_PLATFORM_PACKAGES:
|
||||
platform_package = CODEX_PLATFORM_PACKAGES[package]
|
||||
|
||||
readme_src = REPO_ROOT / "README.md"
|
||||
if readme_src.exists():
|
||||
shutil.copy2(readme_src, staging_dir / "README.md")
|
||||
|
||||
with open(CODEX_CLI_ROOT / "package.json", "r", encoding="utf-8") as fh:
|
||||
codex_package_json = json.load(fh)
|
||||
|
||||
package_json = {
|
||||
"name": platform_package["npm_name"],
|
||||
"version": version,
|
||||
"license": codex_package_json.get("license", "Apache-2.0"),
|
||||
"os": [platform_package["os"]],
|
||||
"cpu": [platform_package["cpu"]],
|
||||
"files": ["vendor"],
|
||||
"repository": codex_package_json.get("repository"),
|
||||
}
|
||||
|
||||
engines = codex_package_json.get("engines")
|
||||
if isinstance(engines, dict):
|
||||
package_json["engines"] = engines
|
||||
|
||||
package_manager = codex_package_json.get("packageManager")
|
||||
if isinstance(package_manager, str):
|
||||
package_json["packageManager"] = package_manager
|
||||
elif package == "codex-responses-api-proxy":
|
||||
bin_dir = staging_dir / "bin"
|
||||
bin_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -190,11 +286,20 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None:
|
||||
else:
|
||||
raise RuntimeError(f"Unknown package '{package}'.")
|
||||
|
||||
if package_json_path is not None:
|
||||
with open(package_json_path, "r", encoding="utf-8") as fh:
|
||||
package_json = json.load(fh)
|
||||
package_json["version"] = version
|
||||
|
||||
if package == "codex-sdk":
|
||||
if package == "codex":
|
||||
package_json["files"] = ["bin"]
|
||||
package_json["optionalDependencies"] = {
|
||||
CODEX_PLATFORM_PACKAGES[platform_package]["npm_name"]: version
|
||||
for platform_package in PACKAGE_EXPANSIONS["codex"]
|
||||
if platform_package != "codex"
|
||||
}
|
||||
|
||||
elif package == "codex-sdk":
|
||||
scripts = package_json.get("scripts")
|
||||
if isinstance(scripts, dict):
|
||||
scripts.pop("prepare", None)
|
||||
@@ -240,8 +345,8 @@ def stage_codex_sdk_sources(staging_dir: Path) -> None:
|
||||
def copy_native_binaries(
|
||||
vendor_src: Path,
|
||||
staging_dir: Path,
|
||||
package: str,
|
||||
components: list[str],
|
||||
target_filter: set[str] | None = None,
|
||||
) -> None:
|
||||
vendor_src = vendor_src.resolve()
|
||||
if not vendor_src.exists():
|
||||
@@ -256,15 +361,18 @@ def copy_native_binaries(
|
||||
shutil.rmtree(vendor_dest)
|
||||
vendor_dest.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
copied_targets: set[str] = set()
|
||||
|
||||
for target_dir in vendor_src.iterdir():
|
||||
if not target_dir.is_dir():
|
||||
continue
|
||||
|
||||
if "windows" in target_dir.name:
|
||||
components_set.update(WINDOWS_ONLY_COMPONENTS.get(package, []))
|
||||
if target_filter is not None and target_dir.name not in target_filter:
|
||||
continue
|
||||
|
||||
dest_target_dir = vendor_dest / target_dir.name
|
||||
dest_target_dir.mkdir(parents=True, exist_ok=True)
|
||||
copied_targets.add(target_dir.name)
|
||||
|
||||
for component in components_set:
|
||||
dest_dir_name = COMPONENT_DEST_DIR.get(component)
|
||||
@@ -282,6 +390,12 @@ def copy_native_binaries(
|
||||
shutil.rmtree(dest_component_dir)
|
||||
shutil.copytree(src_component_dir, dest_component_dir)
|
||||
|
||||
if target_filter is not None:
|
||||
missing_targets = sorted(target_filter - copied_targets)
|
||||
if missing_targets:
|
||||
missing_list = ", ".join(missing_targets)
|
||||
raise RuntimeError(f"Missing target directories in vendor source: {missing_list}")
|
||||
|
||||
|
||||
def run_npm_pack(staging_dir: Path, output_path: Path) -> Path:
|
||||
output_path = output_path.resolve()
|
||||
|
||||
@@ -25,7 +25,7 @@ if _SPEC is None or _SPEC.loader is None:
|
||||
_BUILD_MODULE = importlib.util.module_from_spec(_SPEC)
|
||||
_SPEC.loader.exec_module(_BUILD_MODULE)
|
||||
PACKAGE_NATIVE_COMPONENTS = getattr(_BUILD_MODULE, "PACKAGE_NATIVE_COMPONENTS", {})
|
||||
WINDOWS_ONLY_COMPONENTS = getattr(_BUILD_MODULE, "WINDOWS_ONLY_COMPONENTS", {})
|
||||
PACKAGE_EXPANSIONS = getattr(_BUILD_MODULE, "PACKAGE_EXPANSIONS", {})
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
@@ -64,10 +64,19 @@ def collect_native_components(packages: list[str]) -> set[str]:
|
||||
components: set[str] = set()
|
||||
for package in packages:
|
||||
components.update(PACKAGE_NATIVE_COMPONENTS.get(package, []))
|
||||
components.update(WINDOWS_ONLY_COMPONENTS.get(package, []))
|
||||
return components
|
||||
|
||||
|
||||
def expand_packages(packages: list[str]) -> list[str]:
|
||||
expanded: list[str] = []
|
||||
for package in packages:
|
||||
for expanded_package in PACKAGE_EXPANSIONS.get(package, [package]):
|
||||
if expanded_package in expanded:
|
||||
continue
|
||||
expanded.append(expanded_package)
|
||||
return expanded
|
||||
|
||||
|
||||
def resolve_release_workflow(version: str) -> dict:
|
||||
stdout = subprocess.check_output(
|
||||
[
|
||||
@@ -128,14 +137,14 @@ def main() -> int:
|
||||
|
||||
runner_temp = Path(os.environ.get("RUNNER_TEMP", tempfile.gettempdir()))
|
||||
|
||||
packages = list(args.packages)
|
||||
packages = expand_packages(list(args.packages))
|
||||
native_components = collect_native_components(packages)
|
||||
|
||||
vendor_temp_root: Path | None = None
|
||||
vendor_src: Path | None = None
|
||||
resolved_head_sha: str | None = None
|
||||
|
||||
final_messsages = []
|
||||
final_messages = []
|
||||
|
||||
try:
|
||||
if native_components:
|
||||
@@ -174,12 +183,12 @@ def main() -> int:
|
||||
if not args.keep_staging_dirs:
|
||||
shutil.rmtree(staging_dir, ignore_errors=True)
|
||||
|
||||
final_messsages.append(f"Staged {package} at {pack_output}")
|
||||
final_messages.append(f"Staged {package} at {pack_output}")
|
||||
finally:
|
||||
if vendor_temp_root is not None and not args.keep_staging_dirs:
|
||||
shutil.rmtree(vendor_temp_root, ignore_errors=True)
|
||||
|
||||
for msg in final_messsages:
|
||||
for msg in final_messages:
|
||||
print(msg)
|
||||
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user