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=["cffi"])
def test_cffi_asprintf(selenium):
    from cffi import FFI

    ffi = FFI()
    ffi.cdef(
        """int asprintf(char** buf, const char *format, ...);   // copy-pasted from the man page"""
    )
    C = ffi.dlopen(None)  # loads the entire C namespace
    buf = ffi.new("char**")
    arg1 = ffi.new("char[]", b"wo")
    arg2 = ffi.new("char[]", b"ld")
    C.asprintf(buf, b"hello %sr%s", arg1, arg2)
    assert ffi.string(buf[0]).decode() == "hello world"

from pyodide_test_runner import run_in_pyodide


@run_in_pyodide(packages=["cffi"])
def test_cffi_asprintf(selenium):
    from cffi import FFI

    ffi = FFI()
    ffi.cdef(
        """int asprintf(char** buf, const char *format, ...);   // copy-pasted from the man page"""
    )
    C = ffi.dlopen(None)  # loads the entire C namespace
    buf = ffi.new("char**")
    arg1 = ffi.new("char[]", b"wo")
    arg2 = ffi.new("char[]", b"ld")
    C.asprintf(buf, b"hello %sr%s", arg1, arg2)
    assert ffi.string(buf[0]).decode() == "hello world"
