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

import pytest


def test_init(selenium_esm):
    assert "Python initialization complete" in selenium_esm.logs.splitlines()


def test_print(selenium_esm):
    selenium_esm.run("print('This should be logged')")
    assert "This should be logged" in selenium_esm.logs.splitlines()


@pytest.mark.xfail_browsers(node="No window in node")
def test_import_js(selenium_esm):
    result = selenium_esm.run(
        """
        import js
        js.window.title = 'Foo'
        js.window.title
        """
    )
    assert result == "Foo"
    result = selenium_esm.run(
        """
        dir(js)
        """
    )
    assert len(result) > 100
    assert "document" in result
    assert "window" in result

import pytest


def test_init(selenium_esm):
    assert "Python initialization complete" in selenium_esm.logs.splitlines()


def test_print(selenium_esm):
    selenium_esm.run("print('This should be logged')")
    assert "This should be logged" in selenium_esm.logs.splitlines()


@pytest.mark.xfail_browsers(node="No window in node")
def test_import_js(selenium_esm):
    result = selenium_esm.run(
        """
        import js
        js.window.title = 'Foo'
        js.window.title
        """
    )
    assert result == "Foo"
    result = selenium_esm.run(
        """
        dir(js)
        """
    )
    assert len(result) > 100
    assert "document" in result
    assert "window" in result
