From 98a242175b445d4b7f5551ef44ea945e4cf03ea2 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Thu, 2 Mar 2023 11:17:56 -0800 Subject: [PATCH 1/2] allow independent py/js package releases --- .github/workflows/.nox-session.yml | 19 +- .github/workflows/codeql-analysis.yml | 60 ++--- .github/workflows/deploy-docs.yml | 2 +- .github/workflows/publish-js.yml | 31 --- .github/workflows/publish-py.yml | 38 --- .github/workflows/publish.yml | 21 ++ .github/workflows/test.yml | 4 +- VERSION | 1 - noxfile.py | 300 ++++++++++++++++-------- requirements/nox-deps.txt | 1 + scripts/changes_since_release.py | 56 ----- scripts/common/__init__.py | 0 scripts/common/github_utils.py | 54 ----- scripts/update_versions.py | 84 ------- setup.py | 4 +- src/client/package.json | 1 - src/client/packages/app/package.json | 3 +- src/client/packages/client/package.json | 2 +- 18 files changed, 277 insertions(+), 404 deletions(-) delete mode 100644 .github/workflows/publish-js.yml delete mode 100644 .github/workflows/publish-py.yml create mode 100644 .github/workflows/publish.yml delete mode 100644 VERSION delete mode 100644 scripts/changes_since_release.py delete mode 100644 scripts/common/__init__.py delete mode 100644 scripts/common/github_utils.py delete mode 100644 scripts/update_versions.py diff --git a/.github/workflows/.nox-session.yml b/.github/workflows/.nox-session.yml index 9cf5f645b..daddd127e 100644 --- a/.github/workflows/.nox-session.yml +++ b/.github/workflows/.nox-session.yml @@ -3,10 +3,17 @@ name: Nox Session on: workflow_call: inputs: + job-name: + required: true + type: string nox-args: required: true type: string - session-args: + nox-env: + required: false + type: string + default: "{}" + nox-session-args: required: false type: string runs-on-array: @@ -17,10 +24,6 @@ on: required: false type: string default: '["3.x"]' - job-name: - required: false - type: string - default: python-{0} {1} jobs: nox-session: @@ -35,14 +38,12 @@ jobs: - uses: actions/setup-node@v2 with: node-version: "14.x" - - name: Install Specific NPM Version - run: npm install -g npm@8.3 - name: Use Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install Python Dependencies run: pip install -r requirements/nox-deps.txt - - name: Run Tests - env: { "CI": "true" } + - name: Run Sessions + env: ${{ inputs.nox-env }} run: nox ${{ inputs.nox-args }} --stop-on-first-error -- ${{ inputs.session-args }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 830092f76..b4f77ee00 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,16 +9,16 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: codeql on: push: - branches: [ main ] + branches: [main] pull_request: # The branches below must be a subset of the branches above - branches: [ main ] + branches: [main] schedule: - - cron: '43 3 * * 3' + - cron: "43 3 * * 3" jobs: analyze: @@ -32,40 +32,40 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript', 'python' ] + language: ["javascript", "python"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 3de3cff17..7337f505b 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,7 +1,7 @@ # This workflows will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries -name: Deploy Documentation +name: deploy-docs on: push: diff --git a/.github/workflows/publish-js.yml b/.github/workflows/publish-js.yml deleted file mode 100644 index 8b9f1369e..000000000 --- a/.github/workflows/publish-js.yml +++ /dev/null @@ -1,31 +0,0 @@ -# This workflows will upload a Javscript Package using NPM to npmjs.org when a release is created -# For more information see: https://docs.github.com/en/actions/guides/publishing-nodejs-packages - -name: Publish Javascript - -on: - release: - types: [published] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v2 - with: - node-version: "14.x" - registry-url: "https://registry.npmjs.org" - - name: Install Specific NPM Version - run: npm install -g npm@8.3 - - name: Prepare Release - working-directory: ./src/client - run: | - npm install -g npm@7.22.0 - npm install - - name: Publish Release - working-directory: ./src/client - run: npm run publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }} diff --git a/.github/workflows/publish-py.yml b/.github/workflows/publish-py.yml deleted file mode 100644 index 799f5d060..000000000 --- a/.github/workflows/publish-py.yml +++ /dev/null @@ -1,38 +0,0 @@ -# This workflows will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - -name: Publish Python - -on: - release: - types: [published] - -jobs: - release-package: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "14.x" - - name: Install Specific NPM Version - run: npm install -g npm@8.3 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: "3.x" - - name: Install latest NPM - run: | - npm install -g npm@7.22.0 - npm --version - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements/build-pkg.txt - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python -m build --sdist --wheel --outdir dist . - twine upload dist/* diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..e892f2268 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,21 @@ +# This workflows will upload a Javscript Package using NPM to npmjs.org when a release is created +# For more information see: https://docs.github.com/en/actions/guides/publishing-nodejs-packages + +name: publish + +on: + release: + types: [published] + +jobs: + publish: + uses: ./.github/workflows/.nox-session.yml + with: + job-name: "publish" + nox-args: "-s publish" + nox-env: > + { + "NODE_AUTH_TOKEN": "${{ secrets.NPM_AUTOMATION_TOKEN }}", + "PYPI_USERNAME": "${{ secrets.PYPI_USERNAME }}", + "PYPI_PASSWORD": "${{ secrets.PYPI_PASSWORD }}" + } diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7779049b8..d20863e0d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,12 +16,12 @@ jobs: with: job-name: "python-{0}" nox-args: "-t check-python" - session-args: "--pytest --maxfail=3 --reruns 3" + nox-session-args: "--pytest --maxfail=3 --reruns 3" python-environments: uses: ./.github/workflows/.nox-session.yml with: nox-args: "-s check-python-tests" - session-args: "--no-cov --pytest --maxfail=3 --reruns 3" + nox-session-args: "--no-cov --pytest --maxfail=3 --reruns 3" runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]' python-version-array: '["3.7", "3.8", "3.9", "3.10", "3.11"]' docs: diff --git a/VERSION b/VERSION deleted file mode 100644 index 3eefcb9dd..000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.0.0 diff --git a/noxfile.py b/noxfile.py index b7ff0c269..d1d0a6706 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,17 +1,25 @@ from __future__ import annotations +import json import os +import re from argparse import REMAINDER from dataclasses import replace from pathlib import Path from shutil import rmtree -from typing import Sequence +from typing import Callable, Literal, NamedTuple, Sequence from noxopt import Annotated, NoxOpt, Option, Session -ROOT = Path(__file__).parent -CLIENT_DIR = ROOT / "src" / "client" +ROOT = Path(__file__).parent.resolve() +SRC_DIR = ROOT / "src" +CLIENT_DIR = SRC_DIR / "client" +REACTPY_DIR = SRC_DIR / "reactpy" + +TAG_PATTERN = re.compile( + r"^(?P[0-9a-zA-Z-@/]+)-v(?P[0-9][0-9a-zA-Z-\.\+]*)$" +) REMAINING_ARGS = Option(nargs=REMAINDER, type=str) @@ -19,14 +27,14 @@ @group.setup -def setup_checks( - session: Session, - ci: Annotated[bool, Option(help="whether running in CI")] = False, -) -> None: +def setup_checks(session: Session) -> None: session.install("--upgrade", "pip") - if ci: - session.log("Running in CI environment - installing latest NPM") - session.run("npm", "install", "-g", "npm@latest", external=True) + + +@group.setup("check-javascript") +def setup_javascript_checks(session: Session) -> None: + session.chdir(CLIENT_DIR) + session.run("npm", "ci", external=True) @group.session @@ -188,12 +196,6 @@ def check_docs(session: Session) -> None: session.run("docker", "build", ".", "--file", "docs/Dockerfile", external=True) -@group.setup("check-javascript") -def setup_javascript_checks(session: Session) -> None: - session.chdir(CLIENT_DIR) - session.run("npm", "install", external=True) - - @group.session def check_javascript_suite(session: Session) -> None: """Run the Javascript-based test suite and ensure it bundles succesfully""" @@ -221,85 +223,39 @@ def build_javascript(session: Session) -> None: @group.session def build_python(session: Session) -> None: - """Build javascript client code""" + """Build python package dist""" rmtree(str(ROOT / "build")) rmtree(str(ROOT / "dist")) - session.install("build", "wheel") + install_requirements_file(session, "build-pkg") session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".") @group.session -def tag(session: Session, version: str = "") -> None: - """Create a new git tag""" - try: - session.run( - "git", - "diff", - "--cached", - "--exit-code", - silent=True, - external=True, - ) - session.run( - "git", - "diff", - "--exit-code", - silent=True, - external=True, - ) - except Exception: - session.error("Cannot create a tag - there are uncommited changes") - - if not version: - session.error("No version tag given") - new_version = version - - install_requirements_file(session, "make-release") +def publish(session: Session) -> None: + packages = get_packages(session) - # check that version is valid semver - session.run("pysemver", "check", new_version) + release_prep = {"js": prepare_javascript_release, "py": prepare_python_release} - old_version = get_version() - session.log(f"Old version: {old_version}") - session.log(f"New version: {new_version}") - set_version(new_version) + publishers: list[Callable[[], None]] = [] + for tag, tag_package, tag_version in get_current_tags(session): + if tag_package not in packages: + session.error(f"Tag {tag} references missing package {tag_package}") - session.run("python", "scripts/update_versions.py") - - # trigger npm install to update package-lock.json - session.install("-e", ".") - - version = get_version() - install_requirements_file(session, "make-release") - session.run("pysemver", "check", version) - - changelog_file = ROOT / "docs" / "source" / "about" / "changelog.rst" - for line in changelog_file.read_text().splitlines(): - if line == f"v{version}": - session.log(f"Found changelog section for version {version}") - break - else: - session.error( - f"Something went wrong - could not find a title section for {version}" - ) + pkg_kind, pkg_path, pkg_version = packages[tag_package] + if pkg_version != tag_version: + session.error( + f"Tag {tag} references version {tag_version} of package {tag_package}, " + f"but the current version is {pkg_version}" + ) - if session.interactive: - response = input("Confirm (yes/no): ").lower() - if response != "yes": - session.error("Did not create tag") + session.chdir(pkg_path) + session.log(f"Preparing {tag_package} for release...") + publishers.append((pkg_path, release_prep[pkg_kind](session, tag_package))) - # stage, commit, tag, and push version bump - session.run("git", "add", "--all", external=True) - session.run("git", "commit", "-m", f"version {new_version}", external=True) - session.run("git", "tag", version, external=True) - session.run("git", "push", "origin", "main", "--tags", external=True) - - -@group.session -def changes_since_release(session: Session) -> None: - """Output the latest changes since the last release""" - session.install("requests", "python-dateutil") - session.run("python", "scripts/changes_since_release.py", *session.posargs) + for pkg_path, publish in publishers: + session.log(f"Publishing {tag_package}...") + session.chdir(pkg_path) + publish() def install_requirements_file(session: Session, name: str) -> None: @@ -316,14 +272,6 @@ def install_reactpy_dev(session: Session, extras: str = "all") -> None: session.posargs.remove("--no-install") -def get_version() -> str: - return (ROOT / "VERSION").read_text().strip() - - -def set_version(new: str) -> None: - (ROOT / "VERSION").write_text(new.strip() + "\n") - - def get_reactpy_script_env() -> dict[str, str]: return { "PYTHONPATH": os.getcwd(), @@ -333,3 +281,171 @@ def get_reactpy_script_env() -> dict[str, str]: ), "REACTPY_CHECK_VDOM_SPEC": os.environ.get("REACTPY_CHECK_VDOM_SPEC", "0"), } + + +def prepare_javascript_release(session: Session, name: str) -> Callable[[], None]: + node_auth_token = session.env.get("NODE_AUTH_TOKEN") + if node_auth_token is None: + session.error("NODE_AUTH_TOKEN environment variable must be set") + + session.run("npm", "ci", external=True) + + def publish() -> None: + session.run( + "npm", + "publish", + "--access", + "public", + external=True, + env={"NODE_AUTH_TOKEN": node_auth_token}, + ) + + return publish + + +def prepare_python_release(session: Session, name: str) -> Callable[[], None]: + twine_username = session.env.get("PYPI_USERNAME") + twine_password = session.env.get("PYPI_PASSWORD") + + if not (twine_password and twine_username): + session.error( + "PYPI_USERNAME and PYPI_PASSWORD environment variables must be set" + ) + + rmtree("build") + rmtree("dist") + + install_requirements_file(session, "build-pkg") + session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".") + + def publish(): + session.run( + "twine", + "upload", + "dist/*", + env={"TWINE_USERNAME": twine_username, "TWINE_PASSWORD": twine_password}, + ) + + return publish + + +def get_packages(session: Session) -> dict[str, PackageInfo]: + packages: dict[str, PackageInfo] = { + "reactpy": PackageInfo("py", ROOT, get_reactpy_package_version(session)) + } + + # collect javascript packages + for pkg in (CLIENT_DIR / "packages").glob("*"): + pkg_json_file = pkg / "package.json" + if not pkg_json_file.exists(): + session.error(f"package.json not found in {pkg}") + + pkg_json = json.loads(pkg_json_file.read_text()) + + pkg_name = pkg_json.get("name") + pkg_version = pkg_json.get("version") + + if pkg_version is None: + session.log(f"Skipping - {pkg_name} has no name or version in package.json") + continue + + if pkg_name is None: + session.error(f"Package {pkg} has no name in package.json") + + if pkg_name in packages: + session.error(f"Duplicate package name {pkg_name}") + + packages[pkg_name] = PackageInfo("js", pkg, pkg_version) + + return packages + + +class PackageInfo(NamedTuple): + kind: Literal["js", "py"] + path: Path + version: str + + +def get_current_tags(session: Session) -> list[TagInfo]: + """Get tags for the current commit""" + # check if unstaged changes + try: + session.run( + "git", + "diff", + "--cached", + "--exit-code", + silent=True, + external=True, + ) + session.run( + "git", + "diff", + "--exit-code", + silent=True, + external=True, + ) + except Exception: + session.error("Cannot create a tag - there are uncommited changes") + + tags_per_commit: dict[str, list[str]] = {} + for commit, tag in map( + str.split, + session.run( + "git", + "for-each-ref", + "--format", + r"%(objectname) %(refname:short)", + "refs/tags", + silent=True, + external=True, + ).splitlines(), + ): + tags_per_commit.setdefault(commit, []).append(tag) + + current_commit = session.run( + "git", "rev-parse", "HEAD", silent=True, external=True + ).strip() + tags = tags_per_commit.get(current_commit, []) + + if not tags: + session.error("No tags found for current commit") + + parsed_tags: list[TagInfo] = [] + for tag in tags: + match = TAG_PATTERN.match(tag) + if not match: + session.error( + f"Invalid tag {tag} - must be of the form -" + ) + parsed_tags.append(TagInfo(tag, match["name"], match["version"])) + + session.log(f"Found tags: {[info.tag for info in parsed_tags]}") + + return parsed_tags + + +class TagInfo(NamedTuple): + tag: str + package: str + version: str + + +def get_reactpy_package_version(session: Session) -> str: + pkg_root_init_file = REACTPY_DIR / "__init__.py" + for line in pkg_root_init_file.read_text().split("\n"): + if line.startswith('__version__ = "') and line.endswith('" # DO NOT MODIFY'): + return ( + line + # get assignment value + .split("=", 1)[1] + # remove "DO NOT MODIFY" comment + .split("#", 1)[0] + # clean up leading/trailing space + .strip() + # remove the quotes + [1:-1] + ) + break + else: + session.error(f"No version found in {pkg_root_init_file}") diff --git a/requirements/nox-deps.txt b/requirements/nox-deps.txt index 218b48093..b336417e9 100644 --- a/requirements/nox-deps.txt +++ b/requirements/nox-deps.txt @@ -1,2 +1,3 @@ nox noxopt +requests diff --git a/scripts/changes_since_release.py b/scripts/changes_since_release.py deleted file mode 100644 index b12f6f94f..000000000 --- a/scripts/changes_since_release.py +++ /dev/null @@ -1,56 +0,0 @@ -from __future__ import annotations - -import sys - -from common.github_utils import ( - REPO_NAME, - date_range_query, - last_release_date, - search_reactpy_repo, -) - - -SECTION_FORMAT_TEMPLATES = { - "md": lambda title: f"# {title}", - "rst": lambda title: f"**{title}**\n", - "text": lambda title: f"{title}\n{'-' * len(title)}", -} - - -ISSUE_FORMAT_TEMPLATES = { - "md": lambda title, number, **_: f"- {title} - [#{number}](https://github.com/{REPO_NAME}/issues/{number})", - "rst": lambda title, number, **_: f"- {title} - :issue:`{number}`", - "text": lambda title, number, **_: f"- {title} - #{number}", -} - -PULL_REQUEST_FORMAT_TEMPLATES = { - "md": lambda title, number, **_: f"- {title} - [#{number}](https://github.com/{REPO_NAME}/pull/{number})", - "rst": lambda title, number, **_: f"- {title} - :pull:`{number}`", - "text": lambda title, number, **_: f"- {title} - #{number}", -} - - -def show_issues(format: str): - print(SECTION_FORMAT_TEMPLATES[format]("Closed Issues")) - template = ISSUE_FORMAT_TEMPLATES[format] - query = f"type:issue closed:{date_range_query(last_release_date())}" - for issue in search_reactpy_repo(query): - print(template(**issue)) - - -def show_pull_requests(format: str = "text"): - print(SECTION_FORMAT_TEMPLATES[format]("Merged Pull Requests")) - template = PULL_REQUEST_FORMAT_TEMPLATES[format] - query = f"type:pr merged:{date_range_query(last_release_date())}" - for pull in search_reactpy_repo(query): - print(template(**pull)) - - -def main(format: str = "text"): - for func in [show_issues, show_pull_requests]: - func(format) - print() - - -if __name__ == "__main__": - main(*sys.argv[1:]) diff --git a/scripts/common/__init__.py b/scripts/common/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/scripts/common/github_utils.py b/scripts/common/github_utils.py deleted file mode 100644 index f72d6a78c..000000000 --- a/scripts/common/github_utils.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - -from datetime import datetime -from typing import Any, Iterator, Optional - -import requests -from dateutil.parser import isoparse - - -REPO_NAME = "reactive-python/reactpy" - - -def last_release_date() -> datetime: - response = requests.get(f"https://api.github.com/repos/{REPO_NAME}/releases/latest") - return isoparse(response.json()["published_at"]) - - -def date_range_query( - start: Optional[datetime] = None, - stop: Optional[datetime] = None, -) -> str: - assert start or stop, "No date range given" - - if stop is None: - return ">" + start.isoformat() - - if start is None: - return "<=" + stop.isoformat() - - return start.isoformat() + ".." + stop.isoformat() - - -def search_reactpy_repo(query: str) -> Iterator[Any]: - page = 0 - while True: - page += 1 - response = requests.get( - "https://api.github.com/search/issues", - {"q": f"repo:{REPO_NAME} " + query, "per_page": 15, "page": page}, - ) - - response_json = response.json() - - try: - if response_json["incomplete_results"]: - raise RuntimeError(response) - except KeyError: - raise RuntimeError(response_json) - - items = response_json["items"] - if items: - yield from items - else: - break diff --git a/scripts/update_versions.py b/scripts/update_versions.py deleted file mode 100644 index 949bf1709..000000000 --- a/scripts/update_versions.py +++ /dev/null @@ -1,84 +0,0 @@ -import json -from datetime import datetime -from pathlib import Path - -import semver - - -ROOT = Path("__file__").parent.parent -VERSION_FILE = ROOT / Path("VERSION") -PY_PKG_INIT_FILE = ROOT / "src" / "reactpy" / "__init__.py" -JS_ROOT_DIR = ROOT / "src" / "client" -JS_PACKAGE_JSON_FILES = [ - pkg_dir / "package.json" for pkg_dir in (JS_ROOT_DIR / "packages").iterdir() -] + [JS_ROOT_DIR / "package.json"] -CHANGELOG_FILE = ROOT / "docs" / "source" / "about" / "changelog.rst" -VERSION_INFO = semver.VersionInfo.parse(VERSION_FILE.read_text().strip()) - - -def main() -> None: - version_str = str(VERSION_INFO) - update_py_version(version_str) - update_js_versions(version_str) - update_changelog_version(version_str) - - -def update_py_version(new_version: str) -> None: - new_lines = PY_PKG_INIT_FILE.read_text().splitlines() - for index, line in enumerate(new_lines): - if line.startswith('__version__ = "') and line.endswith('" # DO NOT MODIFY'): - line = f'__version__ = "{new_version}" # DO NOT MODIFY' - new_lines[index] = line - break - else: - raise RuntimeError(f"No __version__ assignment found in {PY_PKG_INIT_FILE}") - PY_PKG_INIT_FILE.write_text("\n".join(new_lines) + "\n") - - -def update_js_versions(new_version: str) -> None: - for pkg_json_file in JS_PACKAGE_JSON_FILES: - pkg_json = json.loads(pkg_json_file.read_text()) - pkg_json["version"] = new_version - pkg_json_file.write_text(json.dumps(pkg_json, indent=2, sort_keys=True) + "\n") - - -def update_changelog_version(new_version: str) -> None: - today = datetime.now().strftime("%Y-%m-%d") - old_content = CHANGELOG_FILE.read_text().split("\n") - - new_content = [] - for index in range(len(old_content) - 1): - if index == len(old_content) - 2: - # reached end of file - continue - - this_line, next_line = old_content[index : index + 2] - if this_line == "Unreleased" and next_line == ("-" * len(this_line)): - new_content.append(_UNRELEASED_SECTION) - - title = f"v{new_version}" - new_content.append(title) - new_content.append("-" * len(title)) - new_content.append(f":octicon:`milestone` *released on {today}*") - - new_content.extend(old_content[index + 2 :]) - break - else: - new_content.append(this_line) - else: - raise ValueError(f"Did not find 'Unreleased' section in {CHANGELOG_FILE}") - - CHANGELOG_FILE.write_text("\n".join(new_content)) - - -_UNRELEASED_SECTION = """\ -Unreleased ----------- - -No changes. - -""" - - -if __name__ == "__main__": - main() diff --git a/setup.py b/setup.py index e0f998876..5f256008f 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ def list2cmdline(cmd_list): "description": "It's React, but in Python", "author": "Ryan Morshead", "author_email": "ryan.morshead@gmail.com", - "url": "https://github.com/rmorshea/reactpy", + "url": "https://github.com/reactive-python/reactpy", "license": "MIT", "platforms": "Linux, Mac OS X, Windows", "keywords": ["interactive", "widgets", "DOM", "React"], @@ -164,7 +164,7 @@ def run(self): npm = shutil.which("npm") # this is required on windows if npm is None: raise RuntimeError("NPM is not installed.") - for args in (f"{npm} install", f"{npm} run build"): + for args in (f"{npm} ci", f"{npm} run build"): args_list = args.split() log.info(f"> {list2cmdline(args_list)}") subprocess.run(args_list, cwd=str(JS_DIR), check=True) diff --git a/src/client/package.json b/src/client/package.json index 55f0ef10c..d480580f2 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -11,7 +11,6 @@ "build": "vite build", "check-format": "npm --workspaces run check-format", "format": "npm --workspaces run format", - "publish": "npm --workspaces publish", "test": "npm --workspaces test" }, "version": "1.0.0", diff --git a/src/client/packages/app/package.json b/src/client/packages/app/package.json index 25fbf4c04..0424577a9 100644 --- a/src/client/packages/app/package.json +++ b/src/client/packages/app/package.json @@ -19,6 +19,5 @@ "check-format": "prettier --check ./src", "format": "prettier --write ./src", "test": "echo 'no tests'" - }, - "version": "0.45.0" + } } diff --git a/src/client/packages/client/package.json b/src/client/packages/client/package.json index 12df7401f..e9bf4cc13 100644 --- a/src/client/packages/client/package.json +++ b/src/client/packages/client/package.json @@ -31,5 +31,5 @@ "test": "uvu tests" }, "type": "module", - "version": "0.45.0" + "version": "0.1.0" } From ad4a786cfeb4cc07d4d07b133777f7f094757a03 Mon Sep 17 00:00:00 2001 From: rmorshea Date: Fri, 3 Mar 2023 12:21:37 -0800 Subject: [PATCH 2/2] misc fixes --- .github/workflows/.nox-session.yml | 10 ++++++---- .github/workflows/test.yml | 1 + noxfile.py | 22 +++++++++++++--------- requirements/nox-deps.txt | 1 - 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.github/workflows/.nox-session.yml b/.github/workflows/.nox-session.yml index daddd127e..2ce3773fa 100644 --- a/.github/workflows/.nox-session.yml +++ b/.github/workflows/.nox-session.yml @@ -30,14 +30,16 @@ jobs: name: ${{ format(inputs.job-name, matrix.python-version, matrix.runs-on) }} strategy: matrix: - runs-on: ${{fromJson(inputs.runs-on-array)}} - python-version: ${{fromJson(inputs.python-version-array)}} + runs-on: ${{ fromJson(inputs.runs-on-array) }} + python-version: ${{ fromJson(inputs.python-version-array) }} runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: "14.x" + - name: Pin NPM Version + run: npm install -g npm@8.3 - name: Use Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: @@ -45,5 +47,5 @@ jobs: - name: Install Python Dependencies run: pip install -r requirements/nox-deps.txt - name: Run Sessions - env: ${{ inputs.nox-env }} - run: nox ${{ inputs.nox-args }} --stop-on-first-error -- ${{ inputs.session-args }} + env: ${{ fromJson(inputs.nox-env) }} + run: nox ${{ inputs.nox-args }} --stop-on-first-error -- ${{ inputs.nox-session-args }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d20863e0d..0b733f924 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,6 +20,7 @@ jobs: python-environments: uses: ./.github/workflows/.nox-session.yml with: + job-name: "python-{0} {1}" nox-args: "-s check-python-tests" nox-session-args: "--no-cov --pytest --maxfail=3 --reruns 3" runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]' diff --git a/noxfile.py b/noxfile.py index d1d0a6706..e4caf1785 100644 --- a/noxfile.py +++ b/noxfile.py @@ -7,13 +7,17 @@ from dataclasses import replace from pathlib import Path from shutil import rmtree -from typing import Callable, Literal, NamedTuple, Sequence +from typing import TYPE_CHECKING, Callable, NamedTuple, Sequence from noxopt import Annotated, NoxOpt, Option, Session -ROOT = Path(__file__).parent.resolve() -SRC_DIR = ROOT / "src" +if TYPE_CHECKING: + # not available in typing module until Python 3.8 + from typing import Literal + +ROOT_DIR = Path(__file__).parent.resolve() +SRC_DIR = ROOT_DIR / "src" CLIENT_DIR = SRC_DIR / "client" REACTPY_DIR = SRC_DIR / "reactpy" @@ -29,6 +33,7 @@ @group.setup def setup_checks(session: Session) -> None: session.install("--upgrade", "pip") + session.run("pip", "--version") @group.setup("check-javascript") @@ -50,7 +55,7 @@ def format(session: Session) -> None: session.run("npm", "run", "format", external=True) # format docs Javascript - session.chdir(ROOT / "docs" / "source" / "_custom_js") + session.chdir(ROOT_DIR / "docs" / "source" / "_custom_js") session.run("npm", "run", "format", external=True) @@ -224,8 +229,8 @@ def build_javascript(session: Session) -> None: @group.session def build_python(session: Session) -> None: """Build python package dist""" - rmtree(str(ROOT / "build")) - rmtree(str(ROOT / "dist")) + rmtree(str(ROOT_DIR / "build")) + rmtree(str(ROOT_DIR / "dist")) install_requirements_file(session, "build-pkg") session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".") @@ -259,13 +264,12 @@ def publish(session: Session) -> None: def install_requirements_file(session: Session, name: str) -> None: - file_path = ROOT / "requirements" / (name + ".txt") + file_path = ROOT_DIR / "requirements" / (name + ".txt") assert file_path.exists(), f"requirements file {file_path} does not exist" session.install("-r", str(file_path)) def install_reactpy_dev(session: Session, extras: str = "all") -> None: - session.run("pip", "--version") if "--no-install" not in session.posargs: session.install("-e", f".[{extras}]") else: @@ -331,7 +335,7 @@ def publish(): def get_packages(session: Session) -> dict[str, PackageInfo]: packages: dict[str, PackageInfo] = { - "reactpy": PackageInfo("py", ROOT, get_reactpy_package_version(session)) + "reactpy": PackageInfo("py", ROOT_DIR, get_reactpy_package_version(session)) } # collect javascript packages diff --git a/requirements/nox-deps.txt b/requirements/nox-deps.txt index b336417e9..218b48093 100644 --- a/requirements/nox-deps.txt +++ b/requirements/nox-deps.txt @@ -1,3 +1,2 @@ nox noxopt -requests