This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch 
switch-building-airflow-packages-to-generic-image
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 19471658839f678f844c4acfc46a1dd1c0a4b45b
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Sun Nov 19 02:49:26 2023 +0100

    Switch building airlfow packages to gneneric images instead of CI image
    
    Since we are using setuptools (for now) to build airflow packages,
    we still want to run the build in docker image in order to not take
    the risk that code from forks is executed as part of the job that can
    have access to secrets and package writing.
    
    However we do not need the whole Airflow CI image for that. We can
    use two different images:
    
    * official Node image to compile assets on CI
    * Custom docker image based on Python, where we only install git,
      gitpython and wheels/pip in the right versions in order to retrieve
      commit hash
    
    Since compiling assets via mounted volume on MacOS is slow, we optimize
    the build and assets on MacOS are compiled locally using pre-commits -
    but in CI / on Linux we compile assets using the Node image.
    
    This PR switches the images used and uses Python script to build the
    packages rather than bash script.
    
    Building PROD image is also faster now in CI as it does not need to pull
    CI image to build packages - so we can remove the step of pulling the
    CI image needed for PROD build. We still need to wait for the
    constraints to be prepared so PROD build waits for CI image to complete,
    but it does not pull nor use the image.
---
 .github/workflows/build-images.yml                 |   7 --
 .github/workflows/ci.yml                           |  15 ---
 .github/workflows/release_dockerhub_image.yml      |   5 -
 .../airflow_breeze/commands/developer_commands.py  |  13 ++-
 .../commands/developer_commands_config.py          |   1 +
 .../commands/release_candidate_command.py          |  10 +-
 .../commands/release_management_commands.py        | 113 +++++++++++++++-----
 .../commands/release_management_commands_config.py |   3 +-
 .../airflow_breeze/utils/docker_command_utils.py   |   1 -
 dev/breeze/src/airflow_breeze/utils/path_utils.py  |   4 +
 dev/breeze/src/airflow_breeze/utils/run_utils.py   |  15 +++
 images/breeze/output_compile-www-assets.svg        |  26 +++--
 images/breeze/output_compile-www-assets.txt        |   2 +-
 ..._release-management_prepare-airflow-package.svg |  38 ++++---
 ..._release-management_prepare-airflow-package.txt |   2 +-
 scripts/ci/docker-compose/_docker.env              |   1 -
 scripts/ci/docker-compose/base.yml                 |   1 -
 scripts/ci/docker-compose/devcontainer.env         |   1 -
 scripts/in_container/_in_container_utils.sh        |  62 -----------
 .../in_container/run_prepare_airflow_packages.py   | 118 +++++++++++++++++++++
 .../in_container/run_prepare_airflow_packages.sh   |  94 ----------------
 21 files changed, 289 insertions(+), 243 deletions(-)

diff --git a/.github/workflows/build-images.yml 
b/.github/workflows/build-images.yml
index 5872978158..82498ebde0 100644
--- a/.github/workflows/build-images.yml
+++ b/.github/workflows/build-images.yml
@@ -287,13 +287,6 @@ jobs:
           mv -v "target-airflow/.github/actions" ".github"
       - name: "Install Breeze"
         uses: ./.github/actions/breeze
-      - name: >
-          Pull CI image for PROD build:
-          ${{ steps.selective-checks.outputs.default-python-version }}:${{ 
env.IMAGE_TAG }}"
-        shell: bash
-        run: breeze ci-image pull --tag-as-latest
-        env:
-          PYTHON_MAJOR_MINOR_VERSION: ${{ 
steps.selective-checks.outputs.default-python-version }}
       - name: >
           Build PROD Images
           
${{needs.build-info.outputs.all-python-versions-list-as-string}}:${{env.IMAGE_TAG}}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1cc648842f..da4dbfde53 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -418,14 +418,6 @@ jobs:
       - name: "Install Breeze"
         uses: ./.github/actions/breeze
         if: needs.build-info.outputs.in-workflow-build == 'true'
-      - name: >
-          Pull CI image for PROD build:
-          ${{ steps.selective-checks.outputs.default-python-version }}:${{ 
env.IMAGE_TAG }}"
-        shell: bash
-        run: breeze ci-image pull --tag-as-latest
-        env:
-          PYTHON_MAJOR_MINOR_VERSION: ${{ 
steps.selective-checks.outputs.default-python-version }}
-        if: needs.build-info.outputs.in-workflow-build == 'true'
       - name: >
           Build PROD Images
           
${{needs.build-info.outputs.all-python-versions-list-as-string}}:${{env.IMAGE_TAG}}
@@ -467,13 +459,6 @@ jobs:
           submodules: recursive
       - name: "Install Breeze"
         uses: ./.github/actions/breeze
-      - name: >
-          Pull CI image for PROD build:
-          ${{ steps.selective-checks.outputs.default-python-version }}:${{ 
env.IMAGE_TAG }}"
-        shell: bash
-        run: breeze ci-image pull --tag-as-latest
-        env:
-          PYTHON_MAJOR_MINOR_VERSION: ${{ 
steps.selective-checks.outputs.default-python-version }}
       - name: >
           Build Bullseye PROD Images
           
${{needs.build-info.outputs.all-python-versions-list-as-string}}:${{env.IMAGE_TAG}}
diff --git a/.github/workflows/release_dockerhub_image.yml 
b/.github/workflows/release_dockerhub_image.yml
index 9f898eb584..02d7d6fcc0 100644
--- a/.github/workflows/release_dockerhub_image.yml
+++ b/.github/workflows/release_dockerhub_image.yml
@@ -98,11 +98,6 @@ jobs:
         uses: ./.github/actions/breeze
       - name: Free space
         run: breeze ci free-space --answer yes
-      - name: Build CI image for PROD build ${{ 
needs.build-info.outputs.defaultPythonVersion }}
-        run: breeze ci-image build
-        env:
-          PYTHON_MAJOR_MINOR_VERSION: ${{ 
needs.build-info.outputs.defaultPythonVersion }}
-          COMMIT_SHA: ${{ github.sha }}
       - name: "Cleanup dist and context file"
         run: rm -fv ./dist/* ./docker-context-files/*
       - name: "Start ARM instance"
diff --git a/dev/breeze/src/airflow_breeze/commands/developer_commands.py 
b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
index 45032e9d0c..7f4b38d27b 100644
--- a/dev/breeze/src/airflow_breeze/commands/developer_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
@@ -346,7 +346,7 @@ def start_airflow(
         )
         skip_asset_compilation = True
     if use_airflow_version is None and not skip_asset_compilation:
-        run_compile_www_assets(dev=dev_mode, run_in_background=True)
+        run_compile_www_assets(dev=dev_mode, run_in_background=True, 
force_clean=False)
     airflow_constraints_reference = _determine_constraint_branch_used(
         airflow_constraints_reference, use_airflow_version
     )
@@ -669,12 +669,19 @@ def static_checks(
     "recompile assets on-the-fly when they are changed.",
     is_flag=True,
 )
+@click.option(
+    "--force-clean",
+    help="Force cleanup of compile assets before building them.",
+    is_flag=True,
+)
 @option_verbose
 @option_dry_run
-def compile_www_assets(dev: bool):
+def compile_www_assets(dev: bool, force_clean: bool):
     perform_environment_checks()
     assert_pre_commit_installed()
-    compile_www_assets_result = run_compile_www_assets(dev=dev, 
run_in_background=False)
+    compile_www_assets_result = run_compile_www_assets(
+        dev=dev, run_in_background=False, force_clean=force_clean
+    )
     if compile_www_assets_result.returncode != 0:
         get_console().print("[warn]New assets were generated[/]")
     sys.exit(0)
diff --git 
a/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py 
b/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py
index 3ffc016474..65af7d0162 100644
--- a/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/developer_commands_config.py
@@ -148,6 +148,7 @@ DEVELOPER_PARAMETERS: dict[str, list[dict[str, str | 
list[str]]]] = {
             "name": "Compile www assets flag",
             "options": [
                 "--dev",
+                "--force-clean",
             ],
         }
     ],
diff --git 
a/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py 
b/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py
index 344edc9ad7..5fba66b5fd 100644
--- a/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py
+++ b/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py
@@ -96,7 +96,15 @@ def create_artifacts_with_sdist():
 
 def create_artifacts_with_breeze():
     run_command(
-        ["breeze", "release-management", "prepare-airflow-package", 
"--package-format", "both"], check=True
+        [
+            "breeze",
+            "release-management",
+            "prepare-airflow-package",
+            "--use-container-for-assets-compilation",
+            "--package-format",
+            "both",
+        ],
+        check=True,
     )
     console_print("Artifacts created")
 
diff --git 
a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py 
b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
index fe3494541b..c7a29b5aa2 100644
--- a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
@@ -36,11 +36,11 @@ from rich.syntax import Syntax
 from airflow_breeze.commands.ci_image_commands import 
rebuild_or_pull_ci_image_if_needed
 from airflow_breeze.commands.release_management_group import release_management
 from airflow_breeze.global_constants import (
+    ALLOWED_DEBIAN_VERSIONS,
     ALLOWED_PLATFORMS,
     APACHE_AIRFLOW_GITHUB_REPOSITORY,
     CURRENT_PYTHON_MAJOR_MINOR_VERSIONS,
     DEFAULT_PYTHON_MAJOR_MINOR_VERSION,
-    MOUNT_ALL,
     MOUNT_SELECTED,
     MULTI_PLATFORM,
 )
@@ -118,6 +118,7 @@ from airflow_breeze.utils.parallel import (
 )
 from airflow_breeze.utils.path_utils import (
     AIRFLOW_SOURCES_ROOT,
+    AIRFLOW_WWW_DIR,
     CONSTRAINTS_CACHE_DIR,
     DIST_DIR,
     PROVIDER_METADATA_JSON_FILE_PATH,
@@ -132,7 +133,7 @@ from airflow_breeze.utils.publish_docs_builder import 
PublishDocsBuilder
 from airflow_breeze.utils.python_versions import get_python_version_list
 from airflow_breeze.utils.run_utils import (
     RunCommandResult,
-    assert_pre_commit_installed,
+    clean_www_assets,
     run_command,
     run_compile_www_assets,
 )
@@ -200,43 +201,108 @@ echo -e '\\e[34mRun this command to debug:
         )
 
 
+AIRFLOW_PIP_VERSION = "23.3.1"
+WHEEL_VERSION = "0.36.2"
+GITPYTHON_VERSION = "3.1.40"
+RICH_VERSION = "13.7.0"
+
+
+AIRFLOW_BUILD_DOCKERFILE = f"""
+FROM 
python:{DEFAULT_PYTHON_MAJOR_MINOR_VERSION}-slim-{ALLOWED_DEBIAN_VERSIONS[0]}
+RUN apt-get update && apt-get install -y --no-install-recommends git
+RUN pip install pip=={AIRFLOW_PIP_VERSION} wheel=={WHEEL_VERSION} \\
+   gitpython=={GITPYTHON_VERSION} rich=={RICH_VERSION}
+"""
+
+AIRFLOW_BUILD_IMAGE_TAG = "apache/airflow:local-build-image"
+NODE_BUILD_IMAGE_TAG = "node:21.2.0-bookworm-slim"
+
+
+def _compile_assets_in_docker():
+    clean_www_assets()
+    result = run_command(
+        [
+            "docker",
+            "run",
+            "-t",
+            "-v",
+            f"{AIRFLOW_WWW_DIR}:/opt/airflow/airflow/www/",
+            "-e",
+            "FORCE_COLOR=true",
+            NODE_BUILD_IMAGE_TAG,
+            "bash",
+            "-c",
+            "cd /opt/airflow/airflow/www && yarn install --frozen-lockfile && 
yarn run build",
+        ],
+        text=True,
+        capture_output=not get_verbose(),
+        check=False,
+    )
+    if result.returncode != 0:
+        get_console().print("[error]Error compiling assets[/]")
+        get_console().print(result.stdout)
+        get_console().print(result.stderr)
+        sys.exit(result.returncode)
+
+
 @release_management.command(
     name="prepare-airflow-package",
     help="Prepare sdist/whl package of Airflow.",
 )
 @option_package_format
+@click.option(
+    "--use-container-for-assets-compilation",
+    is_flag=True,
+    help="If set, the assets are compiled in docker container. On MacOS, asset 
compilation in containers "
+    "is slower, due to slow mounted filesystem and number of node_module files 
so by default asset "
+    "compilation is done locally. This option is useful for officially 
building packages by release "
+    "manager on MacOS to make sure it is a reproducible build.",
+)
 @option_version_suffix_for_pypi
-@option_debug_release_management
-@option_github_repository
 @option_verbose
 @option_dry_run
 def prepare_airflow_packages(
     package_format: str,
     version_suffix_for_pypi: str,
-    debug: bool,
-    github_repository: str,
+    use_container_for_assets_compilation: bool,
 ):
     perform_environment_checks()
     cleanup_python_generated_files()
-    assert_pre_commit_installed()
-    run_compile_www_assets(dev=False, run_in_background=False)
-    shell_params = ShellParams(
-        github_repository=github_repository,
-        python=DEFAULT_PYTHON_MAJOR_MINOR_VERSION,
-        package_format=package_format,
-        version_suffix_for_pypi=version_suffix_for_pypi,
-        skip_environment_initialization=True,
-        install_providers_from_sources=False,
-        mount_sources=MOUNT_ALL,
+    get_console().print("[info]Compiling assets\n")
+    from sys import platform
+
+    if platform == "darwin" and not use_container_for_assets_compilation:
+        run_compile_www_assets(dev=False, run_in_background=False, 
force_clean=True)
+    else:
+        _compile_assets_in_docker()
+    get_console().print("[success]Assets compiled successfully[/]")
+    run_command(
+        ["docker", "build", "--tag", AIRFLOW_BUILD_IMAGE_TAG, "-"],
+        input=AIRFLOW_BUILD_DOCKERFILE,
+        text=True,
+        check=True,
+        env={"DOCKER_CLI_HINTS": "false"},
     )
-    rebuild_or_pull_ci_image_if_needed(command_params=shell_params)
-    result_command = run_docker_command_with_debug(
-        params=shell_params,
-        
command=["/opt/airflow/scripts/in_container/run_prepare_airflow_packages.sh"],
-        debug=debug,
-        output_outside_the_group=True,
+    run_command(
+        cmd=[
+            "docker",
+            "run",
+            "-t",
+            "-v",
+            f"{AIRFLOW_SOURCES_ROOT}:/opt/airflow:cached",
+            "-e",
+            f"VERSION_SUFFIX_FOR_PYPI={version_suffix_for_pypi}",
+            "-e",
+            "GITHUB_ACTIONS",
+            "-e",
+            f"PACKAGE_FORMAT={package_format}",
+            AIRFLOW_BUILD_IMAGE_TAG,
+            "python",
+            
"/opt/airflow/scripts/in_container/run_prepare_airflow_packages.py",
+        ],
+        check=True,
     )
-    sys.exit(result_command.returncode)
+    get_console().print("[success]Successfully prepared Airflow package!\n\n")
 
 
 def provider_action_summary(description: str, message_type: MessageType, 
packages: list[str]):
@@ -1154,7 +1220,6 @@ def release_prod_images(
 ):
     perform_environment_checks()
     check_remote_ghcr_io_commands()
-    
rebuild_or_pull_ci_image_if_needed(command_params=ShellParams(python=DEFAULT_PYTHON_MAJOR_MINOR_VERSION))
     if not re.match(r"^\d*\.\d*\.\d*$", airflow_version):
         get_console().print(
             f"[warning]Skipping latest image tagging as this is a pre-release 
version: {airflow_version}"
diff --git 
a/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py 
b/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py
index a5b84869b4..e27ccc3dd6 100644
--- 
a/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py
+++ 
b/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py
@@ -54,9 +54,8 @@ RELEASE_MANAGEMENT_PARAMETERS: dict[str, list[dict[str, str | 
list[str]]]] = {
             "name": "Package flags",
             "options": [
                 "--package-format",
+                "--use-container-for-assets-compilation",
                 "--version-suffix-for-pypi",
-                "--debug",
-                "--github-repository",
             ],
         }
     ],
diff --git a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py 
b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
index b090479ea0..e936773162 100644
--- a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
@@ -611,7 +611,6 @@ def update_expected_environment_variables(env: dict[str, 
str]) -> None:
     set_value_to_default_if_not_set(env, "VERBOSE", "false")
     set_value_to_default_if_not_set(env, "VERBOSE_COMMANDS", "false")
     set_value_to_default_if_not_set(env, "VERSION_SUFFIX_FOR_PYPI", "")
-    set_value_to_default_if_not_set(env, "WHEEL_VERSION", "0.36.2")
 
 
 DERIVE_ENV_VARIABLES_FROM_ATTRIBUTES = {
diff --git a/dev/breeze/src/airflow_breeze/utils/path_utils.py 
b/dev/breeze/src/airflow_breeze/utils/path_utils.py
index cb48c5a104..3c8ca42fcc 100644
--- a/dev/breeze/src/airflow_breeze/utils/path_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/path_utils.py
@@ -263,6 +263,7 @@ def find_airflow_sources_root_to_operate_on() -> Path:
 
 
 AIRFLOW_SOURCES_ROOT = find_airflow_sources_root_to_operate_on().resolve()
+AIRFLOW_WWW_DIR = AIRFLOW_SOURCES_ROOT / "airflow" / "www"
 TESTS_PROVIDERS_ROOT = AIRFLOW_SOURCES_ROOT / "tests" / "providers"
 SYSTEM_TESTS_PROVIDERS_ROOT = AIRFLOW_SOURCES_ROOT / "tests" / "system" / 
"providers"
 AIRFLOW_PROVIDERS_ROOT = AIRFLOW_SOURCES_ROOT / "airflow" / "providers"
@@ -277,6 +278,9 @@ AIRFLOW_TMP_DIR_PATH = AIRFLOW_SOURCES_ROOT / "tmp"
 WWW_ASSET_COMPILE_LOCK = WWW_CACHE_DIR / ".asset_compile.lock"
 WWW_ASSET_OUT_FILE = WWW_CACHE_DIR / "asset_compile.out"
 WWW_ASSET_OUT_DEV_MODE_FILE = WWW_CACHE_DIR / "asset_compile_dev_mode.out"
+WWW_ASSET_HASH_FILE = AIRFLOW_SOURCES_ROOT / ".build" / "www" / "hash.txt"
+WWW_NODE_MODULES_DIR = AIRFLOW_SOURCES_ROOT / "airflow" / "www" / 
"node_modules"
+WWW_STATIC_DIST_DIR = AIRFLOW_SOURCES_ROOT / "airflow" / "www" / "static" / 
"dist"
 DAGS_DIR = AIRFLOW_SOURCES_ROOT / "dags"
 FILES_DIR = AIRFLOW_SOURCES_ROOT / "files"
 FILES_SBOM_DIR = FILES_DIR / "sbom"
diff --git a/dev/breeze/src/airflow_breeze/utils/run_utils.py 
b/dev/breeze/src/airflow_breeze/utils/run_utils.py
index 7b1bcd073e..f9415394b6 100644
--- a/dev/breeze/src/airflow_breeze/utils/run_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/run_utils.py
@@ -22,6 +22,7 @@ import contextlib
 import os
 import re
 import shlex
+import shutil
 import signal
 import stat
 import subprocess
@@ -40,8 +41,11 @@ from airflow_breeze.utils.console import Output, get_console
 from airflow_breeze.utils.path_utils import (
     AIRFLOW_SOURCES_ROOT,
     WWW_ASSET_COMPILE_LOCK,
+    WWW_ASSET_HASH_FILE,
     WWW_ASSET_OUT_DEV_MODE_FILE,
     WWW_ASSET_OUT_FILE,
+    WWW_NODE_MODULES_DIR,
+    WWW_STATIC_DIST_DIR,
 )
 from airflow_breeze.utils.shared_options import get_dry_run, get_verbose
 
@@ -448,10 +452,21 @@ def kill_process_group(gid: int):
         pass
 
 
+def clean_www_assets():
+    get_console().print("[info]Cleaning www assets[/]")
+    WWW_ASSET_HASH_FILE.unlink(missing_ok=True)
+    shutil.rmtree(WWW_NODE_MODULES_DIR, ignore_errors=True)
+    shutil.rmtree(WWW_STATIC_DIST_DIR, ignore_errors=True)
+    get_console().print("[info]Cleaned www assets[/]")
+
+
 def run_compile_www_assets(
     dev: bool,
     run_in_background: bool,
+    force_clean: bool,
 ):
+    if force_clean:
+        clean_www_assets()
     if dev:
         get_console().print("\n[warning] The command below will run forever 
until you press Ctrl-C[/]\n")
         get_console().print(
diff --git a/images/breeze/output_compile-www-assets.svg 
b/images/breeze/output_compile-www-assets.svg
index bf245ebe81..f800707827 100644
--- a/images/breeze/output_compile-www-assets.svg
+++ b/images/breeze/output_compile-www-assets.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 391.59999999999997" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 416.0" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -42,7 +42,7 @@
 
     <defs>
     <clipPath id="breeze-compile-www-assets-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="340.59999999999997" />
+      <rect x="0" y="0" width="1463.0" height="365.0" />
     </clipPath>
     <clipPath id="breeze-compile-www-assets-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -83,9 +83,12 @@
 <clipPath id="breeze-compile-www-assets-line-12">
     <rect x="0" y="294.3" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-compile-www-assets-line-13">
+    <rect x="0" y="318.7" width="1464" height="24.65"/>
+            </clipPath>
     </defs>
 
-    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="389.6" rx="8"/><text 
class="breeze-compile-www-assets-title" fill="#c5c8c6" text-anchor="middle" 
x="740" y="27">Command:&#160;compile-www-assets</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="414" rx="8"/><text 
class="breeze-compile-www-assets-title" fill="#c5c8c6" text-anchor="middle" 
x="740" y="27">Command:&#160;compile-www-assets</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -101,14 +104,15 @@
 </text><text class="breeze-compile-www-assets-r1" x="12.2" y="93.2" 
textLength="244" 
clip-path="url(#breeze-compile-www-assets-line-3)">Compiles&#160;www&#160;assets.</text><text
 class="breeze-compile-www-assets-r1" x="1464" y="93.2" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-3)">
 </text><text class="breeze-compile-www-assets-r1" x="1464" y="117.6" 
textLength="12.2" clip-path="url(#breeze-compile-www-assets-line-4)">
 </text><text class="breeze-compile-www-assets-r5" x="0" y="142" 
textLength="24.4" 
clip-path="url(#breeze-compile-www-assets-line-5)">╭─</text><text 
class="breeze-compile-www-assets-r5" x="24.4" y="142" textLength="305" 
clip-path="url(#breeze-compile-www-assets-line-5)">&#160;Compile&#160;www&#160;assets&#160;flag&#160;</text><text
 class="breeze-compile-www-assets-r5" x="329.4" y="142" textLength="1110.2" 
clip-path="url(#breeze-compile-www-assets-line-5)">─────────────────────────────────
 [...]
-</text><text class="breeze-compile-www-assets-r5" x="0" y="166.4" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-6)">│</text><text 
class="breeze-compile-www-assets-r4" x="24.4" y="166.4" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-6)">-</text><text 
class="breeze-compile-www-assets-r4" x="36.6" y="166.4" textLength="48.8" 
clip-path="url(#breeze-compile-www-assets-line-6)">-dev</text><text 
class="breeze-compile-www-assets-r1" x="134.2" y="166.4" textL [...]
-</text><text class="breeze-compile-www-assets-r5" x="0" y="190.8" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-7)">│</text><text 
class="breeze-compile-www-assets-r1" x="134.2" y="190.8" textLength="1305.4" 
clip-path="url(#breeze-compile-www-assets-line-7)">on-the-fly&#160;when&#160;they&#160;are&#160;changed.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;
 [...]
-</text><text class="breeze-compile-www-assets-r5" x="0" y="215.2" 
textLength="1464" 
clip-path="url(#breeze-compile-www-assets-line-8)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-compile-www-assets-r1" x="1464" y="215.2" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-8)">
-</text><text class="breeze-compile-www-assets-r5" x="0" y="239.6" 
textLength="24.4" 
clip-path="url(#breeze-compile-www-assets-line-9)">╭─</text><text 
class="breeze-compile-www-assets-r5" x="24.4" y="239.6" textLength="195.2" 
clip-path="url(#breeze-compile-www-assets-line-9)">&#160;Common&#160;options&#160;</text><text
 class="breeze-compile-www-assets-r5" x="219.6" y="239.6" textLength="1220" 
clip-path="url(#breeze-compile-www-assets-line-9)">──────────────────────────────────────────────
 [...]
-</text><text class="breeze-compile-www-assets-r5" x="0" y="264" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-10)">│</text><text 
class="breeze-compile-www-assets-r4" x="24.4" y="264" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-10)">-</text><text 
class="breeze-compile-www-assets-r4" x="36.6" y="264" textLength="97.6" 
clip-path="url(#breeze-compile-www-assets-line-10)">-verbose</text><text 
class="breeze-compile-www-assets-r6" x="158.6" y="264" textLe [...]
-</text><text class="breeze-compile-www-assets-r5" x="0" y="288.4" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-11)">│</text><text 
class="breeze-compile-www-assets-r4" x="24.4" y="288.4" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-11)">-</text><text 
class="breeze-compile-www-assets-r4" x="36.6" y="288.4" textLength="48.8" 
clip-path="url(#breeze-compile-www-assets-line-11)">-dry</text><text 
class="breeze-compile-www-assets-r4" x="85.4" y="288.4" tex [...]
-</text><text class="breeze-compile-www-assets-r5" x="0" y="312.8" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-12)">│</text><text 
class="breeze-compile-www-assets-r4" x="24.4" y="312.8" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-12)">-</text><text 
class="breeze-compile-www-assets-r4" x="36.6" y="312.8" textLength="61" 
clip-path="url(#breeze-compile-www-assets-line-12)">-help</text><text 
class="breeze-compile-www-assets-r6" x="158.6" y="312.8" tex [...]
-</text><text class="breeze-compile-www-assets-r5" x="0" y="337.2" 
textLength="1464" 
clip-path="url(#breeze-compile-www-assets-line-13)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-compile-www-assets-r1" x="1464" y="337.2" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-13)">
+</text><text class="breeze-compile-www-assets-r5" x="0" y="166.4" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-6)">│</text><text 
class="breeze-compile-www-assets-r4" x="24.4" y="166.4" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-6)">-</text><text 
class="breeze-compile-www-assets-r4" x="36.6" y="166.4" textLength="48.8" 
clip-path="url(#breeze-compile-www-assets-line-6)">-dev</text><text 
class="breeze-compile-www-assets-r1" x="231.8" y="166.4" textL [...]
+</text><text class="breeze-compile-www-assets-r5" x="0" y="190.8" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-7)">│</text><text 
class="breeze-compile-www-assets-r1" x="231.8" y="190.8" textLength="1207.8" 
clip-path="url(#breeze-compile-www-assets-line-7)">on-the-fly&#160;when&#160;they&#160;are&#160;changed.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;
 [...]
+</text><text class="breeze-compile-www-assets-r5" x="0" y="215.2" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-8)">│</text><text 
class="breeze-compile-www-assets-r4" x="24.4" y="215.2" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-8)">-</text><text 
class="breeze-compile-www-assets-r4" x="36.6" y="215.2" textLength="73.2" 
clip-path="url(#breeze-compile-www-assets-line-8)">-force</text><text 
class="breeze-compile-www-assets-r4" x="109.8" y="215.2" tex [...]
+</text><text class="breeze-compile-www-assets-r5" x="0" y="239.6" 
textLength="1464" 
clip-path="url(#breeze-compile-www-assets-line-9)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-compile-www-assets-r1" x="1464" y="239.6" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-9)">
+</text><text class="breeze-compile-www-assets-r5" x="0" y="264" 
textLength="24.4" 
clip-path="url(#breeze-compile-www-assets-line-10)">╭─</text><text 
class="breeze-compile-www-assets-r5" x="24.4" y="264" textLength="195.2" 
clip-path="url(#breeze-compile-www-assets-line-10)">&#160;Common&#160;options&#160;</text><text
 class="breeze-compile-www-assets-r5" x="219.6" y="264" textLength="1220" 
clip-path="url(#breeze-compile-www-assets-line-10)">─────────────────────────────────────────────────
 [...]
+</text><text class="breeze-compile-www-assets-r5" x="0" y="288.4" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-11)">│</text><text 
class="breeze-compile-www-assets-r4" x="24.4" y="288.4" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-11)">-</text><text 
class="breeze-compile-www-assets-r4" x="36.6" y="288.4" textLength="97.6" 
clip-path="url(#breeze-compile-www-assets-line-11)">-verbose</text><text 
class="breeze-compile-www-assets-r6" x="158.6" y="288.4 [...]
+</text><text class="breeze-compile-www-assets-r5" x="0" y="312.8" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-12)">│</text><text 
class="breeze-compile-www-assets-r4" x="24.4" y="312.8" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-12)">-</text><text 
class="breeze-compile-www-assets-r4" x="36.6" y="312.8" textLength="48.8" 
clip-path="url(#breeze-compile-www-assets-line-12)">-dry</text><text 
class="breeze-compile-www-assets-r4" x="85.4" y="312.8" tex [...]
+</text><text class="breeze-compile-www-assets-r5" x="0" y="337.2" 
textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-13)">│</text><text 
class="breeze-compile-www-assets-r4" x="24.4" y="337.2" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-13)">-</text><text 
class="breeze-compile-www-assets-r4" x="36.6" y="337.2" textLength="61" 
clip-path="url(#breeze-compile-www-assets-line-13)">-help</text><text 
class="breeze-compile-www-assets-r6" x="158.6" y="337.2" tex [...]
+</text><text class="breeze-compile-www-assets-r5" x="0" y="361.6" 
textLength="1464" 
clip-path="url(#breeze-compile-www-assets-line-14)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-compile-www-assets-r1" x="1464" y="361.6" textLength="12.2" 
clip-path="url(#breeze-compile-www-assets-line-14)">
 </text>
     </g>
     </g>
diff --git a/images/breeze/output_compile-www-assets.txt 
b/images/breeze/output_compile-www-assets.txt
index 78b4aea264..09a068ea8d 100644
--- a/images/breeze/output_compile-www-assets.txt
+++ b/images/breeze/output_compile-www-assets.txt
@@ -1 +1 @@
-c8a8c4f002f7246d0541897fc7c70313
+2c65b08cbff1b5be5b5c55fcd18580a9
diff --git 
a/images/breeze/output_release-management_prepare-airflow-package.svg 
b/images/breeze/output_release-management_prepare-airflow-package.svg
index 6d2d35508c..4565c3e73d 100644
--- a/images/breeze/output_release-management_prepare-airflow-package.svg
+++ b/images/breeze/output_release-management_prepare-airflow-package.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 440.4" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 513.5999999999999" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -43,7 +43,7 @@
 
     <defs>
     <clipPath 
id="breeze-release-management-prepare-airflow-package-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="389.4" />
+      <rect x="0" y="0" width="1463.0" height="462.59999999999997" />
     </clipPath>
     <clipPath id="breeze-release-management-prepare-airflow-package-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -90,9 +90,18 @@
 <clipPath id="breeze-release-management-prepare-airflow-package-line-14">
     <rect x="0" y="343.1" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-release-management-prepare-airflow-package-line-15">
+    <rect x="0" y="367.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-release-management-prepare-airflow-package-line-16">
+    <rect x="0" y="391.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-release-management-prepare-airflow-package-line-17">
+    <rect x="0" y="416.3" width="1464" height="24.65"/>
+            </clipPath>
     </defs>
 
-    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="438.4" rx="8"/><text 
class="breeze-release-management-prepare-airflow-package-title" fill="#c5c8c6" 
text-anchor="middle" x="740" 
y="27">Command:&#160;release-management&#160;prepare-airflow-package</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="511.6" rx="8"/><text 
class="breeze-release-management-prepare-airflow-package-title" fill="#c5c8c6" 
text-anchor="middle" x="740" 
y="27">Command:&#160;release-management&#160;prepare-airflow-package</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -108,16 +117,19 @@
 </text><text class="breeze-release-management-prepare-airflow-package-r1" 
x="12.2" y="93.2" textLength="451.4" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-3)">Prepare&#160;sdist/whl&#160;package&#160;of&#160;Airflow.</text><text
 class="breeze-release-management-prepare-airflow-package-r1" x="1464" y="93.2" 
textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-3)">
 </text><text class="breeze-release-management-prepare-airflow-package-r1" 
x="1464" y="117.6" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-4)">
 </text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="142" textLength="24.4" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-5)">╭─</text><text
 class="breeze-release-management-prepare-airflow-package-r5" x="24.4" y="142" 
textLength="183" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-5)">&#160;Package&#160;flags&#160;</text><text
 class="breeze-release-management-prepare-airflow-package-r5" x="207.4" y="142" 
text [...]
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="166.4" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-6)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="166.4" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-6)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="166.4" textLength="97.6" clip-path= [...]
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="190.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-7)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="190.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-7)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="190.8" textLength="97.6" clip-path= [...]
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="215.2" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-8)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="215.2" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-8)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="215.2" textLength="73.2" clip-path= [...]
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="239.6" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-9)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="239.6" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-9)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="239.6" textLength="85.4" clip-path= [...]
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="264" textLength="1464" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-10)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-release-management-prepare-airflow-package-r1" x="1464" y="264" 
textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-10)">
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="288.4" textLength="24.4" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-11)">╭─</text><text
 class="breeze-release-management-prepare-airflow-package-r5" x="24.4" 
y="288.4" textLength="195.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-11)">&#160;Common&#160;options&#160;</text><text
 class="breeze-release-management-prepare-airflow-package-r5" x="219.6" y=" 
[...]
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="312.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-12)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="312.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-12)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="312.8" textLength="97.6" clip-pat [...]
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="337.2" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-13)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="337.2" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-13)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="337.2" textLength="48.8" clip-pat [...]
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="361.6" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-14)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="361.6" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-14)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="361.6" textLength="61" clip-path= [...]
-</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="386" textLength="1464" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-15)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-release-management-prepare-airflow-package-r1" x="1464" y="386" 
textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-15)">
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="166.4" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-6)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="166.4" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-6)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="166.4" textLength="97.6" clip-path= [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="190.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-7)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="190.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-7)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="190.8" textLength="48.8" clip-path= [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="215.2" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-8)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r1" x="536.8" 
y="215.2" textLength="902.8" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-8)">compilation&#160;in&#160;containers&#160;is&#160;slower,&#160;due&#160;to&#160;slow&#160;mounted&#160;filesystem&#160;an
 [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="239.6" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-9)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r1" x="536.8" 
y="239.6" textLength="902.8" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-9)">number&#160;of&#160;node_module&#160;files&#160;so&#160;by&#160;default&#160;asset&#160;compilation&#160;is&#160;done&#1
 [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="264" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-10)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r1" x="536.8" y="264" 
textLength="902.8" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-10)">locally.&#160;This&#160;option&#160;is&#160;useful&#160;for&#160;officially&#160;building&#160;packages&#160;by&#160;relea
 [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="288.4" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-11)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r1" x="536.8" 
y="288.4" textLength="902.8" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-11)">manager&#160;on&#160;MacOS&#160;to&#160;make&#160;sure&#160;it&#160;is&#160;a&#160;reproducible&#160;build.&#160;&#160
 [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="312.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-12)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="312.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-12)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="312.8" textLength="97.6" clip-pat [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="337.2" textLength="1464" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-13)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-release-management-prepare-airflow-package-r1" x="1464" 
y="337.2" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-13)">
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="361.6" textLength="24.4" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-14)">╭─</text><text
 class="breeze-release-management-prepare-airflow-package-r5" x="24.4" 
y="361.6" textLength="195.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-14)">&#160;Common&#160;options&#160;</text><text
 class="breeze-release-management-prepare-airflow-package-r5" x="219.6" y=" 
[...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="386" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-15)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" y="386" 
textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-15)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" y="386" 
textLength="97.6" clip-path="url [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="410.4" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-16)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="410.4" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-16)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="410.4" textLength="48.8" clip-pat [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="434.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-17)">│</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="24.4" 
y="434.8" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-17)">-</text><text
 class="breeze-release-management-prepare-airflow-package-r4" x="36.6" 
y="434.8" textLength="61" clip-path= [...]
+</text><text class="breeze-release-management-prepare-airflow-package-r5" 
x="0" y="459.2" textLength="1464" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-18)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-release-management-prepare-airflow-package-r1" x="1464" 
y="459.2" textLength="12.2" 
clip-path="url(#breeze-release-management-prepare-airflow-package-line-18)">
 </text>
     </g>
     </g>
diff --git 
a/images/breeze/output_release-management_prepare-airflow-package.txt 
b/images/breeze/output_release-management_prepare-airflow-package.txt
index d2e8102ace..644026b1cf 100644
--- a/images/breeze/output_release-management_prepare-airflow-package.txt
+++ b/images/breeze/output_release-management_prepare-airflow-package.txt
@@ -1 +1 @@
-85d01c57e5b5ee0fb9e5f9d9706ed3b5
+5077bcfc4272c969f8b80ea4438a438a
diff --git a/scripts/ci/docker-compose/_docker.env 
b/scripts/ci/docker-compose/_docker.env
index 4b7fc60a02..ae3c567bfd 100644
--- a/scripts/ci/docker-compose/_docker.env
+++ b/scripts/ci/docker-compose/_docker.env
@@ -83,4 +83,3 @@ DOWNGRADE_SQLALCHEMY
 VERBOSE
 VERBOSE_COMMANDS
 VERSION_SUFFIX_FOR_PYPI
-WHEEL_VERSION
diff --git a/scripts/ci/docker-compose/base.yml 
b/scripts/ci/docker-compose/base.yml
index 86c51a0b9a..76e41adfcf 100644
--- a/scripts/ci/docker-compose/base.yml
+++ b/scripts/ci/docker-compose/base.yml
@@ -93,7 +93,6 @@ services:
       - VERBOSE=${VERBOSE}
       - VERBOSE_COMMANDS=${VERBOSE_COMMANDS}
       - VERSION_SUFFIX_FOR_PYPI=${VERSION_SUFFIX_FOR_PYPI}
-      - WHEEL_VERSION=${WHEEL_VERSION}
     volumes:
       # Pass docker to inside of the container so that Kind and Moto tests can 
use it.
       # NOTE! Even if we are using "desktop-linux" context where 
"/var/run/docker.sock" is not used,
diff --git a/scripts/ci/docker-compose/devcontainer.env 
b/scripts/ci/docker-compose/devcontainer.env
index dd7da1ac86..aa5798beb0 100644
--- a/scripts/ci/docker-compose/devcontainer.env
+++ b/scripts/ci/docker-compose/devcontainer.env
@@ -74,5 +74,4 @@ UPGRADE_TO_NEWER_DEPENDENCIES="false"
 VERBOSE="false"
 VERBOSE_COMMANDS="false"
 VERSION_SUFFIX_FOR_PYPI=
-WHEEL_VERSION=0.36.2
 AIRFLOW_SOURCES=/workspaces/airflow
diff --git a/scripts/in_container/_in_container_utils.sh 
b/scripts/in_container/_in_container_utils.sh
index 6be73d2936..b3606973c4 100644
--- a/scripts/in_container/_in_container_utils.sh
+++ b/scripts/in_container/_in_container_utils.sh
@@ -105,29 +105,6 @@ function in_container_basic_sanity_check() {
     in_container_go_to_airflow_sources
 }
 
-export 
DISABLE_CHECKS_FOR_TESTS="missing-docstring,no-self-use,too-many-public-methods,protected-access,do-not-use-asserts"
-
-function start_output_heartbeat() {
-    MESSAGE=${1:-"Still working!"}
-    INTERVAL=${2:=10}
-    echo
-    echo "Starting output heartbeat"
-    echo
-
-    bash 2>/dev/null <<EOF &
-while true; do
-  echo "\$(date): ${MESSAGE} "
-  sleep ${INTERVAL}
-done
-EOF
-    export HEARTBEAT_PID=$!
-}
-
-function stop_output_heartbeat() {
-    kill "${HEARTBEAT_PID}" || true
-    wait "${HEARTBEAT_PID}" || true 2>/dev/null
-}
-
 function dump_airflow_logs() {
     local dump_file
     dump_file=/files/airflow_logs_$(date 
"+%Y-%m-%d")_${CI_BUILD_ID}_${CI_JOB_ID}.log.tar.gz
@@ -331,45 +308,6 @@ function 
install_all_providers_from_pypi_with_eager_upgrade() {
     set +x
 }
 
-function install_all_provider_packages_from_wheels() {
-    echo
-    echo "Installing all provider packages from wheels"
-    echo
-    uninstall_providers
-    echo 
"${COLOR_BLUE}===================================================================================${COLOR_RESET}"
-    ls -w 1 /dist
-    echo 
"${COLOR_BLUE}===================================================================================${COLOR_RESET}"
-    pip install /dist/apache_airflow_providers_*.whl
-}
-
-function install_all_provider_packages_from_sdist() {
-    echo
-    echo "Installing all provider packages from .tar.gz"
-    echo
-    uninstall_providers
-    echo 
"${COLOR_BLUE}===================================================================================${COLOR_RESET}"
-    ls -w 1 /dist
-    echo 
"${COLOR_BLUE}===================================================================================${COLOR_RESET}"
-    pip install /dist/apache-airflow-providers-*.tar.gz
-}
-
-function twine_check_provider_packages_from_wheels() {
-    echo
-    echo "Twine check of all provider packages from wheels"
-    echo
-    echo 
"${COLOR_BLUE}===================================================================================${COLOR_RESET}"
-    ls -w 1 /dist
-    echo 
"${COLOR_BLUE}===================================================================================${COLOR_RESET}"
-    twine check /dist/apache_airflow_providers_*.whl
-}
-
-function twine_check_provider_packages_from_sdist() {
-    echo
-    echo "Twine check all provider packages from sdist"
-    echo
-    twine check /dist/apache-airflow-providers-*.tar.gz
-}
-
 function install_supported_pip_version() {
     if [[ ${AIRFLOW_PIP_VERSION} =~ .*https.* ]]; then
         pip install --disable-pip-version-check "pip @ ${AIRFLOW_PIP_VERSION}"
diff --git a/scripts/in_container/run_prepare_airflow_packages.py 
b/scripts/in_container/run_prepare_airflow_packages.py
new file mode 100755
index 0000000000..c1d2fb8aa5
--- /dev/null
+++ b/scripts/in_container/run_prepare_airflow_packages.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import os
+import re
+import subprocess
+import sys
+from pathlib import Path
+from shutil import rmtree
+
+import rich
+
+
+def process_summary(success_message: str, error_message: str, 
completed_process: subprocess.CompletedProcess):
+    if completed_process.returncode != 0:
+        if os.environ.get("GITHUB_ACTIONS", "") != "":
+            print("::endgroup::")
+            print(f"::error::{error_message}")
+        rich.print(f"[red]{error_message}")
+        rich.print(completed_process.stdout)
+        rich.print(completed_process.stderr)
+        sys.exit(completed_process.returncode)
+    else:
+        rich.print(f"[green]{success_message}")
+
+
+AIRFLOW_SOURCES_ROOT = Path(__file__).parents[2].resolve()
+WWW_DIRECTORY = AIRFLOW_SOURCES_ROOT / "airflow" / "www"
+
+rich.print("[green]Installed packages\n")
+
+rich.print("[bright_blue]Cleaning build directories\n")
+
+for egg_info_file in AIRFLOW_SOURCES_ROOT.glob("*egg-info*"):
+    rmtree(egg_info_file, ignore_errors=True)
+
+rmtree(AIRFLOW_SOURCES_ROOT / "build", ignore_errors=True)
+
+rich.print("[bright_blue]Cleaned build directories\n")
+
+version_suffix = os.environ.get("VERSION_SUFFIX_FOR_PYPI", "")
+package_format = os.environ.get("PACKAGE_FORMAT", "wheel")
+
+rich.print(f"[bright_blue]Marking {AIRFLOW_SOURCES_ROOT} as safe directory for 
git commands.\n")
+
+subprocess.run(
+    ["git", "config", "--global", "--unset-all", "safe.directory"],
+    cwd=AIRFLOW_SOURCES_ROOT,
+    stdout=subprocess.DEVNULL,
+    stderr=subprocess.DEVNULL,
+    check=False,
+)
+
+subprocess.run(
+    ["git", "config", "--global", "--add", "safe.directory", 
AIRFLOW_SOURCES_ROOT],
+    cwd=AIRFLOW_SOURCES_ROOT,
+    stdout=subprocess.DEVNULL,
+    stderr=subprocess.DEVNULL,
+    check=True,
+)
+
+rich.print(f"[bright_blue]Marked {AIRFLOW_SOURCES_ROOT} as safe directory for 
git commands.\n")
+
+rich.print("[bright_blue]Checking airflow version\n")
+
+airflow_version = subprocess.check_output(
+    [sys.executable, "setup.py", "--version"], text=True, 
cwd=AIRFLOW_SOURCES_ROOT
+).strip()
+
+rich.print(f"[bright_blue]Airflow version: {airflow_version}\n")
+
+RELEASED_VERSION_MATCHER = re.compile(r"^\d+\.\d+\.\d+$")
+
+command = [sys.executable, "setup.py"]
+
+if version_suffix:
+    if RELEASED_VERSION_MATCHER.match(airflow_version):
+        rich.print(f"[bright_blue]Adding {version_suffix} suffix to the 
{airflow_version}")
+        command.extend(["egg_info", "--tag-build", version_suffix])
+    elif not airflow_version.endswith(version_suffix):
+        rich.print(f"[red]Version {airflow_version} does not end with 
{version_suffix}. Using !")
+        sys.exit(1)
+
+if package_format in ["both", "wheel"]:
+    command.append("bdist_wheel")
+if package_format in ["both", "sdist"]:
+    command.append("sdist")
+
+rich.print(f"[bright_blue]Building packages: {package_format}\n")
+
+process = subprocess.run(command, capture_output=True, text=True, 
cwd=AIRFLOW_SOURCES_ROOT)
+
+process_summary("Airflow packages built successfully", "Error building Airflow 
packages", process)
+
+if os.environ.get("GITHUB_ACTIONS", "") != "":
+    print("::endgroup::")
+
+rich.print("[bright_blue]Packages built successfully:\n")
+for file in (AIRFLOW_SOURCES_ROOT / "dist").glob("apache*"):
+    rich.print(file.name)
+rich.print()
diff --git a/scripts/in_container/run_prepare_airflow_packages.sh 
b/scripts/in_container/run_prepare_airflow_packages.sh
deleted file mode 100755
index afcceedbf0..0000000000
--- a/scripts/in_container/run_prepare_airflow_packages.sh
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/env bash
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# shellcheck source=scripts/in_container/_in_container_script_init.sh
-. "$( dirname "${BASH_SOURCE[0]}" )/_in_container_script_init.sh"
-
-function prepare_airflow_packages() {
-    echo 
"-----------------------------------------------------------------------------------"
-    if [[ "${VERSION_SUFFIX_FOR_PYPI}" == '' ]]; then
-        echo
-        echo "Preparing official version of provider with no suffixes"
-        echo
-    else
-        echo
-        echo " Package Version of providers suffix set for PyPI version: 
${VERSION_SUFFIX_FOR_PYPI}"
-        echo
-    fi
-    echo 
"-----------------------------------------------------------------------------------"
-
-    rm -rf -- *egg-info*
-    rm -rf -- build
-
-    install_supported_pip_version
-    pip install "wheel==${WHEEL_VERSION}"
-
-    local packages=()
-
-    if [[ ${PACKAGE_FORMAT} == "wheel" || ${PACKAGE_FORMAT} == "both" ]] ; then
-        packages+=("bdist_wheel")
-    fi
-    if [[ ${PACKAGE_FORMAT} == "sdist" || ${PACKAGE_FORMAT} == "both" ]] ; then
-        packages+=("sdist")
-    fi
-    local tag_build=()
-    if [[ -n ${VERSION_SUFFIX_FOR_PYPI} ]]; then
-        AIRFLOW_VERSION=$(python setup.py --version)
-        if [[  ${AIRFLOW_VERSION} =~ ^[0-9\.]+$  ]]; then
-            echo
-            echo "${COLOR_BLUE}Adding ${VERSION_SUFFIX_FOR_PYPI} suffix to 
${AIRFLOW_VERSION}${COLOR_RESET}"
-            echo
-            tag_build=('egg_info' '--tag-build' "${VERSION_SUFFIX_FOR_PYPI}")
-        else
-            if [[ ${AIRFLOW_VERSION} != *${VERSION_SUFFIX_FOR_PYPI} ]]; then
-                echo
-                echo "${COLOR_YELLOW}The requested PyPI suffix 
${VERSION_SUFFIX_FOR_PYPI} does not match the one in ${AIRFLOW_VERSION}. 
Overriding it with one from ${VERSION_SUFFIX_FOR_PYPI}.${COLOR_RESET}"
-                echo
-            fi
-        fi
-    fi
-
-    # Prepare airflow's wheel
-    PYTHONUNBUFFERED=1 python setup.py "${tag_build[@]}" "${packages[@]}"
-
-    # clean-up
-    rm -rf -- *egg-info*
-    rm -rf -- build
-
-    echo 
"${COLOR_BLUE}===================================================================================${COLOR_RESET}"
-    echo
-    echo "${COLOR_GREEN}Airflow package prepared in format: 
${PACKAGE_FORMAT}${COLOR_RESET}"
-    echo
-    echo 
"${COLOR_BLUE}===================================================================================${COLOR_RESET}"
-    ls -w 1 ./dist
-    echo 
"${COLOR_BLUE}===================================================================================${COLOR_RESET}"
-}
-
-function mark_directory_as_safe() {
-    git config --global --unset-all safe.directory || true
-    git config --global --add safe.directory /opt/airflow
-}
-
-install_supported_pip_version
-
-mark_directory_as_safe
-
-prepare_airflow_packages
-
-echo
-echo "${COLOR_GREEN}All good! Airflow packages are prepared in dist 
folder${COLOR_RESET}"
-echo

Reply via email to