ixt-cli 0.8.0__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.
- ixt/__init__.py +8 -0
- ixt/__main__.py +8 -0
- ixt/backends/__init__.py +1 -0
- ixt/backends/binary.py +935 -0
- ixt/backends/binary_resolver.py +307 -0
- ixt/backends/node.py +490 -0
- ixt/backends/python.py +234 -0
- ixt/cli/__init__.py +31 -0
- ixt/cli/argparse_completion.py +557 -0
- ixt/cli/cmd_apply.py +404 -0
- ixt/cli/cmd_cache.py +86 -0
- ixt/cli/cmd_config.py +295 -0
- ixt/cli/cmd_info.py +116 -0
- ixt/cli/cmd_install.py +508 -0
- ixt/cli/cmd_misc.py +261 -0
- ixt/cli/cmd_registry.py +35 -0
- ixt/cli/cmd_upgrade.py +336 -0
- ixt/cli/commands.py +70 -0
- ixt/cli/parser.py +555 -0
- ixt/cli/render.py +85 -0
- ixt/config/__init__.py +5 -0
- ixt/config/asset_index.py +305 -0
- ixt/config/asset_pattern_cache.py +87 -0
- ixt/config/env_policy.py +340 -0
- ixt/config/flags.py +29 -0
- ixt/config/fs_policy.py +17 -0
- ixt/config/heuristics.py +465 -0
- ixt/config/models.py +176 -0
- ixt/config/registry.py +145 -0
- ixt/config/settings.py +173 -0
- ixt/config/setup_toml.py +179 -0
- ixt/config/toml.py +416 -0
- ixt/core/__init__.py +16 -0
- ixt/core/apply.py +564 -0
- ixt/core/apply_actions.py +106 -0
- ixt/core/backend.py +187 -0
- ixt/core/bootstrap.py +410 -0
- ixt/core/cache.py +332 -0
- ixt/core/discover.py +150 -0
- ixt/core/doctor.py +591 -0
- ixt/core/export.py +419 -0
- ixt/core/expose.py +350 -0
- ixt/core/extract.py +261 -0
- ixt/core/hooks.py +182 -0
- ixt/core/identity.py +148 -0
- ixt/core/inject.py +143 -0
- ixt/core/install.py +509 -0
- ixt/core/install_local.py +229 -0
- ixt/core/locks.py +54 -0
- ixt/core/pathlink.py +86 -0
- ixt/core/resolution_stats.py +191 -0
- ixt/core/resolve.py +150 -0
- ixt/core/resolve_cache.py +185 -0
- ixt/core/runtimes.py +192 -0
- ixt/core/save.py +237 -0
- ixt/core/setup_completions.py +11 -0
- ixt/core/setup_path.py +368 -0
- ixt/core/upgrade.py +596 -0
- ixt/data/__init__.py +10 -0
- ixt/data/asset_index.json +574 -0
- ixt/data/heuristics.toml +98 -0
- ixt/data/registry.toml +71 -0
- ixt/libs/__init__.py +3 -0
- ixt/libs/constants.py +4 -0
- ixt/libs/fmt.py +108 -0
- ixt/libs/logger.py +109 -0
- ixt/libs/output.py +25 -0
- ixt/libs/req_spec.py +115 -0
- ixt/libs/semver.py +149 -0
- ixt/libs/shell.py +126 -0
- ixt/libs/style.py +238 -0
- ixt/net/__init__.py +1 -0
- ixt/net/github_api.py +158 -0
- ixt/net/gitlab_api.py +149 -0
- ixt/net/http.py +194 -0
- ixt/net/npm.py +24 -0
- ixt/net/pypi.py +26 -0
- ixt/net/source.py +163 -0
- ixt/platform/__init__.py +131 -0
- ixt/platform/win.py +68 -0
- ixt_cli-0.8.0.dist-info/METADATA +294 -0
- ixt_cli-0.8.0.dist-info/RECORD +84 -0
- ixt_cli-0.8.0.dist-info/WHEEL +4 -0
- ixt_cli-0.8.0.dist-info/entry_points.txt +2 -0
ixt/cli/cmd_config.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""``ixt tool config`` subcommand and its action handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
|
|
8
|
+
from ixt.cli.render import format_exposed_bins, id_suffix
|
|
9
|
+
from ixt.config.models import ToolRecord
|
|
10
|
+
from ixt.config.settings import get_settings
|
|
11
|
+
from ixt.core.install import display_tool_name
|
|
12
|
+
from ixt.libs.logger import get_logger
|
|
13
|
+
from ixt.libs.style import bold, cyan, dim, green, red, yellow
|
|
14
|
+
|
|
15
|
+
_ALL_TOOLS_SENTINEL = "__all__"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def cmd_config(args: argparse.Namespace) -> int:
|
|
19
|
+
"""Dispatch ``ixt tool config <target> <action>`` to the right handler."""
|
|
20
|
+
logger = get_logger("config")
|
|
21
|
+
if args.tool != _ALL_TOOLS_SENTINEL:
|
|
22
|
+
from ixt.config.settings import get_settings
|
|
23
|
+
from ixt.core.install import resolve_tool_name
|
|
24
|
+
|
|
25
|
+
settings = get_settings()
|
|
26
|
+
try:
|
|
27
|
+
args.tool = resolve_tool_name(args.tool, settings=settings)
|
|
28
|
+
except ValueError as e:
|
|
29
|
+
logger.error(str(e))
|
|
30
|
+
return 1
|
|
31
|
+
return _CONFIG_ACTIONS[args.config_action](args, logger)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _cmd_config_expose(args: argparse.Namespace, logger) -> int:
|
|
35
|
+
from ixt.core.expose import reexpose_tool
|
|
36
|
+
|
|
37
|
+
settings = get_settings()
|
|
38
|
+
rules = args.rules
|
|
39
|
+
|
|
40
|
+
if args.tool == _ALL_TOOLS_SENTINEL:
|
|
41
|
+
meta_files = settings.iter_installed_metadata()
|
|
42
|
+
if not meta_files:
|
|
43
|
+
logger.info("No tools installed")
|
|
44
|
+
return 0
|
|
45
|
+
for meta_file in meta_files:
|
|
46
|
+
record = ToolRecord.load_json(meta_file)
|
|
47
|
+
result = reexpose_tool(record, rules, settings)
|
|
48
|
+
logger.info(f" {bold(display_tool_name(record, settings))} {id_suffix(record.name)}")
|
|
49
|
+
logger.info(f" exposed: {format_exposed_bins(result.linked)}")
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
meta_file = settings.get_tool_metadata_file(args.tool)
|
|
53
|
+
if not meta_file.exists():
|
|
54
|
+
logger.error(f"Tool '{args.tool}' is not installed")
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
result = reexpose_tool(ToolRecord.load_json(meta_file), rules, settings)
|
|
58
|
+
|
|
59
|
+
logger.info(f" exposed: {format_exposed_bins(result.linked)}")
|
|
60
|
+
return 0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _cmd_config_pkg_action(args: argparse.Namespace, logger) -> int:
|
|
64
|
+
"""Shared inject/uninject handler — the action is on ``args.config_action``."""
|
|
65
|
+
if args.tool == _ALL_TOOLS_SENTINEL:
|
|
66
|
+
logger.error("'__all__' is only supported with 'expose'")
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
from ixt.core.inject import (
|
|
70
|
+
PackageAlreadyInjectedError,
|
|
71
|
+
PackageNotInjectedError,
|
|
72
|
+
inject_packages,
|
|
73
|
+
uninject_packages,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if args.config_action == "inject":
|
|
77
|
+
op, dup_exc, marker, verb, prep = (
|
|
78
|
+
inject_packages,
|
|
79
|
+
PackageAlreadyInjectedError,
|
|
80
|
+
green("+"),
|
|
81
|
+
"injected",
|
|
82
|
+
"into",
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
op, dup_exc, marker, verb, prep = (
|
|
86
|
+
uninject_packages,
|
|
87
|
+
PackageNotInjectedError,
|
|
88
|
+
red("-"),
|
|
89
|
+
"uninjected",
|
|
90
|
+
"from",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
op(args.tool, [args.package])
|
|
95
|
+
except FileNotFoundError:
|
|
96
|
+
logger.error(f"Tool '{args.tool}' is not installed")
|
|
97
|
+
return 1
|
|
98
|
+
except dup_exc as e:
|
|
99
|
+
logger.error(str(e))
|
|
100
|
+
return 1
|
|
101
|
+
except ValueError as e:
|
|
102
|
+
logger.error(str(e))
|
|
103
|
+
return 1
|
|
104
|
+
|
|
105
|
+
logger.info(f" {marker} {verb} {cyan(args.package)} {prep} {bold(args.tool)}")
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _cmd_config_env(args: argparse.Namespace, logger) -> int:
|
|
110
|
+
if args.tool == _ALL_TOOLS_SENTINEL:
|
|
111
|
+
logger.error("'__all__' is not supported with 'env'")
|
|
112
|
+
return 1
|
|
113
|
+
|
|
114
|
+
settings = get_settings()
|
|
115
|
+
meta_file = settings.get_tool_metadata_file(args.tool)
|
|
116
|
+
if not meta_file.exists():
|
|
117
|
+
logger.error(f"Tool '{args.tool}' is not installed")
|
|
118
|
+
return 1
|
|
119
|
+
|
|
120
|
+
record = ToolRecord.load_json(meta_file)
|
|
121
|
+
|
|
122
|
+
action = getattr(args, "env_action", None)
|
|
123
|
+
|
|
124
|
+
if action is None or action == "list":
|
|
125
|
+
_print_env_policy(record, logger)
|
|
126
|
+
return 0
|
|
127
|
+
|
|
128
|
+
from ixt.config.env_policy import apply_policy
|
|
129
|
+
|
|
130
|
+
if action == "reset":
|
|
131
|
+
record.env_base = "all"
|
|
132
|
+
record.env_allow = []
|
|
133
|
+
record.env_deny = {}
|
|
134
|
+
elif action == "base":
|
|
135
|
+
record.env_base = args.value
|
|
136
|
+
elif action == "allow":
|
|
137
|
+
for pat in args.patterns:
|
|
138
|
+
if pat not in record.env_allow:
|
|
139
|
+
record.env_allow.append(pat)
|
|
140
|
+
elif action == "deny":
|
|
141
|
+
exceptions = getattr(args, "exceptions", [])
|
|
142
|
+
for pat in args.patterns:
|
|
143
|
+
existing = record.env_deny.get(pat, {})
|
|
144
|
+
existing_except = existing.get("except", [])
|
|
145
|
+
record.env_deny[pat] = {"except": sorted(set(existing_except) | set(exceptions))}
|
|
146
|
+
|
|
147
|
+
record.save_json(meta_file)
|
|
148
|
+
|
|
149
|
+
if record.exposed_bins:
|
|
150
|
+
apply_policy(record, settings.bin_dir)
|
|
151
|
+
|
|
152
|
+
_warn_env_policy_without_bwrap(record, logger)
|
|
153
|
+
_print_env_policy(record, logger)
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _warn_env_policy_without_bwrap(record: ToolRecord, logger) -> None:
|
|
158
|
+
"""Block warning at config-time when env policy is set but bwrap is missing.
|
|
159
|
+
|
|
160
|
+
Unconditional (no marker file): the user is configuring the policy
|
|
161
|
+
*now*, they need to know its effective scope before they trust it.
|
|
162
|
+
The runtime warning (separate path, marker-throttled) is for
|
|
163
|
+
repeated invocations afterwards.
|
|
164
|
+
"""
|
|
165
|
+
from ixt.config.env_policy import DEGRADED_WARNING_LINES, has_env_policy
|
|
166
|
+
from ixt.libs.shell import command_exists
|
|
167
|
+
|
|
168
|
+
if not has_env_policy(record):
|
|
169
|
+
return
|
|
170
|
+
if command_exists("bwrap"):
|
|
171
|
+
return
|
|
172
|
+
for line in DEGRADED_WARNING_LINES:
|
|
173
|
+
logger.warn(line)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _print_env_policy(record: ToolRecord, logger) -> None:
|
|
177
|
+
from ixt.config.env_policy import ENV_MODELS, has_env_policy
|
|
178
|
+
from ixt.config.fs_policy import has_fs_policy
|
|
179
|
+
|
|
180
|
+
logger.info(f"env policy for {bold(record.name)}:")
|
|
181
|
+
base_label = record.env_base
|
|
182
|
+
if record.env_base in ENV_MODELS:
|
|
183
|
+
patterns = " ".join(ENV_MODELS[record.env_base])
|
|
184
|
+
base_label = f"{record.env_base} ({dim(patterns)})"
|
|
185
|
+
logger.info(f" base : {base_label}")
|
|
186
|
+
if record.env_allow:
|
|
187
|
+
logger.info(f" allow: {' '.join(cyan(p) for p in record.env_allow)}")
|
|
188
|
+
else:
|
|
189
|
+
logger.info(f" allow: {dim('(none)')}")
|
|
190
|
+
if record.env_deny:
|
|
191
|
+
for pat, rule in record.env_deny.items():
|
|
192
|
+
excs = rule.get("except", [])
|
|
193
|
+
except_str = f" (except: {', '.join(cyan(e) for e in excs)})" if excs else ""
|
|
194
|
+
logger.info(f" deny : {red(pat)}{except_str}")
|
|
195
|
+
else:
|
|
196
|
+
logger.info(f" deny : {dim('(none)')}")
|
|
197
|
+
if not has_env_policy(record) and not has_fs_policy(record):
|
|
198
|
+
logger.info(f" {dim('no policy — tool runs with full environment (symlink)')}")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _auto_promote_fs_base(record: ToolRecord, logger) -> None:
|
|
202
|
+
"""If user adds fs ro/rw/scratch while fs_base is "all", promote to "app-minimal".
|
|
203
|
+
|
|
204
|
+
Without this, ``fs ro X`` alone enters bwrap with an empty sandbox
|
|
205
|
+
(no preset mounted) → ``bwrap: execvp ...: No such file or directory``.
|
|
206
|
+
Promotion gives a sane default; user can widen later with
|
|
207
|
+
``fs base app-common`` or narrow further with ``fs base none``.
|
|
208
|
+
"""
|
|
209
|
+
if record.fs_base == "all":
|
|
210
|
+
record.fs_base = "app-minimal"
|
|
211
|
+
logger.warn('note: fs_base auto-set to "app-minimal" — tool binary + libs only,')
|
|
212
|
+
logger.warn(" your project files are NOT visible. Add 'fs rw .' or")
|
|
213
|
+
logger.warn(" 'fs base app-common' to widen access.")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _cmd_config_fs(args: argparse.Namespace, logger) -> int:
|
|
217
|
+
if args.tool == _ALL_TOOLS_SENTINEL:
|
|
218
|
+
logger.error("'__all__' is not supported with 'fs'")
|
|
219
|
+
return 1
|
|
220
|
+
|
|
221
|
+
settings = get_settings()
|
|
222
|
+
meta_file = settings.get_tool_metadata_file(args.tool)
|
|
223
|
+
if not meta_file.exists():
|
|
224
|
+
logger.error(f"Tool '{args.tool}' is not installed")
|
|
225
|
+
return 1
|
|
226
|
+
|
|
227
|
+
record = ToolRecord.load_json(meta_file)
|
|
228
|
+
action = getattr(args, "fs_action", None)
|
|
229
|
+
|
|
230
|
+
if action is None or action == "list":
|
|
231
|
+
_print_fs_policy(record, logger)
|
|
232
|
+
return 0
|
|
233
|
+
|
|
234
|
+
from ixt.config.env_policy import apply_policy
|
|
235
|
+
|
|
236
|
+
if action in ("ro", "rw", "scratch"):
|
|
237
|
+
_auto_promote_fs_base(record, logger)
|
|
238
|
+
|
|
239
|
+
if action == "reset":
|
|
240
|
+
record.fs_base = "all"
|
|
241
|
+
record.fs_ro = []
|
|
242
|
+
record.fs_rw = []
|
|
243
|
+
record.fs_scratch = []
|
|
244
|
+
elif action == "base":
|
|
245
|
+
record.fs_base = args.value
|
|
246
|
+
elif action == "ro":
|
|
247
|
+
for p in args.paths:
|
|
248
|
+
if p not in record.fs_ro:
|
|
249
|
+
record.fs_ro.append(p)
|
|
250
|
+
elif action == "rw":
|
|
251
|
+
for p in args.paths:
|
|
252
|
+
if p not in record.fs_rw:
|
|
253
|
+
record.fs_rw.append(p)
|
|
254
|
+
elif action == "scratch":
|
|
255
|
+
for p in args.paths:
|
|
256
|
+
if p not in record.fs_scratch:
|
|
257
|
+
record.fs_scratch.append(p)
|
|
258
|
+
|
|
259
|
+
record.save_json(meta_file)
|
|
260
|
+
|
|
261
|
+
if record.exposed_bins:
|
|
262
|
+
apply_policy(record, settings.bin_dir)
|
|
263
|
+
|
|
264
|
+
_print_fs_policy(record, logger)
|
|
265
|
+
return 0
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _print_fs_policy(record: ToolRecord, logger) -> None:
|
|
269
|
+
from ixt.config.fs_policy import has_fs_policy
|
|
270
|
+
|
|
271
|
+
logger.info(f"fs policy for {bold(record.name)}:")
|
|
272
|
+
logger.info(f" base : {record.fs_base}")
|
|
273
|
+
if record.fs_ro:
|
|
274
|
+
logger.info(f" ro : {' '.join(cyan(p) for p in record.fs_ro)}")
|
|
275
|
+
else:
|
|
276
|
+
logger.info(f" ro : {dim('(none)')}")
|
|
277
|
+
if record.fs_rw:
|
|
278
|
+
logger.info(f" rw : {' '.join(cyan(p) for p in record.fs_rw)}")
|
|
279
|
+
else:
|
|
280
|
+
logger.info(f" rw : {dim('(none)')}")
|
|
281
|
+
if record.fs_scratch:
|
|
282
|
+
logger.info(f" scratch: {' '.join(yellow(p) for p in record.fs_scratch)}")
|
|
283
|
+
else:
|
|
284
|
+
logger.info(f" scratch: {dim('(none)')}")
|
|
285
|
+
if not has_fs_policy(record):
|
|
286
|
+
logger.info(f" {dim('no fs policy — tool runs without filesystem isolation')}")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
_CONFIG_ACTIONS: dict[str, Callable] = {
|
|
290
|
+
"expose": _cmd_config_expose,
|
|
291
|
+
"inject": _cmd_config_pkg_action,
|
|
292
|
+
"uninject": _cmd_config_pkg_action,
|
|
293
|
+
"env": _cmd_config_env,
|
|
294
|
+
"fs": _cmd_config_fs,
|
|
295
|
+
}
|
ixt/cli/cmd_info.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""``ixt tool list`` and ``ixt tool info`` subcommands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ixt.cli.render import format_exposed_bin
|
|
9
|
+
from ixt.config.models import ToolRecord
|
|
10
|
+
from ixt.config.settings import get_settings
|
|
11
|
+
from ixt.core.install import display_tool_name, resolve_tool_arg
|
|
12
|
+
from ixt.libs.logger import get_logger
|
|
13
|
+
from ixt.libs.output import data
|
|
14
|
+
from ixt.libs.style import bold, cyan, dim
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_binary_metadata(env_dir: Path) -> dict | None:
|
|
18
|
+
"""Load binary backend metadata from env_dir, or None if absent."""
|
|
19
|
+
path = env_dir / "binary_metadata.json"
|
|
20
|
+
if not path.exists():
|
|
21
|
+
return None
|
|
22
|
+
import json
|
|
23
|
+
|
|
24
|
+
with path.open(encoding="utf-8") as f:
|
|
25
|
+
return json.load(f)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cmd_list(args: argparse.Namespace) -> int:
|
|
29
|
+
logger = get_logger("list")
|
|
30
|
+
settings = get_settings()
|
|
31
|
+
meta_files = settings.iter_installed_metadata()
|
|
32
|
+
|
|
33
|
+
if not meta_files:
|
|
34
|
+
logger.info("No tools installed")
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
verbose = bool(getattr(args, "verbose_list", False))
|
|
38
|
+
|
|
39
|
+
records = [ToolRecord.load_json(mf) for mf in meta_files]
|
|
40
|
+
display_counts: dict[str, int] = {}
|
|
41
|
+
for r in records:
|
|
42
|
+
display = display_tool_name(r, settings)
|
|
43
|
+
display_counts[display] = display_counts.get(display, 0) + 1
|
|
44
|
+
|
|
45
|
+
for record in records:
|
|
46
|
+
display = display_tool_name(record, settings)
|
|
47
|
+
dn = record.name if (verbose or display_counts.get(display, 0) > 1) else display
|
|
48
|
+
|
|
49
|
+
version = f" {dim(record.version)}" if record.version else ""
|
|
50
|
+
backend_tag = dim(f"[{record.backend}]")
|
|
51
|
+
if record.exposed_bins:
|
|
52
|
+
bins = ", ".join(
|
|
53
|
+
format_exposed_bin(n, s) for n, s in sorted(record.exposed_bins.items())
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
bins = dim("none")
|
|
57
|
+
line = f" {bold(dn)}{version} {backend_tag} {bins}"
|
|
58
|
+
if record.injected:
|
|
59
|
+
inj = ", ".join(record.injected)
|
|
60
|
+
line += f" {dim(f'+{inj}')}"
|
|
61
|
+
data(line)
|
|
62
|
+
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def cmd_info(args: argparse.Namespace) -> int:
|
|
67
|
+
logger = get_logger("info")
|
|
68
|
+
settings = get_settings()
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
tool_name = resolve_tool_arg(args.tool, settings=settings)
|
|
72
|
+
except ValueError as e:
|
|
73
|
+
logger.error(str(e))
|
|
74
|
+
return 1
|
|
75
|
+
|
|
76
|
+
meta_file = settings.get_tool_metadata_file(tool_name)
|
|
77
|
+
|
|
78
|
+
if not meta_file.exists():
|
|
79
|
+
logger.error(f"Tool '{args.tool}' is not installed")
|
|
80
|
+
logger.error(f" {dim('Hint: run ixt tool list to see installed tools')}")
|
|
81
|
+
return 1
|
|
82
|
+
|
|
83
|
+
record = ToolRecord.load_json(meta_file)
|
|
84
|
+
|
|
85
|
+
data(f" {bold(display_tool_name(record, settings))}")
|
|
86
|
+
data(f" Id: {record.name}")
|
|
87
|
+
data(f" Backend: {record.backend}")
|
|
88
|
+
if record.name != record.pkg():
|
|
89
|
+
data(f" Package: {record.pkg()}")
|
|
90
|
+
data(f" Spec: {record.spec}")
|
|
91
|
+
data(f" Version: {record.version or dim('unknown')}")
|
|
92
|
+
data(f" Env: {dim(record.env_dir)}")
|
|
93
|
+
data(f" Rules: {', '.join(record.expose_rules)}")
|
|
94
|
+
|
|
95
|
+
if record.backend == "binary":
|
|
96
|
+
from ixt.net.source import parse_spec as _parse_spec
|
|
97
|
+
|
|
98
|
+
repo_spec = _parse_spec(record.spec)
|
|
99
|
+
if repo_spec:
|
|
100
|
+
data(f" Source: {repo_spec.owner}/{repo_spec.repo}")
|
|
101
|
+
bmeta = _load_binary_metadata(Path(record.env_dir))
|
|
102
|
+
if bmeta:
|
|
103
|
+
data(f" Release: {bmeta.get('tag', dim('unknown'))}")
|
|
104
|
+
data(f" Asset: {bmeta.get('asset_name', dim('unknown'))}")
|
|
105
|
+
|
|
106
|
+
if record.injected:
|
|
107
|
+
data(f" Injected: {', '.join(cyan(p) for p in record.injected)}")
|
|
108
|
+
|
|
109
|
+
if record.exposed_bins:
|
|
110
|
+
data(" Exposed:")
|
|
111
|
+
for name, source in sorted(record.exposed_bins.items()):
|
|
112
|
+
data(f" {cyan(name)} {dim('->')} {dim(source)}")
|
|
113
|
+
else:
|
|
114
|
+
data(f" Exposed: {dim('none')}")
|
|
115
|
+
|
|
116
|
+
return 0
|