diff --git a/.gitignore b/.gitignore index 61519e3..2a149a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Directory created from using the 'rsm' CLI tool. -.rsm/ +/.rsm/ # The recommended virtualenv directory. /.venv/ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..9919bf8 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10.13 diff --git a/.rsmrc b/.rsmrc index f10b5d8..cd08df6 100644 --- a/.rsmrc +++ b/.rsmrc @@ -25,8 +25,8 @@ # `.rsmrc` as the working directory so that any paths that we specify # below are relative to it. protoc --working-directory=. -dev --working-directory=. -dev --protoc-watch +dev run --working-directory=. +dev run --protoc-watch # Generate code from our '.proto' files in 'api/'. protoc --output-directory=api/ @@ -39,19 +39,23 @@ protoc api/ protoc -- --resemble_python_out=backend/api --resemble_react_out=web/src/api # Watch if any generated files are modified. -dev --watch=backend/api/**/*.py +dev run --watch=backend/api/**/*.py # Watch if any of our source files are modified. -dev --watch=backend/src/**/*.py +dev run --watch=backend/src/**/*.py # PYTHONPATH must be explicitly set to pick up generated code. -dev --env=PYTHONPATH=backend/api/ +dev run --env=PYTHONPATH=backend/api/ # Tell `rsm` that this is a Python application. -dev --python +dev run --python # Save state between chaos restarts. -dev --name=hello +dev run --name=hello # Run the application! -dev backend/src/main.py +dev run backend/src/main.py + +# When running `rsm dev expunge`, the state we want to remove is that of the +# "hello" application, since that's what we were running with `rsm dev run`. +dev expunge --name=hello diff --git a/.tests/test.sh b/.tests/test.sh index fd686b3..1de854d 100755 --- a/.tests/test.sh +++ b/.tests/test.sh @@ -12,48 +12,30 @@ ls -l api/ backend/src/ web/ 2> /dev/null > /dev/null || { exit 1 } -# Create and activate a virtual environment so that we don't pollute the -# system's Python installation. -VENV="./.resemble-hello-venv" -python -m venv $VENV -source $VENV/bin/activate - -# If `REBOOT_RESEMBLE_WHL_FILE` is set, have it refer to an absolute non-symlink -# (= canonical) path. -if [ -v REBOOT_RESEMBLE_WHL_FILE ]; then - REBOOT_RESEMBLE_WHL_FILE=$(readlink --canonicalize $REBOOT_RESEMBLE_WHL_FILE) +# Convert symlinks to files that we need to mutate into copies. +for file in "requirements.lock" "requirements-dev.lock" "pyproject.toml"; do + cp "$file" "${file}.tmp" + rm "$file" + mv "${file}.tmp" "$file" +done + +# Use the published Resemble pip package by default, but allow the test system +# to override them with a different value. +if [ -n "$REBOOT_RESEMBLE_WHL_FILE" ]; then + # Install the `reboot-resemble` package from the specified path explicitly, over- + # writing the version from `pyproject.toml`. + rye remove --no-sync reboot-resemble + rye remove --no-sync --dev reboot-resemble + rye add --dev reboot-resemble --absolute --path=$REBOOT_RESEMBLE_WHL_FILE fi -# Normally, tests will use the published Resemble PyPI package; this is what -# happens when this test is run from `.github/workflows/*.yml`. -# -# However, when there is a need to test changes to the Resemble package itself, -# the test system can override the default and use an explicit local wheel file -# instead. -REBOOT_RESEMBLE_PACKAGE=${REBOOT_RESEMBLE_WHL_FILE:-"reboot-resemble"} - -# Manually install the Resemble pip package before installing the -# requirements.txt. This allows us to install unreleased versions of -# the Resemble package during tests. -pip install $REBOOT_RESEMBLE_PACKAGE - -# Save the pip show info on the package so that we can compare it after -# installing the rest of the requirements, to check that our custom whl hasn't -# been overwritten. -resemble_info=$(pip show reboot-resemble) - -pip install -r backend/src/requirements.txt - -# Double check that we haven't reinstalled another version of the -# reboot-resemble package. -if [ "$resemble_info" != "$(pip show reboot-resemble)" ]; then - echo "ERROR: reboot-resemble whl overwritten by pip install. Are the package versions out of sync?" - exit 1 -fi +# Create and activate a virtual environment. +rye sync --no-lock +source .venv/bin/activate rsm protoc -mypy --python-executable=$VENV/bin/python backend/ +mypy backend/ pytest backend/ @@ -62,6 +44,11 @@ pytest backend/ # We will only do this if this machine has the `docker` command installed. That # means this is skipped on e.g. GitHub's Mac OS X runners. if command -v docker &> /dev/null; then + if [ -n "$REBOOT_RESEMBLE_WHL_FILE" ]; then + # If `REBOOT_RESEMBLE_WHL_FILE` is set, have it refer to an absolute non-symlink + # (= canonical) path. + REBOOT_RESEMBLE_WHL_FILE=$(readlink --canonicalize $REBOOT_RESEMBLE_WHL_FILE) + fi # Since Docker can't follow symlinks to files outside the build context, we # can't build the Docker image in a directory where the Dockerfile is a symlink. # That situation occurs when e.g. running this test on Bazel. Follow the symlink @@ -71,6 +58,3 @@ if command -v docker &> /dev/null; then popd fi - -# Clean up. -rm -rf ./.resemble-hello-venv diff --git a/BUILD.bazel b/BUILD.bazel index deb8585..06e36fc 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -11,6 +11,7 @@ filegroup( # Files that may be created by activity in this directory, such # as manually following the steps of the `README.md` file, but which # are not part of the "source code" of this repository. + ".venv/**/*", ".resemble-hello-venv/**/*", ".pytest_cache/**/*", "api/gen/**/*.js", diff --git a/Dockerfile b/Dockerfile index 84f8dbe..f2e9755 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,6 +52,15 @@ RUN mkdir $NVM_DIR && \ # Our container will run as `root`, so the root user must load `nvm` on login. RUN echo ". $NVM_DIR/nvm.sh" >> /root/.bashrc +### Our application. + +# First ONLY copy and install the requirements, so that changes outside +# `requirements.txt` don't force a re-install of all dependencies. +# +# Note that this will install the Resemble library and CLI. +COPY requirements.lock requirements.txt +RUN pip install -r requirements.txt + ### Unpublished Resemble package. # If you plan to use this Dockerfile for your own project, you may omit this # section; it is useful only for Reboot's internal development. Outside of @@ -70,17 +79,9 @@ COPY .unpublished-*-wheel/*.whl .unpublished-resemble-wheel/ # If `.unpublished-resemble-wheel/` was empty or did not exist, the following # `ls` will fail and instead of `pip install` we'll run `echo`, which means this # RUN has no effects in that case. -RUN (ls ./.unpublished-resemble-wheel/*.whl && pip install ./.unpublished-resemble-wheel/*.whl) \ +RUN (ls ./.unpublished-resemble-wheel/*.whl && pip install --force-reinstall ./.unpublished-resemble-wheel/*.whl) \ || echo "No unpublished wheels to install." - -### Our application. - -# First ONLY copy and install the requirements, so that changes outside -# `requirements.txt` don't force a re-install of all dependencies. -# -# Note that this will install the Resemble library and CLI. -COPY backend/src/requirements.txt requirements.txt -RUN pip install -r requirements.txt +### End of interlude. # Next, copy the API definition and generate Resemble code. This step is also # separate so it is only repeated if the `api/` code changes. diff --git a/README.md b/README.md index eebb3e8..eb9400a 100644 --- a/README.md +++ b/README.md @@ -1,188 +1,80 @@ # Resemble Hello World For the impatient: -1. Get a suitable environment: - * Use VSCode (on your machine) - * [... connected to a GitHub Codespace](#use-vscode-connected-to-a-github-codespace) - * [... with a local Dev Container](#use-vscode-with-a-local-dev-container) - * [Use a Docker Container](#use-a-docker-container) - * [Install prerequisites manually](#install-prerequisites-manually) +1. Prepare an environment by either: + * [Using VSCode connected to a GitHub Codespace](#using-vscode-connected-to-a-github-codespace) + * [Installing prerequisites directly](#installing-prerequisites-directly) 2. [Run the application](#run-the-application) ### Overview This repository contains a simple example application written using Resemble. -The '.proto' files can be found in the `api/` directory, grouped into +The [Resemble '.proto' definition](https://docs.reboot.dev/docs/model/overview#generated-code) +can be found in the `api/` directory, grouped into subdirectories by proto package, while backend specific code can be found in `backend/` and web specific code in `web/`. -This repository includes a [Dev Container](https://containers.dev/) that _has all of the dependencies you need to build and run code in this repository already installed_. +## Prepare an environment by... -> [!NOTE] -> The Dev Container's configuration for this repository is found in -> [`.devcontainer/devcontainer.json`](main/.devcontainer/devcontainer.json). You -> may expand on it to customize your development environment to your -> liking. + +### Using VSCode connected to a GitHub Codespace -You can start the Dev Container in two different ways. +This method requires running [VSCode](https://code.visualstudio.com/) on your machine: if that isn't your bag, see [the other environment option](#install-prerequisites-directly) below. - -## Use VSCode connected to a GitHub Codespace +This repository includes a [Dev Container config](./.devcontainer/devcontainer.json) (more about [Dev Containers](https://containers.dev/)) that declares all of the dependencies that you need to build and run the example. Dev Containers can be started locally with VSCode, but we recommend using GitHub's [Codespaces](https://github.com/features/codespaces) to quickly launch the Dev Container: -GitHub's [Codespaces](https://github.com/features/codespaces) are machines that -are hosted in the cloud for you. - -> [!IMPORTANT] -> You must connect your local VSCode to the codespace, you can not use VSCode in a browser window. - -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/reboot-dev/resemble-hello) -
-(Right-Click to open in new tab or window) - -If you haven't [set your default editor to VSCode for codespaces](https://docs.github.com/en/codespaces/customizing-your-codespace/setting-your-default-editor-for-github-codespaces), then the 'Open in GitHub Codespaces' button above will end up opening VSCode in the browser. You can close that browser tab because _YOU MUST_ [open the existing codespace](https://docs.github.com/en/codespaces/developing-in-codespaces/opening-an-existing-codespace?tool=vscode) using the VSCode on your machine. You can also go to [https://github.com/codespaces](https://github.com/codespaces) and click the three dots next to the codespace you just created and then click `Open in ...` then `Open in Visual Studio Code`. - -Now you're ready to [run the application](#run-the-application)! - - -## Use VSCode with a local Dev Container - -> [!IMPORTANT] -> Currently, our Dev Container at [`.devcontainer/devcontainer.json`](main/.devcontainer/devcontainer.json) **only works on x86 CPU architectures**. - -If your machine meets the required specifications, you can start this -repository's Dev Container with VSCode locally rather than using a GitHub Codespace. - -Clone this repository: - - - -```shell -git clone https://github.com/reboot-dev/resemble-hello.git -``` - -Open the Dev Container: - -- In VSCode, open the `resemble-hello` folder you've cloned. -- Press: Ctrl+Shift+P (Linux / Windows) or Command+Shift+P (Mac) -- Type/Select: `Dev Containers: Reopen In Container` - -VSCode will now start the Dev Container and restart VSCode to be running -inside of that container. +1. Right-click to create a Codespace in a new tab or window: + * [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/reboot-dev/resemble-hello) + * *Important*: In order to view the example's frontend, you must connect your local VSCode to the codespace: you cannot use VSCode in a browser window. +2. Go to [https://github.com/codespaces](https://github.com/codespaces) and click the three dots next to the codespace you just created and then click `Open in Visual Studio Code`. + * You can [set your default editor to VSCode for codespaces](https://docs.github.com/en/codespaces/customizing-your-codespace/setting-your-default-editor-for-github-codespaces) to avoid this step in the future. See [these instructions](https://docs.github.com/en/codespaces/developing-in-codespaces/opening-an-existing-codespace?tool=vscode) for more information. Now you're ready to [run the application](#run-the-application)! - -## Use a Docker container - -We've created a [Docker container](ghcr.io/reboot-dev/resemble-standalone) that _has all of the dependencies you need to build and run code in this repository already installed_. -> [!IMPORTANT] -> The Docker container currently **only works on x86 CPU architectures**. Check back soon for more supported architectures. + +### Installing prerequisites directly -Clone this repository: +Running directly on a host requires: -```shell -git clone https://github.com/reboot-dev/resemble-hello.git -cd resemble-hello/ -``` - -Run the container: - -```shell -export HOST_WORKING_DIRECTORY="$(pwd)" -export CONTAINER_WORKSPACE_DIRECTORY="/workspaces/$(basename $HOST_WORKING_DIRECTORY)" -docker run \ - --mount type=bind,source="$HOST_WORKING_DIRECTORY",target="$CONTAINER_WORKSPACE_DIRECTORY" \ - --workdir "$CONTAINER_WORKSPACE_DIRECTORY" \ - --env "HOST_UID=$(id -u)" \ - --env "HOST_GID=$(id -g)" \ - -p 127.0.0.1:3000:3000/tcp \ - -p 127.0.0.1:9991:9991/tcp \ - --privileged \ - --interactive \ - --tty \ - ghcr.io/reboot-dev/resemble-standalone:latest \ - /bin/bash -``` - -Explanation of flags: -* We --mount our --workdir (working directory), so we can work with it from the container. -* We tell the container about our user's UID and GID so that the container's - user can match them, providing the same permissions inside and outside the - container. -* We bind port 3000 so that we can access a React web front end (e.g., from a browser), and port 9991 so the web front end can access the Resemble backend. -* `--privileged` so that we can run Docker inside of the container. -* `--interactive` and `--tty` (often abbreviated `-it`) lets us interact with - the created container. -* `ghcr.io/reboot-dev/resemble-standalone:latest` is the name of the container we'll be running. -* `/bin/bash` is the shell we'd like to run. - -Now you're ready to [run the application](#run-the-application)! - - -## Install prerequisites manually - -> [!IMPORTANT] -> Resemble backends currently can **on x86_64 Linux** machines with -> `glibc>=2.35` (Ubuntu Jammy and other equivalent-generation Linux -> distributions), and **on arm64/x86_64 MacOS**, where `MacOS>=13.0` and -> `Xcode>=14.3`. If you have a machine that doesn't fit this requirement, we -> suggest using one of the approaches discussed above. -### Prerequisites - -You must have the following tools installed: - -- Python (including `pip` and `venv`) >= 3.10 -- Node.js (including `npm`) +- A platform of either: + - `x86_64 Linux` with `glibc>=2.35` (Ubuntu Jammy and other equivalent-generation Linux distributions) + - `arm64 or x86_64 MacOS` with `MacOS>=13.0` and `Xcode>=14.3` +- [Rye](https://rye-up.com/) + - A tool to manage `python`, `pip`, and `venv`. If you are already familiar with Python [virtual environments](https://docs.python.org/3/library/venv.html), feel free to use your tool of choice with [`pyproject.toml`](./pyproject.toml). Python>=3.10 is required. +- Node.js + - Including `npm`. - Docker + - Note: the example does not run "inside of" Docker, but Docker is used to host a native support service for local development. -### Clone Repository - -Clone this repository: - -```shell -git clone https://github.com/reboot-dev/resemble-hello.git -cd resemble-hello/ -``` - -### Create and activate a virtual environment - -Create a new Python virtual environment in which to install Resemble -requirements and run an application: - -```sh -python -m venv ./.venv -source ./.venv/bin/activate -``` - -To learn more about why virtual environments are a best practice for Python -projects, see [the Python documentation for the `venv` module.](https://docs.python.org/3/library/venv.html) +If you are unable to meet any of these requirements, we suggest using the [VSCode and Dev Container environment](#using-vscode-connected-to-a-github-codespace) discussed above. Now you're ready to [run the application](#run-the-application)! ## Run the application -### Backend via `rsm dev` +### Backend via `rsm dev run` Our backend is implemented in Python and we must install its dependencies before running it. The most notable of those dependencies is the `reboot-resemble` PyPI distribution, which contains both the Resemble CLI (`rsm`) and the `resemble` Python package. +Using `rye`, we can create and activate a virtualenv containing this project's dependencies (as well as fetch an appropriate Python version) using: ```sh -pip install -r backend/src/requirements.txt +rye sync --no-lock +source .venv/bin/activate ``` -To run the application, you can now use the Resemble CLI `rsm`: - +Then, to run the application, you can use the Resemble CLI `rsm` (present in the active virtualenv): ```shell -rsm dev +rsm dev run ``` -Running `rsm dev` will watch for file modifications and restart the +Running `rsm dev run` will watch for file modifications and restart the application if necessary. See the `.rsmrc` file for flags and -arguments that get expanded when running `rsm dev`. +arguments that get expanded when running `rsm dev run`. ### Front end @@ -200,7 +92,7 @@ If not using VSCode, visit [http://127.0.0.1:3000](http://127.0.0.1:3000)`. The application comes with backend tests. Before you run the tests, you'll -need to ensure you've run `rsm protoc`. If you've already run `rsm dev` +need to ensure you've run `rsm protoc`. If you've already run `rsm dev run` without modifying `.rsmrc`, `rsm protoc` will have been run for you as part of that command. Otherwise, you can do it manually. @@ -219,14 +111,14 @@ Now you can run the tests using `pytest`: pytest backend/ ``` -### Running on the Resemble Cloud +### Running on the Reboot Cloud Pick a public Docker registry you can push images to. Determine the name you'd like the image to have in that registry. For example: `ghcr.io/your-github-username/resemble-hello`. Then, run the following to build and push your `resemble-hello` container: -``` +```shell export IMAGE_NAME= ./build.sh --push $IMAGE_NAME ``` @@ -248,21 +140,21 @@ To make calls to the application that just started, get the endpoint URL from message output to the console. ```sh -Application starting; you application will be available at: +Application starting; your application will be available at: -..resemble.cloud:9991 +.prod1.resemble.cloud:9991 ``` To build a version of the frontend that can talk to the deployed app, replace -value passed to the `ResembleClient` in `web/serc/index.tsx`: +value passed to the `ResembleClient` in `web/src/index.tsx`: ```tsx const client = new ResembleClient( - "..resemble.cloud:9991") + ".prod1.resemble.cloud:9991") ; ``` -Then run `npm run build`. +Then, in the `web/` directory, run `npm run build`. Once built, this front end can be deployed to any static hosting provider like S3, Vercel, Cloudflare or Firebase hosting. diff --git a/backend/src/main.py b/backend/src/main.py index 00e5246..71ead02 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -11,7 +11,7 @@ async def initialize(workflow: Workflow): - hello = Hello(EXAMPLE_STATE_MACHINE_ID) + hello = Hello.lookup(EXAMPLE_STATE_MACHINE_ID) # Implicitly construct state machine upon first write. await hello.Send(workflow, message="Hello, World!") diff --git a/backend/src/requirements.txt b/backend/src/requirements.txt deleted file mode 100644 index 04955a0..0000000 --- a/backend/src/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -reboot-resemble>=0.5.3 -pytest>=7.4.2 -types-protobuf>=4.24.0.20240129 diff --git a/backend/tests/hello_servicer_test.py b/backend/tests/hello_servicer_test.py index 0a85d8f..e23a333 100644 --- a/backend/tests/hello_servicer_test.py +++ b/backend/tests/hello_servicer_test.py @@ -19,7 +19,7 @@ async def test_hello(self) -> None: workflow: Workflow = self.rsm.create_workflow(name=f"test-{self.id()}") - hello = Hello("testing-hello") + hello = Hello.lookup("testing-hello") await hello.Send(workflow, message="Hello, World") diff --git a/build.sh b/build.sh index 830093c..ed5d060 100755 --- a/build.sh +++ b/build.sh @@ -36,7 +36,7 @@ DOCKER_IMAGE_NAME=${POSITIONAL_ARGS[0]} # The following is a little helper for developers working on the Resemble # library. You likely won't need it if you're just using Resemble. -if [ -v REBOOT_RESEMBLE_WHL_FILE ]; then +if [ -n "$REBOOT_RESEMBLE_WHL_FILE" ]; then # Place the wheel package in a place where the Docker build process can # reach it. That means placing it in the build context, meaning the # directory containing the `Dockerfile`, or a subdirectory. We'll create diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..633f110 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[project] +requires-python = ">= 3.10" +dependencies = [ + "reboot-resemble>=0.5.4", +] + +[tool.rye] +dev-dependencies = [ + "reboot-resemble>=0.5.4", + "mypy>=1.2.0", + "pytest>=7.4.2", + "types-protobuf>=4.24.0.20240129", +] + +# This project only uses `rye` to provide `python` and its dependencies, so +# these settings remove the need to name or version the project. +virtual = true +managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 0000000..591df7a --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,164 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +aiofiles==23.2.1 + # via reboot-resemble +aiohttp==3.8.5 + # via kubernetes-asyncio + # via reboot-resemble +aiosignal==1.3.1 + # via aiohttp +async-timeout==4.0.3 + # via aiohttp +attrs==23.2.0 + # via aiohttp +cachetools==5.3.3 + # via google-auth +certifi==2024.2.2 + # via kubernetes + # via kubernetes-asyncio + # via requests +cffi==1.16.0 + # via cryptography +charset-normalizer==3.3.2 + # via aiohttp + # via requests +colorama==0.4.6 + # via reboot-resemble +cryptography==42.0.2 + # via reboot-resemble +exceptiongroup==1.2.0 + # via pytest +frozenlist==1.4.1 + # via aiohttp + # via aiosignal +gitdb==4.0.11 + # via gitpython +gitpython==3.1.31 + # via reboot-resemble +google-auth==2.29.0 + # via kubernetes +googleapis-common-protos==1.61.0 + # via grpcio-status + # via reboot-resemble +grpc-interceptor==0.15.4 + # via reboot-resemble +grpcio==1.56.2 + # via grpc-interceptor + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via reboot-resemble +grpcio-health-checking==1.56.2 + # via reboot-resemble +grpcio-reflection==1.56.2 + # via reboot-resemble +grpcio-status==1.56.2 + # via reboot-resemble +grpcio-tools==1.56.2 + # via reboot-resemble +idna==3.6 + # via requests + # via yarl +iniconfig==2.0.0 + # via pytest +jinja2==3.1.2 + # via jinja2-strcase + # via reboot-resemble +jinja2-strcase==0.0.2 + # via reboot-resemble +kubernetes==24.2.0 + # via reboot-resemble +kubernetes-asyncio==24.2.2 + # via reboot-resemble +markupsafe==2.1.5 + # via jinja2 +multidict==6.0.5 + # via aiohttp + # via yarl +mypy==1.9.0 +mypy-extensions==1.0.0 + # via mypy +mypy-protobuf==3.5.0 + # via reboot-resemble +oauthlib==3.2.2 + # via requests-oauthlib +packaging==23.1 + # via pytest + # via reboot-resemble +pluggy==1.4.0 + # via pytest +protobuf==4.25.2 + # via googleapis-common-protos + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via mypy-protobuf + # via reboot-resemble +psutil==5.9.5 + # via reboot-resemble +pyasn1==0.5.1 + # via pyasn1-modules + # via rsa +pyasn1-modules==0.3.0 + # via google-auth +pycparser==2.21 + # via cffi +pyjwt==2.8.0 + # via reboot-resemble +pyprctl==0.1.3 + # via reboot-resemble +pytest==8.1.1 +python-dateutil==2.9.0.post0 + # via kubernetes + # via kubernetes-asyncio +pyyaml==6.0.1 + # via kubernetes + # via kubernetes-asyncio +reboot-resemble==0.5.3 +requests==2.31.0 + # via kubernetes + # via requests-oauthlib +requests-oauthlib==2.0.0 + # via kubernetes +rsa==4.9 + # via google-auth +setuptools==69.2.0 + # via grpcio-tools + # via kubernetes + # via kubernetes-asyncio +six==1.16.0 + # via kubernetes + # via kubernetes-asyncio + # via python-dateutil +smmap==5.0.1 + # via gitdb +tomli==2.0.1 + # via mypy + # via pytest +types-protobuf==4.24.0.20240311 + # via mypy-protobuf +typing-extensions==4.9.0 + # via mypy + # via reboot-resemble +urllib3==1.26.15 + # via kubernetes + # via kubernetes-asyncio + # via reboot-resemble + # via requests +watchdog==3.0.0 + # via reboot-resemble +websocket-client==1.7.0 + # via kubernetes +websockets==12.0 + # via reboot-resemble +yarl==1.9.4 + # via aiohttp diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 0000000..a1498d0 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,149 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +aiofiles==23.2.1 + # via reboot-resemble +aiohttp==3.8.5 + # via kubernetes-asyncio + # via reboot-resemble +aiosignal==1.3.1 + # via aiohttp +async-timeout==4.0.3 + # via aiohttp +attrs==23.2.0 + # via aiohttp +cachetools==5.3.3 + # via google-auth +certifi==2024.2.2 + # via kubernetes + # via kubernetes-asyncio + # via requests +cffi==1.16.0 + # via cryptography +charset-normalizer==3.3.2 + # via aiohttp + # via requests +colorama==0.4.6 + # via reboot-resemble +cryptography==42.0.2 + # via reboot-resemble +frozenlist==1.4.1 + # via aiohttp + # via aiosignal +gitdb==4.0.11 + # via gitpython +gitpython==3.1.31 + # via reboot-resemble +google-auth==2.29.0 + # via kubernetes +googleapis-common-protos==1.61.0 + # via grpcio-status + # via reboot-resemble +grpc-interceptor==0.15.4 + # via reboot-resemble +grpcio==1.56.2 + # via grpc-interceptor + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via reboot-resemble +grpcio-health-checking==1.56.2 + # via reboot-resemble +grpcio-reflection==1.56.2 + # via reboot-resemble +grpcio-status==1.56.2 + # via reboot-resemble +grpcio-tools==1.56.2 + # via reboot-resemble +idna==3.6 + # via requests + # via yarl +jinja2==3.1.2 + # via jinja2-strcase + # via reboot-resemble +jinja2-strcase==0.0.2 + # via reboot-resemble +kubernetes==24.2.0 + # via reboot-resemble +kubernetes-asyncio==24.2.2 + # via reboot-resemble +markupsafe==2.1.5 + # via jinja2 +multidict==6.0.5 + # via aiohttp + # via yarl +mypy-protobuf==3.5.0 + # via reboot-resemble +oauthlib==3.2.2 + # via requests-oauthlib +packaging==23.1 + # via reboot-resemble +protobuf==4.25.2 + # via googleapis-common-protos + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via mypy-protobuf + # via reboot-resemble +psutil==5.9.5 + # via reboot-resemble +pyasn1==0.5.1 + # via pyasn1-modules + # via rsa +pyasn1-modules==0.3.0 + # via google-auth +pycparser==2.21 + # via cffi +pyjwt==2.8.0 + # via reboot-resemble +pyprctl==0.1.3 + # via reboot-resemble +python-dateutil==2.9.0.post0 + # via kubernetes + # via kubernetes-asyncio +pyyaml==6.0.1 + # via kubernetes + # via kubernetes-asyncio +reboot-resemble==0.5.3 +requests==2.31.0 + # via kubernetes + # via requests-oauthlib +requests-oauthlib==2.0.0 + # via kubernetes +rsa==4.9 + # via google-auth +setuptools==69.2.0 + # via grpcio-tools + # via kubernetes + # via kubernetes-asyncio +six==1.16.0 + # via kubernetes + # via kubernetes-asyncio + # via python-dateutil +smmap==5.0.1 + # via gitdb +types-protobuf==4.24.0.20240311 + # via mypy-protobuf +typing-extensions==4.9.0 + # via reboot-resemble +urllib3==1.26.15 + # via kubernetes + # via kubernetes-asyncio + # via reboot-resemble + # via requests +watchdog==3.0.0 + # via reboot-resemble +websocket-client==1.7.0 + # via kubernetes +websockets==12.0 + # via reboot-resemble +yarl==1.9.4 + # via aiohttp diff --git a/web/.env b/web/.env new file mode 100644 index 0000000..75144a8 --- /dev/null +++ b/web/.env @@ -0,0 +1 @@ +REACT_APP_REBOOT_RESEMBLE_ENDPOINT=https://localhost.direct:9991 diff --git a/web/package-lock.json b/web/package-lock.json index 13337cf..58aa6e8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,7 +8,7 @@ "name": "web", "version": "0.1.0", "dependencies": { - "@reboot-dev/resemble-react": "^0.4.0", + "@reboot-dev/resemble-react": "^0.5.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -3261,23 +3261,24 @@ } }, "node_modules/@reboot-dev/resemble-api": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@reboot-dev/resemble-api/-/resemble-api-0.0.1.tgz", - "integrity": "sha512-kpEsNYQ0tW20SJg+bP6w7DHG6azCYZbf+f3LOuVayE2hXjnMt+BGJCmcO3GPxRsqZ1UkrE1/hgtqxEa4e4uEwg==" + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/resemble-api/-/resemble-api-0.5.3.tgz", + "integrity": "sha512-yvLnSXBtKlMs2oZVtztU901o/VdAQ3sv57QJZWziTM+CXvzt0Gcbci8uMd5FbYUdjYzSruHt5jRkLzT9thWE0g==" }, "node_modules/@reboot-dev/resemble-react": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/resemble-react/-/resemble-react-0.2.0.tgz", - "integrity": "sha512-Y/vtcWmGND31n+49/5EAOC47vPs9R2MpjpFAWENAlaQz0A24Gl4H0D6NRF77nScdSjPniR8tbzJ2Z3CFmpKBjg==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/resemble-react/-/resemble-react-0.5.3.tgz", + "integrity": "sha512-51EB1neUdnzfn8TtrTX/hDrI0XBhkXuZMw8fvUnq3usVL/kC9L250VHgya/NixMS+ZINT18+72uhhX9z/iUKLg==", "dependencies": { "@bufbuild/protobuf": "^1.3.1", - "@reboot-dev/resemble-api": "0.0.1", + "@reboot-dev/resemble-api": "0.5.3", "@types/react": "^18.2.22", "@types/uuid": "^9.0.4", "react": "^18.2.0", "react-dom": "^18.2.0", "tslib": "^2.6.2", - "typescript": "4.8.4" + "typescript": "4.8.4", + "uuid": "^9.0.1" } }, "node_modules/@reboot-dev/resemble-react/node_modules/typescript": { @@ -3292,6 +3293,18 @@ "node": ">=4.2.0" } }, + "node_modules/@reboot-dev/resemble-react/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4121,9 +4134,12 @@ "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==" }, "node_modules/@types/node": { - "version": "16.18.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.54.tgz", - "integrity": "sha512-oTmGy68gxZZ21FhTJVVvZBYpQHEBZxHKTsGshobMqm9qWpbqdZsA5jvsuPZcHu0KwpmLrOHWPdEfg7XDpNT9UA==" + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -16517,6 +16533,11 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -19822,29 +19843,35 @@ } }, "@reboot-dev/resemble-api": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@reboot-dev/resemble-api/-/resemble-api-0.0.1.tgz", - "integrity": "sha512-kpEsNYQ0tW20SJg+bP6w7DHG6azCYZbf+f3LOuVayE2hXjnMt+BGJCmcO3GPxRsqZ1UkrE1/hgtqxEa4e4uEwg==" + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/resemble-api/-/resemble-api-0.5.3.tgz", + "integrity": "sha512-yvLnSXBtKlMs2oZVtztU901o/VdAQ3sv57QJZWziTM+CXvzt0Gcbci8uMd5FbYUdjYzSruHt5jRkLzT9thWE0g==" }, "@reboot-dev/resemble-react": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/resemble-react/-/resemble-react-0.2.0.tgz", - "integrity": "sha512-Y/vtcWmGND31n+49/5EAOC47vPs9R2MpjpFAWENAlaQz0A24Gl4H0D6NRF77nScdSjPniR8tbzJ2Z3CFmpKBjg==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/resemble-react/-/resemble-react-0.5.3.tgz", + "integrity": "sha512-51EB1neUdnzfn8TtrTX/hDrI0XBhkXuZMw8fvUnq3usVL/kC9L250VHgya/NixMS+ZINT18+72uhhX9z/iUKLg==", "requires": { "@bufbuild/protobuf": "^1.3.1", - "@reboot-dev/resemble-api": "0.0.1", + "@reboot-dev/resemble-api": "0.5.3", "@types/react": "^18.2.22", "@types/uuid": "^9.0.4", "react": "^18.2.0", "react-dom": "^18.2.0", "tslib": "^2.6.2", - "typescript": "4.8.4" + "typescript": "4.8.4", + "uuid": "^9.0.1" }, "dependencies": { "typescript": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==" + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" } } }, @@ -20468,9 +20495,12 @@ "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==" }, "@types/node": { - "version": "16.18.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.54.tgz", - "integrity": "sha512-oTmGy68gxZZ21FhTJVVvZBYpQHEBZxHKTsGshobMqm9qWpbqdZsA5jvsuPZcHu0KwpmLrOHWPdEfg7XDpNT9UA==" + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "requires": { + "undici-types": "~5.26.4" + } }, "@types/parse-json": { "version": "4.0.0", @@ -29324,6 +29354,11 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", diff --git a/web/package.json b/web/package.json index 9ff9078..74d0bec 100644 --- a/web/package.json +++ b/web/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reboot-dev/resemble-react": "^0.5.3", + "@reboot-dev/resemble-react": "^0.5.4", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/web/src/App.module.css b/web/src/App.module.css index 8a15ea7..2289ef5 100644 --- a/web/src/App.module.css +++ b/web/src/App.module.css @@ -39,6 +39,12 @@ color: #e91e63; } +.informationText { + text-align: center; + color: #a9a9a9; + font-family: Sans-Serif; +} + .buttonDisabled, .buttonEnabled { float: right; diff --git a/web/src/App.tsx b/web/src/App.tsx index 3f809e7..29138a8 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -31,7 +31,7 @@ const App = () => { const [message, setMessage] = useState("Hello, Resemble!"); const { useMessages, mutators } = useHello({ id: STATE_MACHINE_ID }); - const { response /* , isLoading */ } = useMessages(); + const { response, isLoading } = useMessages(); const handleClick = () => { mutators.send({ message: message }); @@ -59,10 +59,13 @@ const App = () => { > Send - {response !== undefined && + {(response !== undefined && response.messages.length > 0 && response.messages.map((message: string) => ( + ))) || + (response !== undefined && response.messages.length == 0 && ( +

No messages yet!

))} {/* Optimistically render each send. Each pending mutation on @@ -73,6 +76,13 @@ const App = () => { {mutators.send.pending.map(({ request: { message }, isLoading }) => ( ))} + {/* + If we're loading our first response, show the user a loading message, + so that they don't just see an emtpy screen. + */} + {isLoading && response === undefined && ( +

Loading...

+ )} ); }; diff --git a/web/src/index.tsx b/web/src/index.tsx index 73c16f7..ae550c9 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -12,7 +12,9 @@ const root = ReactDOM.createRoot( ); // Use TLS (via localhost.direct) so we get the advantage of HTTP/2 // multiplexing. -const client = new ResembleClient("https://localhost.direct:9991"); +const client = new ResembleClient( + process.env.REACT_APP_REBOOT_RESEMBLE_ENDPOINT as string +); root.render(