mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
Prevent npm alpha dist-tags from moving backward (#49704)
## Why Publishing an older alpha or alpha hotfix can overwrite npm dist-tags that already point to a newer release. ## What changed Check each package's current npm dist-tag before publishing an alpha release, using the existing release version comparison and workflow concurrency lock. Handle `alpha` and platform-specific tags independently for Codex, the SDK, and the responses API proxy. When the release does not advance the current version, publish under `release-<version>-<tag>` to keep it installable without changing the alpha or `latest` tags. Abort publishing if the registry lookup fails. ## Testing Add unit tests for package and platform tag selection, older releases, dotted alpha hotfixes, missing tags, and registry lookup failures. GitOrigin-RevId: 9c0edec1149bf78c01aac0f700e963630316eaea
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
"""Choose a forward-only npm alpha dist-tag under rust-release's concurrency lock."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
|
||||
from releases import should_update_version
|
||||
|
||||
|
||||
def npm_alpha_tag(tarball: Path, version: str, tag: str) -> str:
|
||||
# Read the actual package name: platform archives share @openai/codex,
|
||||
# while the SDK and proxy have independent alpha pointers.
|
||||
with tarfile.open(tarball, "r:gz") as archive:
|
||||
with archive.extractfile("package/package.json") as manifest:
|
||||
package = json.load(manifest)["name"]
|
||||
|
||||
result = subprocess.run(
|
||||
["npm", "view", package, "dist-tags", "--json", "--prefer-online"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
current = json.loads(result.stdout).get(tag, "")
|
||||
if tag != "alpha":
|
||||
# build_npm_package.py appends -<platform> to the release version.
|
||||
current = current.removesuffix(f"-{tag.removeprefix('alpha-')}")
|
||||
if should_update_version(version, current):
|
||||
return tag
|
||||
|
||||
# npm publish always writes a tag. Keep this version installable without
|
||||
# changing alpha, alpha-<platform>, or latest. Prefix avoids semver tags,
|
||||
# which npm rejects. Include the platform for versions of the same package.
|
||||
print(
|
||||
f"Keeping {package}@{tag} at {current}; publishing {version} separately",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return f"release-{version}-{tag}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(npm_alpha_tag(Path(sys.argv[1]), sys.argv[2], sys.argv[3]))
|
||||
@@ -1,5 +1,13 @@
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from npm_alpha_tag import npm_alpha_tag
|
||||
from releases import is_valid_release_version, should_update_version
|
||||
|
||||
|
||||
@@ -52,5 +60,92 @@ class VersionComparisonTest(unittest.TestCase):
|
||||
self.assertRaises(ValueError, should_update_version, "0.123", "")
|
||||
|
||||
|
||||
class NpmAlphaTagTest(unittest.TestCase):
|
||||
def test_published_tags_across_packages_and_platforms(self) -> None:
|
||||
# Each package/tag reads its own registry value, even after a partially
|
||||
# published release. Also exercise a missing pointer and dotted hotfix.
|
||||
cases = [
|
||||
(
|
||||
"@openai/codex",
|
||||
"alpha",
|
||||
"0.158.0-alpha.10",
|
||||
"0.157.0-alpha.11.1",
|
||||
"release-0.157.0-alpha.11.1-alpha",
|
||||
),
|
||||
(
|
||||
"@openai/codex",
|
||||
"alpha-linux-x64",
|
||||
"0.158.0-alpha.10-linux-x64",
|
||||
"0.157.0-alpha.11.1",
|
||||
"release-0.157.0-alpha.11.1-alpha-linux-x64",
|
||||
),
|
||||
(
|
||||
"@openai/codex",
|
||||
"alpha-win32-arm64",
|
||||
"0.158.0-alpha.9-win32-arm64",
|
||||
"0.158.0-alpha.10",
|
||||
"alpha-win32-arm64",
|
||||
),
|
||||
(
|
||||
"@openai/codex-sdk",
|
||||
"alpha",
|
||||
"0.158.0-alpha.10",
|
||||
"0.158.0-alpha.10.1",
|
||||
"alpha",
|
||||
),
|
||||
(
|
||||
"@openai/codex-responses-api-proxy",
|
||||
"alpha",
|
||||
"0.159.0-alpha.1",
|
||||
"0.158.0-alpha.10.1",
|
||||
"release-0.158.0-alpha.10.1-alpha",
|
||||
),
|
||||
("@openai/codex-sdk", "alpha", None, "0.158.0-alpha.10", "alpha"),
|
||||
]
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tarball = Path(tmpdir) / "package.tgz"
|
||||
for package, tag, current, version, expected in cases:
|
||||
with self.subTest(package=package, tag=tag, version=version):
|
||||
content = json.dumps({"name": package}).encode()
|
||||
with tarfile.open(tarball, "w:gz") as archive:
|
||||
info = tarfile.TarInfo("package/package.json")
|
||||
info.size = len(content)
|
||||
archive.addfile(info, io.BytesIO(content))
|
||||
tags = {tag: current} if current is not None else {}
|
||||
result = subprocess.CompletedProcess([], 0, json.dumps(tags))
|
||||
with patch(
|
||||
"npm_alpha_tag.subprocess.run", return_value=result
|
||||
) as run:
|
||||
self.assertEqual(npm_alpha_tag(tarball, version, tag), expected)
|
||||
run.assert_called_once_with(
|
||||
[
|
||||
"npm",
|
||||
"view",
|
||||
package,
|
||||
"dist-tags",
|
||||
"--json",
|
||||
"--prefer-online",
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
def test_registry_failure_prevents_publish(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tarball = Path(tmpdir) / "package.tgz"
|
||||
content = b'{"name": "@openai/codex"}'
|
||||
with tarfile.open(tarball, "w:gz") as archive:
|
||||
info = tarfile.TarInfo("package/package.json")
|
||||
info.size = len(content)
|
||||
archive.addfile(info, io.BytesIO(content))
|
||||
with patch(
|
||||
"npm_alpha_tag.subprocess.run",
|
||||
side_effect=subprocess.CalledProcessError(1, "npm"),
|
||||
):
|
||||
with self.assertRaises(subprocess.CalledProcessError):
|
||||
npm_alpha_tag(tarball, "0.158.0-alpha.10", "alpha")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -1778,6 +1778,11 @@ jobs:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
@@ -1898,6 +1903,12 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
|
||||
# The workflow concurrency group holds the lock from this lookup
|
||||
# through publish, including concurrent alpha/hotfix release runs.
|
||||
if [[ "${NPM_TAG}" == "alpha" ]]; then
|
||||
tag="$(python3 .github/scripts/npm_alpha_tag.py "${tarball}" "${VERSION}" "${tag}")"
|
||||
fi
|
||||
|
||||
publish_cmd=(npm publish "${GITHUB_WORKSPACE}/${tarball}")
|
||||
if [[ -n "${tag}" ]]; then
|
||||
publish_cmd+=(--tag "${tag}")
|
||||
|
||||
Reference in New Issue
Block a user