Skip to content

Commit 304126c

Browse files
authored
improve publishing logic (#954)
1 parent 665196a commit 304126c

File tree

1 file changed

+85
-27
lines changed

1 file changed

+85
-27
lines changed

noxfile.py

Lines changed: 85 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,49 @@
1212
from noxopt import Annotated, NoxOpt, Option, Session
1313

1414

15+
# --- Typing Preamble ------------------------------------------------------------------
16+
17+
1518
if TYPE_CHECKING:
1619
# not available in typing module until Python 3.8
17-
from typing import Literal
20+
# not available in typing module until Python 3.10
21+
from typing import Literal, Protocol, TypeAlias
22+
23+
class ReleasePrepFunc(Protocol):
24+
def __call__(self, session: Session) -> Callable[[bool], None]:
25+
...
26+
27+
28+
LanguageName: TypeAlias = "Literal['py', 'js']"
29+
30+
31+
# --- Constants ------------------------------------------------------------------------
32+
1833

1934
ROOT_DIR = Path(__file__).parent.resolve()
2035
SRC_DIR = ROOT_DIR / "src"
2136
CLIENT_DIR = SRC_DIR / "client"
2237
REACTPY_DIR = SRC_DIR / "reactpy"
23-
38+
LANGUAGE_TYPES: list[LanguageName] = ["py", "js"]
2439
TAG_PATTERN = re.compile(
25-
r"^(?P<name>[0-9a-zA-Z-@/]+)-v(?P<version>[0-9][0-9a-zA-Z-\.\+]*)$"
40+
# start
41+
r"^"
42+
# package name
43+
r"(?P<name>[0-9a-zA-Z-@/]+)-"
44+
# package language
45+
rf"(?P<language>{'|'.join(LANGUAGE_TYPES)})-"
46+
# package version
47+
r"v(?P<version>[0-9][0-9a-zA-Z-\.\+]*)"
48+
# end
49+
r"$"
2650
)
27-
51+
print(TAG_PATTERN.pattern)
2852
REMAINING_ARGS = Option(nargs=REMAINDER, type=str)
2953

54+
55+
# --- Session Setup --------------------------------------------------------------------
56+
57+
3058
group = NoxOpt(auto_tag=True)
3159

3260

@@ -42,6 +70,9 @@ def setup_javascript_checks(session: Session) -> None:
4270
session.run("npm", "ci", external=True)
4371

4472

73+
# --- Session Definitions --------------------------------------------------------------
74+
75+
4576
@group.session
4677
def format(session: Session) -> None:
4778
"""Auto format Python and Javascript code"""
@@ -236,31 +267,43 @@ def build_python(session: Session) -> None:
236267

237268

238269
@group.session
239-
def publish(session: Session) -> None:
270+
def publish(session: Session, dry_run: bool = False) -> None:
240271
packages = get_packages(session)
241272

242-
release_prep = {"js": prepare_javascript_release, "py": prepare_python_release}
273+
release_prep: dict[LanguageName, ReleasePrepFunc] = {
274+
"js": prepare_javascript_release,
275+
"py": prepare_python_release,
276+
}
277+
278+
publishers: list[Callable[[bool], None]] = []
279+
for tag, tag_lang, tag_pkg, tag_ver in get_current_tags(session):
280+
if tag_pkg not in packages:
281+
session.error(f"Tag {tag} references package {tag_pkg} that does not exist")
243282

244-
publishers: list[Callable[[], None]] = []
245-
for tag, tag_package, tag_version in get_current_tags(session):
246-
if tag_package not in packages:
247-
session.error(f"Tag {tag} references missing package {tag_package}")
283+
pkg_path, pkg_lang, pkg_ver = packages[tag_pkg]
284+
if pkg_ver != tag_ver:
285+
session.error(
286+
f"Tag {tag} references version {tag_ver} of package {tag_pkg}, "
287+
f"but the current version is {pkg_ver}"
288+
)
248289

249-
pkg_kind, pkg_path, pkg_version = packages[tag_package]
250-
if pkg_version != tag_version:
290+
if pkg_lang != tag_lang:
251291
session.error(
252-
f"Tag {tag} references version {tag_version} of package {tag_package}, "
253-
f"but the current version is {pkg_version}"
292+
f"Tag {tag} references language {tag_lang} of package {tag_pkg}, "
293+
f"but the current language is {pkg_lang}"
254294
)
255295

256296
session.chdir(pkg_path)
257-
session.log(f"Preparing {tag_package} for release...")
258-
publishers.append((pkg_path, release_prep[pkg_kind](session, tag_package)))
297+
session.log(f"Preparing {tag_pkg} for release...")
298+
publishers.append((pkg_path, release_prep[tag_lang](session)))
259299

260300
for pkg_path, publish in publishers:
261-
session.log(f"Publishing {tag_package}...")
301+
session.log(f"Publishing {tag_pkg}...")
262302
session.chdir(pkg_path)
263-
publish()
303+
publish(dry_run)
304+
305+
306+
# --- Utilities ------------------------------------------------------------------------
264307

265308

266309
def install_requirements_file(session: Session, name: str) -> None:
@@ -287,14 +330,17 @@ def get_reactpy_script_env() -> dict[str, str]:
287330
}
288331

289332

290-
def prepare_javascript_release(session: Session, name: str) -> Callable[[], None]:
333+
def prepare_javascript_release(session: Session) -> Callable[[bool], None]:
291334
node_auth_token = session.env.get("NODE_AUTH_TOKEN")
292335
if node_auth_token is None:
293336
session.error("NODE_AUTH_TOKEN environment variable must be set")
294337

295338
session.run("npm", "ci", external=True)
296339

297-
def publish() -> None:
340+
def publish(dry_run: bool) -> None:
341+
if dry_run:
342+
session.run("npm", "pack", "--dry-run", external=True)
343+
return
298344
session.run(
299345
"npm",
300346
"publish",
@@ -307,7 +353,7 @@ def publish() -> None:
307353
return publish
308354

309355

310-
def prepare_python_release(session: Session, name: str) -> Callable[[], None]:
356+
def prepare_python_release(session: Session) -> Callable[[bool], None]:
311357
twine_username = session.env.get("PYPI_USERNAME")
312358
twine_password = session.env.get("PYPI_PASSWORD")
313359

@@ -322,7 +368,11 @@ def prepare_python_release(session: Session, name: str) -> Callable[[], None]:
322368
install_requirements_file(session, "build-pkg")
323369
session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".")
324370

325-
def publish():
371+
def publish(dry_run: bool):
372+
if dry_run:
373+
session.run("twine", "check", "dist/*")
374+
return
375+
326376
session.run(
327377
"twine",
328378
"upload",
@@ -335,7 +385,7 @@ def publish():
335385

336386
def get_packages(session: Session) -> dict[str, PackageInfo]:
337387
packages: dict[str, PackageInfo] = {
338-
"reactpy": PackageInfo("py", ROOT_DIR, get_reactpy_package_version(session))
388+
"reactpy": PackageInfo(ROOT_DIR, "py", get_reactpy_package_version(session))
339389
}
340390

341391
# collect javascript packages
@@ -359,14 +409,14 @@ def get_packages(session: Session) -> dict[str, PackageInfo]:
359409
if pkg_name in packages:
360410
session.error(f"Duplicate package name {pkg_name}")
361411

362-
packages[pkg_name] = PackageInfo("js", pkg, pkg_version)
412+
packages[pkg_name] = PackageInfo(pkg, "js", pkg_version)
363413

364414
return packages
365415

366416

367417
class PackageInfo(NamedTuple):
368-
kind: Literal["js", "py"]
369418
path: Path
419+
language: LanguageName
370420
version: str
371421

372422

@@ -420,9 +470,16 @@ def get_current_tags(session: Session) -> list[TagInfo]:
420470
match = TAG_PATTERN.match(tag)
421471
if not match:
422472
session.error(
423-
f"Invalid tag {tag} - must be of the form <package>-<version>"
473+
f"Invalid tag {tag} - must be of the form <package>-<language>-<version>"
474+
)
475+
parsed_tags.append(
476+
TagInfo(
477+
tag,
478+
match["language"],
479+
match["name"],
480+
match["version"],
424481
)
425-
parsed_tags.append(TagInfo(tag, match["name"], match["version"]))
482+
)
426483

427484
session.log(f"Found tags: {[info.tag for info in parsed_tags]}")
428485

@@ -431,6 +488,7 @@ def get_current_tags(session: Session) -> list[TagInfo]:
431488

432489
class TagInfo(NamedTuple):
433490
tag: str
491+
language: LanguageName
434492
package: str
435493
version: str
436494

0 commit comments

Comments
 (0)