Simplify the version bump process. ### Description

Simplify the version bump process.

- Use substitution hook in docs to update CDN URL.
  - In `dev` or `prerelease` versions, it will point to `dev` url
  - In `stable` versions, it will point it `vX.Y.Z` url
- Introduce a version bump script `tools/bump_version.py`.


~- Use `bump2version` to bump Python versions (continuing from #2460)~
~- Use `npm version` to bump JS versions~
~- Update `run_docker` script to use latest deployed pre-built docker image when `--pre-built` option is specified.~

### Checklists

- [x] Add new / update outdated documentation

from pyodide_test_runner import run_in_pyodide


@run_in_pyodide(packages=["lazy-object-proxy"])
def test_lazy_object_proxy(selenium):
    import lazy_object_proxy

    def expensive_func():
        from time import sleep

        print("starting calculation")
        # just as example for a slow computation
        sleep(0.1)
        print("finished calculation")
        # return the result of the calculation
        return 10

    obj = lazy_object_proxy.Proxy(expensive_func)
    # function is called only when object is actually used
    assert obj == 10  # now expensive_func is called

    assert obj == 10  # the result without calling the expensive_func

from pyodide_test_runner import run_in_pyodide


@run_in_pyodide(packages=["lazy-object-proxy"])
def test_lazy_object_proxy(selenium):
    import lazy_object_proxy

    def expensive_func():
        from time import sleep

        print("starting calculation")
        # just as example for a slow computation
        sleep(0.1)
        print("finished calculation")
        # return the result of the calculation
        return 10

    obj = lazy_object_proxy.Proxy(expensive_func)
    # function is called only when object is actually used
    assert obj == 10  # now expensive_func is called

    assert obj == 10  # the result without calling the expensive_func
