fix: prerelease version
This commit is contained in:
@@ -8,6 +8,9 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
/** Published-entry acceptance for argument errors, profile lifecycle, and boot-free config dumps. */
|
||||
const repoRoot = fileURLToPath(new URL('../../../', import.meta.url))
|
||||
// The release version, including a prerelease such as 0.0.1-rc.1: `--version`
|
||||
// prints what this manifest carries, so no test may pin it to a literal.
|
||||
const cliVersion = (JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version: string }).version
|
||||
const dshBin = join(repoRoot, 'apps/cli/lib/bin.js')
|
||||
const invalidProvider = fileURLToPath(new URL('./fixtures/invalid-provider.cordis.yml', import.meta.url))
|
||||
|
||||
@@ -387,7 +390,7 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
|
||||
writeFileSync(join(project, '.env'), 'PATH=/project-only-path\n')
|
||||
try {
|
||||
const result = await runBuiltBin(['--version'], {}, project)
|
||||
expect(result).toEqual({ code: 0, stdout: '0.0.1', stderr: '' })
|
||||
expect(result).toEqual({ code: 0, stdout: cliVersion, stderr: '' })
|
||||
} finally {
|
||||
rmSync(project, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
@@ -461,7 +461,11 @@ describe('CreateWizard and scaffolder', () => {
|
||||
})
|
||||
|
||||
it('reads the release batch from the initializer package', async () => {
|
||||
await expect(readCreateSdkVersion()).resolves.toBe('0.0.1')
|
||||
// The version tracks the release, including a prerelease such as 0.0.1-rc.1,
|
||||
// so the expectation comes from the manifest rather than a literal.
|
||||
const manifest = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')) as { version: string }
|
||||
|
||||
await expect(readCreateSdkVersion()).resolves.toBe(manifest.version)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -32,13 +32,31 @@ def test_release_tag_must_match_repository_version() -> None:
|
||||
build_python_release.validate_release_tag("python-v1.2.4", "1.2.3")
|
||||
|
||||
|
||||
def test_repository_version_rejects_non_stable_versions(tmp_path: Path) -> None:
|
||||
(tmp_path / "package.json").write_text('{"version":"1.2.3-dev"}\n')
|
||||
def test_repository_version_accepts_a_prerelease(tmp_path: Path) -> None:
|
||||
(tmp_path / "package.json").write_text('{"version":"1.2.3-rc.1"}\n')
|
||||
|
||||
with pytest.raises(ValueError, match="must be stable X.Y.Z"):
|
||||
assert build_python_release.repository_version(tmp_path) == "1.2.3-rc.1"
|
||||
|
||||
|
||||
def test_repository_version_rejects_malformed_versions(tmp_path: Path) -> None:
|
||||
(tmp_path / "package.json").write_text('{"version":"v1.2"}\n')
|
||||
|
||||
with pytest.raises(ValueError, match="must be X.Y.Z"):
|
||||
build_python_release.repository_version(tmp_path)
|
||||
|
||||
|
||||
def test_pep440_version_spells_a_prerelease_the_python_way() -> None:
|
||||
# Build backends normalize to this spelling, so the wheel filename and
|
||||
# metadata checks compare against it rather than the repository version.
|
||||
assert build_python_release.pep440_version("1.2.3") == "1.2.3"
|
||||
assert build_python_release.pep440_version("1.2.3-rc.1") == "1.2.3rc1"
|
||||
assert build_python_release.pep440_version("1.2.3-alpha.2") == "1.2.3a2"
|
||||
assert build_python_release.pep440_version("1.2.3-beta.10") == "1.2.3b10"
|
||||
|
||||
with pytest.raises(ValueError, match="no PEP 440 spelling"):
|
||||
build_python_release.pep440_version("1.2.3-nightly")
|
||||
|
||||
|
||||
def test_stage_sdk_keeps_distribution_module_and_runtime_pin_distinct(tmp_path: Path) -> None:
|
||||
destination = tmp_path / "staging"
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ def main() -> None:
|
||||
args = parser.parse_args()
|
||||
version = repository_version()
|
||||
validate_release_tag(args.tag, version)
|
||||
# Wheels carry the PEP 440 spelling; the tag keeps the repository spelling.
|
||||
wheel_version = pep440_version(version)
|
||||
if args.package == "runtime" and (args.platform is None or args.runtime_exe is None):
|
||||
parser.error("runtime builds require --platform and --runtime-exe")
|
||||
if args.package == "sdk" and (args.platform is not None or args.runtime_exe is not None):
|
||||
@@ -53,19 +55,19 @@ def main() -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="dsh-python-release-") as temporary:
|
||||
staging = Path(temporary) / args.package
|
||||
if args.package == "sdk":
|
||||
stage_sdk(staging, version)
|
||||
stage_sdk(staging, wheel_version)
|
||||
environment = None
|
||||
expected = output_dir / f"deepseek_harness_sdk-{version}-py3-none-any.whl"
|
||||
expected = output_dir / f"deepseek_harness_sdk-{wheel_version}-py3-none-any.whl"
|
||||
else:
|
||||
platform_tag, executable_name = PLATFORMS[args.platform]
|
||||
stage_runtime(staging, version, args.runtime_exe.resolve(), executable_name)
|
||||
stage_runtime(staging, wheel_version, args.runtime_exe.resolve(), executable_name)
|
||||
environment = {"DSH_RUNTIME_PLATFORM_TAG": platform_tag}
|
||||
expected = output_dir / f"deepseek_harness_runtime_bin-{version}-py3-none-{platform_tag}.whl"
|
||||
expected = output_dir / f"deepseek_harness_runtime_bin-{wheel_version}-py3-none-{platform_tag}.whl"
|
||||
command = ["uv", "build", "--wheel", "--out-dir", str(output_dir), str(staging)]
|
||||
subprocess.run(command, cwd=ROOT, env=None if environment is None else {**os.environ, **environment}, check=True)
|
||||
if not expected.is_file():
|
||||
raise RuntimeError(f"build did not produce expected wheel: {expected}")
|
||||
verify_wheel(expected, args.package, version, None if args.platform is None else PLATFORMS[args.platform])
|
||||
verify_wheel(expected, args.package, wheel_version, None if args.platform is None else PLATFORMS[args.platform])
|
||||
print(expected)
|
||||
|
||||
|
||||
@@ -76,13 +78,35 @@ def repository_version(root: Path = ROOT) -> str:
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"could not read repository version from {package_json}") from error
|
||||
version = payload.get("version") if isinstance(payload, dict) else None
|
||||
if not isinstance(version, str) or re.fullmatch(r"\d+\.\d+\.\d+", version) is None:
|
||||
if not isinstance(version, str) or re.fullmatch(r"\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?", version) is None:
|
||||
raise ValueError(
|
||||
f"{package_json} version must be stable X.Y.Z, got {version!r}"
|
||||
f"{package_json} version must be X.Y.Z with an optional prerelease segment, got {version!r}"
|
||||
)
|
||||
return version
|
||||
|
||||
|
||||
def pep440_version(version: str) -> str:
|
||||
"""The Python spelling of a repository version.
|
||||
|
||||
A release candidate is `0.0.1-rc.1` in the repository and `0.0.1rc1` under
|
||||
PEP 440. Build backends normalize to the latter, so the wheel filename and
|
||||
metadata carry it: comparing them against the repository spelling would
|
||||
reject every prerelease build.
|
||||
"""
|
||||
stable, separator, prerelease = version.partition("-")
|
||||
if not separator:
|
||||
return stable
|
||||
match = re.fullmatch(r"(a|b|c|rc|alpha|beta|pre|preview)\.?(\d+)", prerelease)
|
||||
if match is None:
|
||||
raise ValueError(
|
||||
f"prerelease segment {prerelease!r} has no PEP 440 spelling; use rc.N, alpha.N, or beta.N"
|
||||
)
|
||||
identifier = {"alpha": "a", "beta": "b", "c": "rc", "pre": "rc", "preview": "rc"}.get(
|
||||
match.group(1), match.group(1)
|
||||
)
|
||||
return f"{stable}{identifier}{match.group(2)}"
|
||||
|
||||
|
||||
def validate_release_tag(tag: str | None, version: str) -> None:
|
||||
if tag is None:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user