py2docfx 0.1.20.dev2246430__py3-none-any.whl → 0.1.20.dev2249481__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,7 +54,7 @@ def update_package_info(executable: str, pkg: PackageInfo, source_folder: str):
54
54
  for meta_info in metadata:
55
55
  meta_info_array = meta_info.split(":")
56
56
  meta_field = meta_info_array[0].strip().lower()
57
- if meta_field in attrs:
57
+ if meta_field in attrs and not hasattr(pkg, meta_field):
58
58
  setattr(
59
59
  pkg,
60
60
  meta_field,
@@ -1,70 +1,25 @@
1
- import os
2
1
  import sys
3
- from pathlib import Path
4
2
 
5
3
  from py2docfx import PACKAGE_ROOT
6
4
  from py2docfx.docfx_yaml.logger import get_logger, run_async_subprocess, run_async_subprocess_without_executable
7
5
 
8
- # NEW: diagnostics import
9
- try:
10
- from diagnostics import pip_diag_bundle
11
- except Exception:
12
- pip_diag_bundle = None # tolerate missing diagnostics in non-CI runs
13
-
14
6
  PYPI = "pypi"
15
7
 
16
- # Keep your original common options; build commands as lists (no .split())
17
8
  pip_install_common_options = [
9
+ # "--no-cache-dir", # to reduce install duration after switching to each venv
10
+ "--quiet",
18
11
  "--no-compile",
12
+ "--no-warn-conflicts",
19
13
  "--disable-pip-version-check",
20
- "-vvv",
14
+ "--verbose"
21
15
  ]
22
16
 
23
- # --- NEW: helpers -------------------------------------------------------------
24
-
25
- def _diag_enabled() -> bool:
26
- # Enable by default in CI; allow opt-out with PY2DOCFX_PIP_DIAG=0
27
- v = os.environ.get("PY2DOCFX_PIP_DIAG", "1").strip()
28
- return v not in ("0", "false", "False", "")
29
-
30
- def _report_dir() -> str:
31
- return os.environ.get("PY2DOCFX_PIP_REPORT_DIR", "pip_reports")
32
-
33
- def build_index_options(
34
- index_url: str | None = None,
35
- extra_index_urls: list[str] | None = None,
36
- trusted_hosts: list[str] | None = None,
37
- constraints_file: str | None = None,
38
- ) -> list[str]:
39
- """
40
- Build pip CLI options for index and constraints.
41
- Use this from call sites to pass consistent resolution settings.
42
- """
43
- opts: list[str] = []
44
- if index_url:
45
- opts += ["--index-url", index_url]
46
- if extra_index_urls:
47
- for u in extra_index_urls:
48
- if u:
49
- opts += ["--extra-index-url", u]
50
- if trusted_hosts:
51
- for h in trusted_hosts:
52
- if h:
53
- opts += ["--trusted-host", h]
54
- if constraints_file:
55
- opts += ["-c", constraints_file]
56
- return opts
57
-
58
- # --- Existing functions with improvements ------------------------------------
59
-
60
17
  async def download(package_name, path, extra_index_url=None, prefer_source_distribution=True):
61
- """
62
- Downloads a package from PyPI (or other index) to the specified path using pip.
63
- """
18
+ # Downloads a package from PyPI to the specified path using pip.
64
19
  download_param = ["pip", "download", "--dest", path, "--no-deps", package_name]
65
20
  if extra_index_url:
66
- # Note: For multiple mirrors use build_index_options and pass through install() signatures instead.
67
- download_param.extend(["--extra-index-url", extra_index_url])
21
+ download_param.append("--extra-index-url")
22
+ download_param.append(extra_index_url)
68
23
  if prefer_source_distribution:
69
24
  download_param.append("--no-binary=:all:")
70
25
  else:
@@ -73,115 +28,23 @@ async def download(package_name, path, extra_index_url=None, prefer_source_distr
73
28
  py2docfx_logger = get_logger(__name__)
74
29
  await run_async_subprocess_without_executable(download_param, py2docfx_logger, cwd=PACKAGE_ROOT)
75
30
 
76
-
77
31
  async def install(package_name, options):
78
- """
79
- Installs a package using the *current* Python (no explicit exe).
80
- Retains the original signature; enable diagnostics via env var.
81
- """
32
+ # Installs a package from PyPI using pip.
33
+ install_param = "pip install {} {}".format(
34
+ " ".join(pip_install_common_options + options), package_name
35
+ ).split(" ")
82
36
  py2docfx_logger = get_logger(__name__)
83
-
84
- # --- NEW: preflight diagnostics
85
- if _diag_enabled() and pip_diag_bundle is not None:
86
- try:
87
- await pip_diag_bundle(
88
- sys.executable,
89
- package_name,
90
- options,
91
- report_dir=_report_dir(),
92
- )
93
- except Exception as _:
94
- # Non-fatal: we still attempt the real install
95
- pass
96
-
97
- # Build the actual pip command (as list)
98
- install_cmd = ["pip", "install"] + pip_install_common_options + options + [package_name]
99
- try:
100
- await run_async_subprocess_without_executable(install_cmd, py2docfx_logger, cwd=PACKAGE_ROOT)
101
- except Exception:
102
- # --- NEW: after-failure diagnostics
103
- if _diag_enabled() and pip_diag_bundle is not None:
104
- try:
105
- await pip_diag_bundle(
106
- sys.executable,
107
- package_name,
108
- options,
109
- report_dir=os.path.join(_report_dir(), "after_failure"),
110
- )
111
- except Exception:
112
- pass
113
- raise
114
-
37
+ await run_async_subprocess_without_executable(install_param, py2docfx_logger, cwd=PACKAGE_ROOT)
115
38
 
116
39
  async def install_in_exe(exe_path, package_name, options):
117
- """
118
- Installs a package using the *given* Python executable path.
119
- FIX: don't duplicate exe_path in argv; pass "-m pip ..." only.
120
- """
40
+ # Installs a package from PyPI using pip.
41
+ install_param = [exe_path] + "-m pip install {} {}".format(
42
+ " ".join(pip_install_common_options + options), package_name
43
+ ).split(" ")
121
44
  py2docfx_logger = get_logger(__name__)
122
-
123
- # --- NEW: preflight diagnostics
124
- if _diag_enabled() and pip_diag_bundle is not None:
125
- try:
126
- await pip_diag_bundle(
127
- exe_path,
128
- package_name,
129
- options,
130
- report_dir=_report_dir(),
131
- )
132
- except Exception:
133
- pass
134
-
135
- # Correct pip invocation: exe_path runs, args start with -m pip
136
- pip_cmd = ["-m", "pip", "install"] + pip_install_common_options + options + [package_name]
137
- try:
138
- await run_async_subprocess(exe_path, pip_cmd, py2docfx_logger, cwd=PACKAGE_ROOT)
139
- except Exception:
140
- # --- NEW: after-failure diagnostics
141
- if _diag_enabled() and pip_diag_bundle is not None:
142
- try:
143
- await pip_diag_bundle(
144
- exe_path,
145
- package_name,
146
- options,
147
- report_dir=os.path.join(_report_dir(), "after_failure"),
148
- )
149
- except Exception:
150
- pass
151
- raise
152
-
45
+ await run_async_subprocess(exe_path, install_param, py2docfx_logger, cwd=PACKAGE_ROOT)
153
46
 
154
47
  async def install_in_exe_async(exe_path, package_name, options):
155
- """
156
- Keeps the original async install; now includes diagnostics.
157
- """
48
+ pip_cmd = ["-m", "pip", "install"]+ pip_install_common_options + options + [package_name]
158
49
  py2docfx_logger = get_logger(__name__)
159
-
160
- # --- NEW: preflight diagnostics
161
- if _diag_enabled() and pip_diag_bundle is not None:
162
- try:
163
- await pip_diag_bundle(
164
- exe_path,
165
- package_name,
166
- options,
167
- report_dir=_report_dir(),
168
- )
169
- except Exception:
170
- pass
171
-
172
- pip_cmd = ["-m", "pip", "install"] + pip_install_common_options + options + [package_name]
173
- try:
174
- await run_async_subprocess(exe_path, pip_cmd, py2docfx_logger, cwd=PACKAGE_ROOT)
175
- except Exception:
176
- # --- NEW: after-failure diagnostics
177
- if _diag_enabled() and pip_diag_bundle is not None:
178
- try:
179
- await pip_diag_bundle(
180
- exe_path,
181
- package_name,
182
- options,
183
- report_dir=os.path.join(_report_dir(), "after_failure"),
184
- )
185
- except Exception:
186
- pass
187
- raise
50
+ await run_async_subprocess(exe_path, pip_cmd, py2docfx_logger)
@@ -50,8 +50,10 @@ def test_update_package_info(init_package_info):
50
50
  assert package.name == "dummy_package"
51
51
  assert package.version == "3.1.0"
52
52
 
53
- # case of metadata
53
+ # case of metadata, unly use metadata file as a fallback
54
54
  package = init_package_info
55
+ del package.name
56
+ del package.version
55
57
  get_source.update_package_info(sys.executable, package, os.path.join(base_path, "mock-2"))
56
58
  assert package.name == "mock_package"
57
59
  assert package.version == "2.2.0"
@@ -277,7 +277,7 @@ def build_finished(app, exception):
277
277
  obj['kind'] = 'import'
278
278
  package_obj = obj
279
279
 
280
- if (obj['type'] == 'class' and obj['inheritance']):
280
+ if (obj['type'] == 'class' and 'inheritance' in obj and len(obj['inheritance'])>0):
281
281
  convert_class_to_enum_if_needed(obj)
282
282
 
283
283
  is_root = insert_node_to_toc_tree_return_is_root_package(toc_yaml, uid, project_name, toc_node_map)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py2docfx
3
- Version: 0.1.20.dev2246430
3
+ Version: 0.1.20.dev2249481
4
4
  Summary: A package built based on Sphinx which download source code package and generate yaml files supported by docfx.
5
5
  Author: Microsoft Corporation
6
6
  License: MIT License
@@ -3,11 +3,10 @@ py2docfx/__main__.py,sha256=NDKRx5RhLJP6Z6zFEdGZJje69jxPaGeHLU_2e5GYUO4,5958
3
3
  py2docfx/convert_prepare/__init__.py,sha256=XxtxrP0kmW3ZBHIAoxsPDEHzcgeC0WSnole8Lk6CjKs,11
4
4
  py2docfx/convert_prepare/arg_parser.py,sha256=7goU287AjAyKGd9d5QKcphMCQOrlPnKpQ_YtdwLSNT8,7618
5
5
  py2docfx/convert_prepare/constants.py,sha256=RC5DqNkqWvx4hb91FrajZ1R9dBFLxcPyoEJ43jdm36E,102
6
- py2docfx/convert_prepare/diagnostics.py,sha256=UYrpzriS9qNRJianh5xxucsUOR2Jd_8_n4e6lOTuDrY,4334
7
6
  py2docfx/convert_prepare/environment.py,sha256=CF8g2hnpwFXsTfvp1O8lwYpR848iCK8bDM2UAWqibEQ,7286
8
7
  py2docfx/convert_prepare/generate_conf.py,sha256=wqs6iyElzJarH-20_qEL9zvZvt5xfBMsGXSXPSZy6wg,2295
9
8
  py2docfx/convert_prepare/generate_document.py,sha256=iTOCMBwkfvZEj5dlEGUq4qynUodaYxJuIrG9R5eGk38,2949
10
- py2docfx/convert_prepare/get_source.py,sha256=I_-QXXFFraMruDf13xlkwGAs5hDuv-wAjW3w-ylOw2Q,6930
9
+ py2docfx/convert_prepare/get_source.py,sha256=eSRnMppk1U9N6wmvmOmQb6tClpDLwXcyrhkwbid5lE0,6963
11
10
  py2docfx/convert_prepare/git.py,sha256=Cq76ooxejKlVJiJWWbBhbLZ_JvxqN2ka12L8jkl80b4,6492
12
11
  py2docfx/convert_prepare/install_package.py,sha256=aJxQBwLRPTIKpdtLVl-SaXPaF_OX_H1ZtWmcKD8Zzuk,274
13
12
  py2docfx/convert_prepare/pack.py,sha256=Jjj9ps8ESoKUFmSk6VgNmxOwMhuwirnQ-rhqigH-4VY,1578
@@ -15,7 +14,7 @@ py2docfx/convert_prepare/package_info.py,sha256=-zrMNeAkHxxzLRjBl1kRehUJy_ugc16y
15
14
  py2docfx/convert_prepare/package_info_extra_settings.py,sha256=u5B5e8hc0m9PA_-0kJzq1LtKn-xzZlucwXHTFy49mDg,1475
16
15
  py2docfx/convert_prepare/params.py,sha256=PXMB8pLtb4XbfI322avA47q0AO-TyBE6kZf7FU8I6v4,1771
17
16
  py2docfx/convert_prepare/paths.py,sha256=964RX81Qf__rzXgEATfqBNFCKTYVjLt9J7WCz2TnNdc,485
18
- py2docfx/convert_prepare/pip_utils.py,sha256=6zK3f7k5bAK_kV07I5IfDEMApPjy5_e8wPHLwWwow88,6470
17
+ py2docfx/convert_prepare/pip_utils.py,sha256=Y_xmIE0Ld-gXgR8rkXSfj-AMh1bcUJvscMgSdNZyPQU,2138
19
18
  py2docfx/convert_prepare/repo_info.py,sha256=6ASJlhBwf6vZTSENgrWCVlJjlJVhuBxzdQyWEdWAC4c,117
20
19
  py2docfx/convert_prepare/source.py,sha256=6-A7oof3-WAQcQZZVpT9pKiFLH4CCIZeYqq0MN0O3gw,1710
21
20
  py2docfx/convert_prepare/sphinx_caller.py,sha256=iyalaxdhcMpiUsPEziEgH7v7cd5Zpx8KeD11iCULGbc,4696
@@ -29,7 +28,7 @@ py2docfx/convert_prepare/subpackage_merge/merge_toc.py,sha256=nkVqe8R0m8D6cyTYV7
29
28
  py2docfx/convert_prepare/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
29
  py2docfx/convert_prepare/tests/test_environment.py,sha256=oJ_IuVQ-zwWNQZRXR74RDWVaWQ76aUl_HjtOx3QuU6E,3709
31
30
  py2docfx/convert_prepare/tests/test_generate_document.py,sha256=mY8DRT-WRGIBFdDRdFwa7ZSUQwR-fTQtyKwzrzFZLU8,2621
32
- py2docfx/convert_prepare/tests/test_get_source.py,sha256=IGBLAemm5SWUnNJJh6GJE5gBtAAdlC_cxS1xSeugzBI,7949
31
+ py2docfx/convert_prepare/tests/test_get_source.py,sha256=A5laL7j8gPyjSOKqssFoRv7YtkELGyDpn5q3vod7gAk,8034
33
32
  py2docfx/convert_prepare/tests/test_pack.py,sha256=Gw-LkIcbQuQHpoSbvkybYt4_fVw-LL12dceWw5AUKdE,4242
34
33
  py2docfx/convert_prepare/tests/test_package_info.py,sha256=3B-IzmUjETVO-5s3g3Lmh2E6JgopwnRauv8mB-SDZEM,3361
35
34
  py2docfx/convert_prepare/tests/test_params.py,sha256=itwmVdBMtU1qIXAGaIoaDfvTOYyAL2B_WLsaBV9KUZY,2232
@@ -58,7 +57,7 @@ py2docfx/convert_prepare/tests/data/subpackage/azure-mgmt-containerservice/azure
58
57
  py2docfx/convert_prepare/tests/data/subpackage/azure-mgmt-containerservice/azure/mgmt/containerservice/v2018_03_31/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
58
  py2docfx/convert_prepare/tests/data/subpackage/azure-mgmt-containerservice/azure/mgmt/containerservice/v2018_03_31/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
59
  py2docfx/docfx_yaml/__init__.py,sha256=KCEizAXv-SXtrYhvFfLHdBWDhz51AA9uagaeTL-Itpo,100
61
- py2docfx/docfx_yaml/build_finished.py,sha256=H4ZtUHMgBEHIFh2QqI1kae8ixlintAryadnz4nQhurs,14078
60
+ py2docfx/docfx_yaml/build_finished.py,sha256=he-n0TdnsRBZUayVHIuNc58e_inNo04oNyGfVsmtv4Y,14110
62
61
  py2docfx/docfx_yaml/build_init.py,sha256=lAw-fnBVQbySfZ7Sut_NpFQUjnqLOmnGQrTBBH2RXcg,1860
63
62
  py2docfx/docfx_yaml/common.py,sha256=UN1MUmjUoN1QSFDR1Cm_bfRuHr6FQiOe5VQV6s8xzjc,6841
64
63
  py2docfx/docfx_yaml/convert_class.py,sha256=YaPjxqNy0p6AXi3H3T0l1MZeYRZ3dWkqusO4E0KT9QU,2161
@@ -3916,7 +3915,7 @@ py2docfx/venv/venv1/Lib/site-packages/wheel/_commands/convert.py,sha256=0wSJMU0m
3916
3915
  py2docfx/venv/venv1/Lib/site-packages/wheel/_commands/pack.py,sha256=o3iwjfRHl7N9ul-M2kHbewLJZnqBLAWf0tzUCwoiTMw,3078
3917
3916
  py2docfx/venv/venv1/Lib/site-packages/wheel/_commands/tags.py,sha256=Rv2ySVb8-qX3osKp3uJgxcIMXkjt43XUD0-zvC6KvnY,4775
3918
3917
  py2docfx/venv/venv1/Lib/site-packages/wheel/_commands/unpack.py,sha256=Y_J7ynxPSoFFTT7H0fMgbBlVErwyDGcObgme5MBuz58,1021
3919
- py2docfx-0.1.20.dev2246430.dist-info/METADATA,sha256=Kky-dbJ4q8dEb_tU_5BQC0iR-d8IpCYo7oxbAVbRZM4,548
3920
- py2docfx-0.1.20.dev2246430.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
3921
- py2docfx-0.1.20.dev2246430.dist-info/top_level.txt,sha256=5dH2uP81dczt_qQJ38wiZ-gzoVWasfiJALWRSjdbnYU,9
3922
- py2docfx-0.1.20.dev2246430.dist-info/RECORD,,
3918
+ py2docfx-0.1.20.dev2249481.dist-info/METADATA,sha256=CNf0_38yfV_R_6gO5K1y_HWcmtgl_8mbHuov1ys_HLA,548
3919
+ py2docfx-0.1.20.dev2249481.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
3920
+ py2docfx-0.1.20.dev2249481.dist-info/top_level.txt,sha256=5dH2uP81dczt_qQJ38wiZ-gzoVWasfiJALWRSjdbnYU,9
3921
+ py2docfx-0.1.20.dev2249481.dist-info/RECORD,,
@@ -1,95 +0,0 @@
1
- # diagnostics.py (or inline with your existing helper module)
2
-
3
- import os, re, json, textwrap
4
- from pathlib import Path
5
- from py2docfx import PACKAGE_ROOT
6
- from py2docfx.docfx_yaml.logger import get_logger, run_async_subprocess
7
-
8
- # Reuse your global install options if you like
9
- pip_install_common_options = ["--no-compile", "--disable-pip-version-check", "-vvv"]
10
-
11
- SECRET_PATTERN = re.compile(r"(//[^:/]+:)([^@]+)(@)") # //user:pass@ → //user:****@
12
-
13
- def _mask(s: str) -> str:
14
- if not s:
15
- return s
16
- return SECRET_PATTERN.sub(r"\1****\3", s)
17
-
18
- def _env_var(name: str) -> str:
19
- val = os.environ.get(name)
20
- return f"{name}={_mask(val)}" if val else f"{name}="
21
-
22
- async def pip_diag_bundle(
23
- exe_path: str,
24
- package_name: str,
25
- extra_install_options: list[str],
26
- report_dir: str = "pip_reports",
27
- include_metadata_peek: bool = True,
28
- ):
29
- """
30
- Collect a comprehensive diagnostic snapshot for pip in the current venv:
31
- - pip/python version
32
- - pip config (with origins)
33
- - pip debug (platform & tags)
34
- - env vars likely to affect pip
35
- - pip list (packages in the venv)
36
- - dry-run resolver report for the target package (JSON)
37
- - optional: METADATA/Requires-Dist peek for any downloaded wheel
38
- """
39
- logger = get_logger(__name__)
40
- out_dir = Path(PACKAGE_ROOT) / report_dir
41
- out_dir.mkdir(parents=True, exist_ok=True)
42
-
43
- # 0) Write a quick "header" file with env context (sanitized)
44
- ctx = {
45
- "cwd": str(PACKAGE_ROOT),
46
- "PIP_INDEX_URL": os.environ.get("PIP_INDEX_URL"),
47
- "PIP_EXTRA_INDEX_URL": os.environ.get("PIP_EXTRA_INDEX_URL"),
48
- "HTTPS_PROXY": os.environ.get("HTTPS_PROXY") or os.environ.get("https_proxy"),
49
- "HTTP_PROXY": os.environ.get("HTTP_PROXY") or os.environ.get("http_proxy"),
50
- }
51
- (out_dir / "00_env_context.txt").write_text(
52
- "\n".join(_env_var(k) for k in ctx.keys()), encoding="utf-8"
53
- )
54
-
55
- # 1) pip/python versions
56
- await run_async_subprocess(exe_path, ["-m", "pip", "--version"], logger, cwd=PACKAGE_ROOT)
57
- await run_async_subprocess(exe_path, ["-c", "import sys; import platform; print(sys.executable); print(sys.version); print(platform.platform())"], logger, cwd=PACKAGE_ROOT)
58
-
59
- # 2) Effective pip config
60
- await run_async_subprocess(exe_path, ["-m", "pip", "config", "list", "-v"], logger, cwd=PACKAGE_ROOT) # [2](https://pip.pypa.io/en/stable/topics/configuration.html)
61
-
62
- # 3) Platform & wheel tag info
63
- await run_async_subprocess(exe_path, ["-m", "pip", "debug"], logger, cwd=PACKAGE_ROOT) # [3](https://pip.pypa.io/en/stable/cli/pip_debug/)
64
-
65
- # 4) Snapshot venv packages (if any)
66
- await run_async_subprocess(exe_path, ["-m", "pip", "list", "--format=freeze"], logger, cwd=PACKAGE_ROOT)
67
-
68
- # 5) Resolver dry run with JSON report (no env changes)
69
- report_path = out_dir / f"{package_name.replace('==', '-').replace(' ', '_')}.dryrun.report.json"
70
- dry_run_cmd = (
71
- ["-m", "pip", "install", "--dry-run", "--ignore-installed", "--report", str(report_path)]
72
- + pip_install_common_options
73
- + extra_install_options
74
- + [package_name]
75
- )
76
- await run_async_subprocess(exe_path, dry_run_cmd, logger, cwd=PACKAGE_ROOT) # [1](https://pip.pypa.io/en/stable/cli/pip_install/)
77
-
78
- # 6) Optional: peek at any already-downloaded wheel METADATA for quick "Requires-Dist" visibility
79
- if include_metadata_peek:
80
- try:
81
- import zipfile, glob
82
- wheel_candidates = glob.glob("**/*.whl", recursive=True)
83
- wheel_candidates = [w for w in wheel_candidates if "microsoft_teams_ai-2.0.0a1-" in w or "microsoft-teams-ai-2.0.0a1-" in w]
84
- if wheel_candidates:
85
- wheel_candidates.sort()
86
- whl = wheel_candidates[-1]
87
- with zipfile.ZipFile(whl) as zf:
88
- meta_name = [n for n in zf.namelist() if n.endswith(".dist-info/METADATA")][0]
89
- meta_text = zf.read(meta_name).decode("utf-8", "replace")
90
- (out_dir / "wheel_METADATA.txt").write_text(meta_text, encoding="utf-8")
91
- except Exception as _:
92
- # Non-fatal
93
- pass
94
-
95
- logger.info(f"[pip-diag] Wrote diagnostic bundle to: {out_dir}")