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

const chai = require("chai");
const fetch = require("node-fetch");

describe("Pyodide", () => {
  it("runPython", async () => {
    const factory = async () => {
      return pyodide.runPython("1+1");
    };
    const result = await chai.assert.isFulfilled(page.evaluate(factory));
    chai.assert.equal(result, 2);
  });
  describe("micropip", () => {
    const globalFetch = globalThis.fetch;

    before(async () => {
      const factory = async () => {
        globalThis.fetch = fetch;
        await pyodide.loadPackage(["micropip"]);
      };
      await chai.assert.isFulfilled(page.evaluate(factory));
    });
    after(async () => {
      const factory = async (globalFetch) => {
        globalThis.fetch = globalFetch || fetch;
      };
      await chai.assert.isFulfilled(page.evaluate(factory, globalFetch));
    });

    it("install", async () => {
      const factory = async () => {
        await pyodide.runPythonAsync(
          'import micropip; await micropip.install("snowballstemmer")'
        );
        return pyodide.runPython(`
          import snowballstemmer
          len(snowballstemmer.stemmer('english').stemWords(['A', 'node', 'test']))
        `);
      };
      const result = await chai.assert.isFulfilled(page.evaluate(factory));
      chai.assert.equal(result, 3);
    });
  });
});

const chai = require("chai");
const fetch = require("node-fetch");

describe("Pyodide", () => {
  it("runPython", async () => {
    const factory = async () => {
      return pyodide.runPython("1+1");
    };
    const result = await chai.assert.isFulfilled(page.evaluate(factory));
    chai.assert.equal(result, 2);
  });
  describe("micropip", () => {
    const globalFetch = globalThis.fetch;

    before(async () => {
      const factory = async () => {
        globalThis.fetch = fetch;
        await pyodide.loadPackage(["micropip"]);
      };
      await chai.assert.isFulfilled(page.evaluate(factory));
    });
    after(async () => {
      const factory = async (globalFetch) => {
        globalThis.fetch = globalFetch || fetch;
      };
      await chai.assert.isFulfilled(page.evaluate(factory, globalFetch));
    });

    it("install", async () => {
      const factory = async () => {
        await pyodide.runPythonAsync(
          'import micropip; await micropip.install("snowballstemmer")'
        );
        return pyodide.runPython(`
          import snowballstemmer
          len(snowballstemmer.stemmer('english').stemWords(['A', 'node', 'test']))
        `);
      };
      const result = await chai.assert.isFulfilled(page.evaluate(factory));
      chai.assert.equal(result, 3);
    });
  });
});
