diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1ee1395e10..9adc90e0401 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,5 +74,15 @@ jobs: - name: Check root README ToC run: python3 scripts/readme_toc.py README.md + - uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49 + with: + tool: just + - name: Install uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + with: + version: "0.11.3" + - name: Ruff format Python scripts (run `just fmt` to fix) + run: just fmt-scripts-check + - name: Prettier (run `pnpm run format:fix` to fix) run: pnpm run format diff --git a/AGENTS.md b/AGENTS.md index 4714b1b8aa4..9ee9b2b6d82 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,7 +55,7 @@ In the codex-rs folder where the rust code lives: trivial; prefer new modules/files and keep `chatwidget.rs` focused on orchestration. - When running Rust commands (e.g. `just fix` or `just test`) be patient with the command and never try to kill them using the PID. Rust lock can make the execution slow, this is expected. -Run `just fmt` (in `codex-rs` directory) automatically after you have finished making Rust code changes; do not ask for approval to run it. Additionally, run the tests: +Run `just fmt` (in the `codex-rs` directory) automatically after you have finished making code changes anywhere in this repository; do not ask for approval to run it. Additionally, run the tests: 1. Do not run `cargo test` directly. Use `just test` so test execution follows the repo defaults. 2. Run the test for the specific project that was changed. For example, if changes were made in `codex-rs/tui`, run `just test -p codex-tui`. diff --git a/justfile b/justfile index 53b37e24caa..2c57e1cdf65 100644 --- a/justfile +++ b/justfile @@ -36,11 +36,16 @@ app-server-test-client *args: cargo build -p codex-cli cargo run -p codex-app-server-test-client -- --codex-bin ./target/debug/codex {args} -# Format Rust and Python SDK code. +# Format Rust, Python SDK code, and Python scripts. fmt: cargo fmt -- --config imports_granularity=Item {stderr-null} uv run --frozen --project ../sdk/python --extra dev ruff check --fix --fix-only ../sdk/python uv run --frozen --project ../sdk/python --extra dev ruff format ../sdk/python + # Root scripts have their own locked Ruff environment. + uv run --frozen --project ../scripts ruff format ../scripts + +fmt-scripts-check: + uv run --frozen --project ../scripts ruff format --check ../scripts fix *args: cargo clippy --fix --tests --allow-dirty {args} diff --git a/scripts/check_blob_size.py b/scripts/check_blob_size.py index 455145f18ea..b63e1d19f0c 100755 --- a/scripts/check_blob_size.py +++ b/scripts/check_blob_size.py @@ -75,7 +75,9 @@ def blob_size(commit: str, path: str) -> int: return int(run_git("cat-file", "-s", f"{commit}:{path}").strip()) -def collect_changed_blobs(base: str, head: str, allowlist: set[str]) -> list[ChangedBlob]: +def collect_changed_blobs( + base: str, head: str, allowlist: set[str] +) -> list[ChangedBlob]: blobs: list[ChangedBlob] = [] for path in get_changed_paths(base, head): blobs.append( @@ -137,7 +139,9 @@ def main() -> int: parser = argparse.ArgumentParser( description="Fail if changed blobs exceed the configured size budget." ) - parser.add_argument("--base", required=True, help="Base git revision to diff against.") + parser.add_argument( + "--base", required=True, help="Base git revision to diff against." + ) parser.add_argument("--head", required=True, help="Head git revision to inspect.") parser.add_argument( "--max-bytes", @@ -156,7 +160,9 @@ def main() -> int: allowlist = load_allowlist(args.allowlist) blobs = collect_changed_blobs(args.base, args.head, allowlist) violations = [ - blob for blob in blobs if blob.size_bytes > args.max_bytes and not blob.is_allowlisted + blob + for blob in blobs + if blob.size_bytes > args.max_bytes and not blob.is_allowlisted ] write_step_summary(args.max_bytes, blobs, violations) @@ -165,7 +171,9 @@ def main() -> int: print("No changed files were detected.") return 0 - print(f"Checked {len(blobs)} changed file(s) against the {args.max_bytes}-byte limit.") + print( + f"Checked {len(blobs)} changed file(s) against the {args.max_bytes}-byte limit." + ) for blob in blobs: status = "allowlisted" if blob.is_allowlisted else "ok" if blob in violations: diff --git a/scripts/codex_package/archive.py b/scripts/codex_package/archive.py index 08944a650f0..7e6a3f84652 100644 --- a/scripts/codex_package/archive.py +++ b/scripts/codex_package/archive.py @@ -99,7 +99,9 @@ def resolve_zstd_command( def write_zip_archive(package_dir: Path, archive_path: Path) -> None: - with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive: + with zipfile.ZipFile( + archive_path, "w", compression=zipfile.ZIP_DEFLATED + ) as archive: for path in package_entries(package_dir): relative_path = path.relative_to(package_dir) if path.is_dir(): @@ -109,4 +111,7 @@ def write_zip_archive(package_dir: Path, archive_path: Path) -> None: def package_entries(package_dir: Path) -> list[Path]: - return sorted(package_dir.rglob("*"), key=lambda path: path.relative_to(package_dir).as_posix()) + return sorted( + package_dir.rglob("*"), + key=lambda path: path.relative_to(package_dir).as_posix(), + ) diff --git a/scripts/codex_package/cargo.py b/scripts/codex_package/cargo.py index f2238dce533..208d85d1746 100644 --- a/scripts/codex_package/cargo.py +++ b/scripts/codex_package/cargo.py @@ -44,8 +44,7 @@ def build_source_binaries( variant, build_entrypoint=entrypoint_bin is None, build_bwrap=spec.is_linux and bwrap_bin is None, - build_codex_command_runner=spec.is_windows - and codex_command_runner_bin is None, + build_codex_command_runner=spec.is_windows and codex_command_runner_bin is None, build_codex_windows_sandbox_setup=spec.is_windows and codex_windows_sandbox_setup_bin is None, ) @@ -138,7 +137,9 @@ def validate_prebuilt_resource_inputs( ) -def resolve_output_path(explicit_path: Path | None, default_path: Path | None) -> Path | None: +def resolve_output_path( + explicit_path: Path | None, default_path: Path | None +) -> Path | None: if explicit_path is not None: return explicit_path.resolve() diff --git a/scripts/codex_package/cli.py b/scripts/codex_package/cli.py index 01520026698..b7d919e4ffa 100644 --- a/scripts/codex_package/cli.py +++ b/scripts/codex_package/cli.py @@ -44,8 +44,7 @@ def parse_args() -> argparse.Namespace: type=Path, default=argparse.SUPPRESS, help=( - "Output directory to create as the package root. Defaults to a new " - "temporary directory." + "Output directory to create as the package root. Defaults to a new temporary directory." ), ) parser.add_argument( @@ -72,8 +71,7 @@ def parse_args() -> argparse.Namespace: "--cargo-profile", default="dev-small", help=( - "Cargo profile for source-built package artifacts. Use release for " - "release packages." + "Cargo profile for source-built package artifacts. Use release for release packages." ), ) parser.add_argument( @@ -169,7 +167,9 @@ def main() -> int: ) prepare_package_dir(package_dir, force=args.force) build_package_dir(package_dir, version, variant, spec, inputs) - validate_package_dir(package_dir, variant, spec, include_zsh=inputs.zsh_bin is not None) + validate_package_dir( + package_dir, variant, spec, include_zsh=inputs.zsh_bin is not None + ) for archive_output in args.archive_output: archive_path = archive_output.resolve() diff --git a/scripts/codex_package/layout.py b/scripts/codex_package/layout.py index c763eb26040..63598672ea8 100644 --- a/scripts/codex_package/layout.py +++ b/scripts/codex_package/layout.py @@ -17,7 +17,9 @@ def prepare_package_dir(package_dir: Path, *, force: bool) -> None: if package_dir.exists(): if not package_dir.is_dir(): - raise RuntimeError(f"Package output exists and is not a directory: {package_dir}") + raise RuntimeError( + f"Package output exists and is not a directory: {package_dir}" + ) if any(package_dir.iterdir()): if not force: raise RuntimeError( diff --git a/scripts/codex_package/test_cargo.py b/scripts/codex_package/test_cargo.py index d090bfd1ed4..6185f8b8b31 100644 --- a/scripts/codex_package/test_cargo.py +++ b/scripts/codex_package/test_cargo.py @@ -27,7 +27,9 @@ def test_macos_package_with_prebuilt_entrypoint_builds_nothing(self) -> None: [], ) - def test_linux_package_with_prebuilt_entrypoint_and_bwrap_builds_nothing(self) -> None: + def test_linux_package_with_prebuilt_entrypoint_and_bwrap_builds_nothing( + self, + ) -> None: self.assertEqual( source_binaries_for_target( TARGET_SPECS["x86_64-unknown-linux-musl"], @@ -40,7 +42,9 @@ def test_linux_package_with_prebuilt_entrypoint_and_bwrap_builds_nothing(self) - [], ) - def test_windows_package_with_prebuilt_entrypoint_and_helpers_builds_nothing(self) -> None: + def test_windows_package_with_prebuilt_entrypoint_and_helpers_builds_nothing( + self, + ) -> None: self.assertEqual( source_binaries_for_target( TARGET_SPECS["x86_64-pc-windows-msvc"], diff --git a/scripts/codex_package/v8.py b/scripts/codex_package/v8.py index 43d0dcb6117..4033e6822fb 100644 --- a/scripts/codex_package/v8.py +++ b/scripts/codex_package/v8.py @@ -43,8 +43,7 @@ def resolve_codex_v8_cargo_env( return {} if archive_override or binding_override: raise RuntimeError( - "Cargo package builds need RUSTY_V8_ARCHIVE and " - "RUSTY_V8_SRC_BINDING_PATH set together." + "Cargo package builds need RUSTY_V8_ARCHIVE and RUSTY_V8_SRC_BINDING_PATH set together." ) artifacts = fetch_codex_v8_artifacts(spec, cache_root=cache_root) @@ -61,12 +60,13 @@ def fetch_codex_v8_artifacts( cache_root: Path | None = None, ) -> RustyV8ArtifactPair: if spec.is_windows: - raise RuntimeError(f"No Codex-built V8 release artifacts for target: {spec.target}") + raise RuntimeError( + f"No Codex-built V8 release artifacts for target: {spec.target}" + ) version = version or resolved_v8_crate_version() release_url = ( - "https://github.com/openai/codex/releases/download/" - f"rusty-v8-v{version}" + f"https://github.com/openai/codex/releases/download/rusty-v8-v{version}" ) target = spec.target cache_dir = (cache_root or default_cache_root()) / f"rusty-v8-{version}-{target}" @@ -98,7 +98,9 @@ def resolved_v8_crate_version() -> str: } ) if len(versions) != 1: - raise RuntimeError(f"Expected exactly one resolved v8 version, found: {versions}") + raise RuntimeError( + f"Expected exactly one resolved v8 version, found: {versions}" + ) return versions[0] @@ -111,18 +113,21 @@ def load_checksums(checksums_path: Path, artifact_names: set[str]) -> dict[str, lines = checksums_path.read_text(encoding="utf-8").splitlines() if len(lines) != len(artifact_names): raise RuntimeError( - f"Expected {len(artifact_names)} V8 checksums in {checksums_path}, " - f"found {len(lines)}." + f"Expected {len(artifact_names)} V8 checksums in {checksums_path}, found {len(lines)}." ) for line in lines: parts = line.split(maxsplit=1) if len(parts) != 2: - raise RuntimeError(f"Invalid V8 checksum line in {checksums_path}: {line!r}") + raise RuntimeError( + f"Invalid V8 checksum line in {checksums_path}: {line!r}" + ) digest, artifact_name = parts[0], parts[1].strip() if len(digest) != 64 or any(char not in "0123456789abcdef" for char in digest): - raise RuntimeError(f"Invalid V8 checksum digest in {checksums_path}: {digest}") + raise RuntimeError( + f"Invalid V8 checksum digest in {checksums_path}: {digest}" + ) if artifact_name not in artifact_names: raise RuntimeError( f"Unexpected V8 checksum artifact in {checksums_path}: {artifact_name}" @@ -146,7 +151,9 @@ def ensure_valid_artifact(artifact: Path, checksum: str, url: str) -> None: return artifact.unlink(missing_ok=True) - raise RuntimeError(f"Codex-built V8 artifact {artifact} failed checksum validation.") + raise RuntimeError( + f"Codex-built V8 artifact {artifact} failed checksum validation." + ) def has_checksum(path: Path, expected: str) -> bool: diff --git a/scripts/just-shell.py b/scripts/just-shell.py index 423e56dafec..bb2db6ebc43 100644 --- a/scripts/just-shell.py +++ b/scripts/just-shell.py @@ -16,7 +16,7 @@ ARGS_TOKEN = "{args}" STDERR_NULL_TOKEN = "{stderr-null}" POWERSHELL_ARGS = "@($args | Select-Object -Skip 1)" -POWERSHELL_STDERR_NULL = '2>$null; exit $LASTEXITCODE' +POWERSHELL_STDERR_NULL = "2>$null; exit $LASTEXITCODE" SH_ARGS = '"$@"' SH_STDERR_NULL = "2>/dev/null" diff --git a/scripts/mock_responses_websocket_server.py b/scripts/mock_responses_websocket_server.py index 477ed4fbbb7..6d41df8a4ab 100644 --- a/scripts/mock_responses_websocket_server.py +++ b/scripts/mock_responses_websocket_server.py @@ -44,13 +44,23 @@ def _event_response_done() -> dict[str, Any]: def _event_response_completed(response_id: str) -> dict[str, Any]: - return {"type": "response.completed", "response": {"id": response_id, "usage": _default_usage()}} + return { + "type": "response.completed", + "response": {"id": response_id, "usage": _default_usage()}, + } -def _event_function_call(call_id: str, name: str, arguments_json: str) -> dict[str, Any]: +def _event_function_call( + call_id: str, name: str, arguments_json: str +) -> dict[str, Any]: return { "type": "response.output_item.done", - "item": {"type": "function_call", "call_id": call_id, "name": name, "arguments": arguments_json}, + "item": { + "type": "function_call", + "call_id": call_id, + "name": name, + "arguments": arguments_json, + }, } @@ -75,6 +85,7 @@ def _print_request(prefix: str, payload: Any) -> None: sys.stdout.write(f"{prefix} {_utc_iso()}\n{pretty}\n") sys.stdout.flush() + async def _handle_connection( websocket: Any, *, @@ -91,7 +102,9 @@ async def _handle_connection( path_no_qs = path.split("?", 1)[0] if path != "(unknown)" else path if path_no_qs != "(unknown)" and path_no_qs != expected_path: - sys.stdout.write(f"[conn] {_utc_iso()} rejecting unexpected path (expected {expected_path})\n") + sys.stdout.write( + f"[conn] {_utc_iso()} rejecting unexpected path (expected {expected_path})\n" + ) sys.stdout.flush() await websocket.close(code=1008, reason="unexpected websocket path") return diff --git a/scripts/pyproject.toml b/scripts/pyproject.toml new file mode 100644 index 00000000000..4340b6b78e8 --- /dev/null +++ b/scripts/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "codex-scripts" +version = "0.0.0" +requires-python = ">=3.10" +dependencies = ["ruff>=0.15.8"] + +[tool.uv] +exclude-newer = "7 days" +index-strategy = "first-index" diff --git a/scripts/readme_toc.py b/scripts/readme_toc.py index 90fdc3ab71f..f37a69d7c71 100755 --- a/scripts/readme_toc.py +++ b/scripts/readme_toc.py @@ -88,7 +88,7 @@ def check_or_fix(readme_path: Path, fix: bool) -> int: current_block = lines[begin_idx + 1 : end_idx] current = [l for l in current_block if l.lstrip().startswith("- [")] # generate expected ToC from content without current ToC - toc_content = lines[:begin_idx] + lines[end_idx+1:] + toc_content = lines[:begin_idx] + lines[end_idx + 1 :] expected = generate_toc_lines("\n".join(toc_content)) if current == expected: return 0 @@ -109,7 +109,7 @@ def check_or_fix(readme_path: Path, fix: bool) -> int: return 1 # rebuild file with updated ToC prefix = lines[: begin_idx + 1] - suffix = lines[end_idx+1:] + suffix = lines[end_idx + 1 :] new_lines = prefix + [""] + expected + [""] + suffix readme_path.write_text("\n".join(new_lines) + "\n", encoding="utf-8") print(f"Updated ToC in {readme_path}.") diff --git a/scripts/stage_npm_packages.py b/scripts/stage_npm_packages.py index d0cfccf37f2..2329d2ba626 100755 --- a/scripts/stage_npm_packages.py +++ b/scripts/stage_npm_packages.py @@ -37,7 +37,9 @@ PACKAGE_NATIVE_COMPONENTS = getattr(_BUILD_MODULE, "PACKAGE_NATIVE_COMPONENTS", {}) PACKAGE_EXPANSIONS = getattr(_BUILD_MODULE, "PACKAGE_EXPANSIONS", {}) CODEX_PLATFORM_PACKAGES = getattr(_BUILD_MODULE, "CODEX_PLATFORM_PACKAGES", {}) -CODEX_PACKAGE_COMPONENT = getattr(_BUILD_MODULE, "CODEX_PACKAGE_COMPONENT", "codex-package") +CODEX_PACKAGE_COMPONENT = getattr( + _BUILD_MODULE, "CODEX_PACKAGE_COMPONENT", "codex-package" +) @dataclass(frozen=True) @@ -159,7 +161,9 @@ def resolve_release_workflow(version: str) -> dict: ) workflow = json.loads(stdout or "null") if not workflow: - raise RuntimeError(f"Unable to find rust-release workflow for version {version}.") + raise RuntimeError( + f"Unable to find rust-release workflow for version {version}." + ) return workflow @@ -386,13 +390,17 @@ def install_single_binary( component: BinaryComponent, ) -> Path: artifact_subdir = artifact_dir_for_target(artifacts_dir, target) - archive_path = binary_archive_path(artifact_subdir, component.artifact_prefix, target) + archive_path = binary_archive_path( + artifact_subdir, component.artifact_prefix, target + ) dest_dir = vendor_dir / target / component.dest_dir dest_dir.mkdir(parents=True, exist_ok=True) binary_name = ( - f"{component.binary_basename}.exe" if "windows" in target else component.binary_basename + f"{component.binary_basename}.exe" + if "windows" in target + else component.binary_basename ) dest = dest_dir / binary_name dest.unlink(missing_ok=True) @@ -405,14 +413,18 @@ def install_single_binary( def binary_archive_path(artifact_dir: Path, artifact_prefix: str, target: str) -> Path: archive_names = [archive_name_for_target(artifact_prefix, target)] if artifact_dir.name == f"{target}-unsigned": - archive_names.append(archive_name_for_target(artifact_prefix, f"{target}-unsigned")) + archive_names.append( + archive_name_for_target(artifact_prefix, f"{target}-unsigned") + ) for archive_name in archive_names: archive_path = artifact_dir / archive_name if archive_path.exists(): return archive_path - raise FileNotFoundError(f"Expected artifact not found: {artifact_dir / archive_names[0]}") + raise FileNotFoundError( + f"Expected artifact not found: {artifact_dir / archive_names[0]}" + ) def archive_name_for_target(artifact_prefix: str, target: str) -> str: @@ -434,7 +446,9 @@ def extract_zstd_archive(archive_path: Path, dest: Path) -> None: dest.parent.mkdir(parents=True, exist_ok=True) output_path = archive_path.parent / dest.name - subprocess.check_call(["zstd", "-f", "-d", str(archive_path), "-o", str(output_path)]) + subprocess.check_call( + ["zstd", "-f", "-d", str(archive_path), "-o", str(output_path)] + ) shutil.move(str(output_path), dest) @@ -497,7 +511,9 @@ def main() -> int: ) print(f"Caching downloaded artifacts in {artifacts_temp_root}", flush=True) for components in native_component_sets: - vendor_temp_root = Path(tempfile.mkdtemp(prefix="npm-native-", dir=runner_temp)) + vendor_temp_root = Path( + tempfile.mkdtemp(prefix="npm-native-", dir=runner_temp) + ) vendor_temp_roots.append(vendor_temp_root) print( "Installing native components " @@ -517,8 +533,12 @@ def main() -> int: print(f"should `git checkout {resolved_head_sha}`", flush=True) for package in packages: - staging_dir = Path(tempfile.mkdtemp(prefix=f"npm-stage-{package}-", dir=runner_temp)) - pack_output = output_dir / tarball_name_for_package(package, args.release_version) + staging_dir = Path( + tempfile.mkdtemp(prefix=f"npm-stage-{package}-", dir=runner_temp) + ) + pack_output = output_dir / tarball_name_for_package( + package, args.release_version + ) print(f"Staging {package} in {staging_dir}", flush=True) cmd = [ @@ -533,7 +553,9 @@ def main() -> int: str(pack_output), ] - vendor_src = vendor_src_by_components.get(native_components_for_package(package)) + vendor_src = vendor_src_by_components.get( + native_components_for_package(package) + ) if vendor_src is not None: cmd.extend(["--vendor-src", str(vendor_src)]) diff --git a/scripts/uv.lock b/scripts/uv.lock new file mode 100644 index 00000000000..6c0c3bd0810 --- /dev/null +++ b/scripts/uv.lock @@ -0,0 +1,43 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[options] +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer-span = "P7D" + +[[package]] +name = "codex-scripts" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [{ name = "ruff", specifier = ">=0.15.8" }] + +[[package]] +name = "ruff" +version = "0.15.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, +] diff --git a/sdk/python/tests/test_artifact_workflow_and_binaries.py b/sdk/python/tests/test_artifact_workflow_and_binaries.py index a6a338bb986..78fbdd62355 100644 --- a/sdk/python/tests/test_artifact_workflow_and_binaries.py +++ b/sdk/python/tests/test_artifact_workflow_and_binaries.py @@ -68,8 +68,8 @@ def test_generation_has_single_maintenance_entrypoint_script() -> None: assert scripts == ["update_sdk_artifacts.py"] -def test_root_fmt_recipe_formats_rust_and_python_sdk() -> None: - """The repo fmt command should work from Rust and Python SDK directories.""" +def test_root_fmt_recipe_formats_rust_python_sdk_and_scripts() -> None: + """The repo fmt command should format Rust, the Python SDK, and scripts.""" justfile = ROOT.parents[1] / "justfile" lines = justfile.read_text().splitlines() fmt_index = lines.index("fmt:") @@ -88,16 +88,18 @@ def test_root_fmt_recipe_formats_rust_and_python_sdk() -> None: } expected = { "working_directory": 'set working-directory := "codex-rs"', - "previous_comment": "# Format Rust and Python SDK code.", + "previous_comment": "# Format Rust, Python SDK code, and Python scripts.", "commands": [ "cargo fmt -- --config imports_granularity=Item {stderr-null}", "uv run --frozen --project ../sdk/python --extra dev ruff check --fix --fix-only ../sdk/python", "uv run --frozen --project ../sdk/python --extra dev ruff format ../sdk/python", + "# Root scripts have their own locked Ruff environment.", + "uv run --frozen --project ../scripts ruff format ../scripts", ], } assert actual == expected, ( - "The root `just fmt` recipe must run Rust fmt and Python SDK Ruff. " + "The root `just fmt` recipe must run Rust fmt and Ruff for Python SDK code and scripts. " "Fix the `fmt` recipe in `justfile`, then run `just fmt`.\n" f"Expected: {json.dumps(expected, indent=2)}\n" f"Actual: {json.dumps(actual, indent=2)}"