terp-cli 0.33.0__tar.gz → 0.34.0__tar.gz

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.
Files changed (39) hide show
  1. {terp_cli-0.33.0 → terp_cli-0.34.0}/PKG-INFO +8 -8
  2. {terp_cli-0.33.0 → terp_cli-0.34.0}/pyproject.toml +8 -8
  3. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/verify.py +57 -62
  4. {terp_cli-0.33.0 → terp_cli-0.34.0}/.gitignore +0 -0
  5. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/__init__.py +0 -0
  6. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/_appref.py +0 -0
  7. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/_engine.py +0 -0
  8. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/_output.py +0 -0
  9. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/_subjects.py +0 -0
  10. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/access.py +0 -0
  11. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/apidocs.py +0 -0
  12. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/authz_surface.py +0 -0
  13. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/capabilities.py +0 -0
  14. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/deploy_safety.py +0 -0
  15. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/dev.py +0 -0
  16. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/docker.py +0 -0
  17. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/docker_masks.py +0 -0
  18. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/envfile.py +0 -0
  19. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/envschema.py +0 -0
  20. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/envseams.py +0 -0
  21. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/fmt.py +0 -0
  22. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/grants.py +0 -0
  23. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/jobs.py +0 -0
  24. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/leases.py +0 -0
  25. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/module_roles.py +0 -0
  26. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/openapi.py +0 -0
  27. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/outbox.py +0 -0
  28. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/ports.py +0 -0
  29. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/profiles.py +0 -0
  30. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/py.typed +0 -0
  31. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/routes.py +0 -0
  32. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/scaffold.py +0 -0
  33. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/schema.py +0 -0
  34. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/seed.py +0 -0
  35. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/service_accounts.py +0 -0
  36. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/smoke.py +0 -0
  37. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/users.py +0 -0
  38. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/version.py +0 -0
  39. {terp_cli-0.33.0 → terp_cli-0.34.0}/src/terp/cli/workbench.py +0 -0
@@ -1,19 +1,19 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: terp-cli
3
- Version: 0.33.0
3
+ Version: 0.34.0
4
4
  Summary: Terp command-line tool — inspect, scaffolding, migrations, checks, api-docs.
5
5
  Project-URL: Repository, https://github.com/AITT-NL/terp-framework
6
6
  Project-URL: Changelog, https://github.com/AITT-NL/terp-framework/blob/main/CHANGELOG.md
7
7
  License-Expression: Apache-2.0
8
8
  Requires-Python: >=3.13
9
9
  Requires-Dist: pyyaml>=6.0
10
- Requires-Dist: terp-arch==0.33.0
11
- Requires-Dist: terp-core==0.33.0
12
- Requires-Dist: terp-migrations==0.33.0
10
+ Requires-Dist: terp-arch==0.34.0
11
+ Requires-Dist: terp-core==0.34.0
12
+ Requires-Dist: terp-migrations==0.34.0
13
13
  Provides-Extra: jobs
14
- Requires-Dist: terp-cap-outbox==0.33.0; extra == 'jobs'
15
- Requires-Dist: terp-cap-scheduler-apscheduler==0.33.0; extra == 'jobs'
14
+ Requires-Dist: terp-cap-outbox==0.34.0; extra == 'jobs'
15
+ Requires-Dist: terp-cap-scheduler-apscheduler==0.34.0; extra == 'jobs'
16
16
  Provides-Extra: scheduler
17
- Requires-Dist: terp-cap-scheduler-apscheduler==0.33.0; extra == 'scheduler'
17
+ Requires-Dist: terp-cap-scheduler-apscheduler==0.34.0; extra == 'scheduler'
18
18
  Provides-Extra: worker
19
- Requires-Dist: terp-cap-outbox==0.33.0; extra == 'worker'
19
+ Requires-Dist: terp-cap-outbox==0.34.0; extra == 'worker'
@@ -4,18 +4,18 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "terp-cli"
7
- version = "0.33.0"
7
+ version = "0.34.0"
8
8
  description = "Terp command-line tool — inspect, scaffolding, migrations, checks, api-docs."
9
9
  requires-python = ">=3.13"
10
10
  license = "Apache-2.0"
11
11
  dependencies = [
12
- "terp-core==0.33.0",
12
+ "terp-core==0.34.0",
13
13
  # `terp migrate` delegates to terp-migrations (lazily imported), keeping Alembic
14
14
  # off the path for `terp inspect` / `terp guide` while making migrations work.
15
- "terp-migrations==0.33.0",
15
+ "terp-migrations==0.34.0",
16
16
  # `terp guide rules` projects the live rule registry; terp-arch is imported lazily
17
17
  # (only when that topic is rendered), so it stays off the common `terp guide` path.
18
- "terp-arch==0.33.0",
18
+ "terp-arch==0.34.0",
19
19
  # `terp verify --only env-seams` and `terp smoke` read the compose profiles as data
20
20
  # (anchors and `<<:` merge keys resolved), so both answer without a Docker daemon or
21
21
  # even the `docker` binary. Declared explicitly rather than leant on: every app
@@ -29,11 +29,11 @@ dependencies = [
29
29
  # or a scheduler engine into apps that only use inspect/check/migrate. Deployments can select
30
30
  # one process role, while `jobs` is the convenient complete jobs-process bundle.
31
31
  [project.optional-dependencies]
32
- worker = ["terp-cap-outbox==0.33.0"]
33
- scheduler = ["terp-cap-scheduler-apscheduler==0.33.0"]
32
+ worker = ["terp-cap-outbox==0.34.0"]
33
+ scheduler = ["terp-cap-scheduler-apscheduler==0.34.0"]
34
34
  jobs = [
35
- "terp-cap-outbox==0.33.0",
36
- "terp-cap-scheduler-apscheduler==0.33.0",
35
+ "terp-cap-outbox==0.34.0",
36
+ "terp-cap-scheduler-apscheduler==0.34.0",
37
37
  ]
38
38
 
39
39
  [project.scripts]
@@ -1090,6 +1090,45 @@ def _node_modules_problem(root: pathlib.Path, workspace: str) -> str | None:
1090
1090
  )
1091
1091
 
1092
1092
 
1093
+ #: The status a shell gives a command it cannot find, and the one a check answers with
1094
+ #: when the tool it runs is not installed.
1095
+ _NOT_FOUND = 127
1096
+
1097
+
1098
+ def _run_argv(
1099
+ argv: list[str], root: pathlib.Path, *, env: dict[str, str] | None = None
1100
+ ) -> subprocess.CompletedProcess[str]:
1101
+ """Run *argv* in *root* as a fixed argv, with no shell; a missing tool is a result.
1102
+
1103
+ Every check that shells out comes through here, so a tool that is not installed
1104
+ answers the way a shell would, exit 127 naming the tool, instead of raising. Only the
1105
+ manifest runner used to catch it. The runners with a body of their own (route types,
1106
+ the API client, the API reference) let ``FileNotFoundError`` escape the profile loop:
1107
+ the run ended in a traceback about ``subprocess``, every check after that one went
1108
+ unrun and unreported, and the one fact worth reading, which tool is missing, was a
1109
+ frame deep in the stack.
1110
+
1111
+ The message is the result's stdout, so each caller's own formatting of a finished
1112
+ command carries it unchanged.
1113
+ """
1114
+ executable = shutil.which(argv[0]) or argv[0]
1115
+ try:
1116
+ return subprocess.run( # noqa: S603 - fixed argv, shell=False
1117
+ [executable, *argv[1:]],
1118
+ cwd=root,
1119
+ capture_output=True,
1120
+ text=True,
1121
+ encoding="utf-8",
1122
+ errors="replace",
1123
+ check=False,
1124
+ env=None if env is None else {**os.environ, **env},
1125
+ )
1126
+ except FileNotFoundError:
1127
+ return subprocess.CompletedProcess(
1128
+ argv, _NOT_FOUND, stdout=f"{argv[0]}: executable not found on PATH", stderr=""
1129
+ )
1130
+
1131
+
1093
1132
  def _run_subprocess(
1094
1133
  check: VerifyCheck, root: pathlib.Path, *, env: dict[str, str] | None = None
1095
1134
  ) -> tuple[int, str]:
@@ -1109,20 +1148,7 @@ def _run_subprocess(
1109
1148
  problem = _node_modules_problem(root, _npm_workspace(argv))
1110
1149
  if problem is not None:
1111
1150
  return 1, problem
1112
- executable = shutil.which(argv[0]) or argv[0]
1113
- try:
1114
- completed = subprocess.run( # noqa: S603 - fixed manifest argv, shell=False
1115
- [executable, *argv[1:]],
1116
- cwd=root,
1117
- capture_output=True,
1118
- text=True,
1119
- encoding="utf-8",
1120
- errors="replace",
1121
- check=False,
1122
- env=None if env is None else {**os.environ, **env},
1123
- )
1124
- except FileNotFoundError:
1125
- return 127, f"{argv[0]}: executable not found on PATH"
1151
+ completed = _run_argv(argv, root, env=env)
1126
1152
  return completed.returncode, completed.stdout + (
1127
1153
  "\n" + completed.stderr if completed.stderr else ""
1128
1154
  )
@@ -1640,17 +1666,7 @@ def _run_routes_drift(root: pathlib.Path) -> tuple[int, str]:
1640
1666
  problem = _node_modules_problem(root, "frontend")
1641
1667
  if problem is not None:
1642
1668
  return 1, problem
1643
- argv = routes_argv(check=True)
1644
- executable = shutil.which(argv[0]) or argv[0]
1645
- completed = subprocess.run( # noqa: S603 - fixed manifest argv, shell=False
1646
- [executable, *argv[1:]],
1647
- cwd=root,
1648
- capture_output=True,
1649
- text=True,
1650
- encoding="utf-8",
1651
- errors="replace",
1652
- check=False,
1653
- )
1669
+ completed = _run_argv(routes_argv(check=True), root)
1654
1670
  return completed.returncode, f"{completed.stdout}{completed.stderr}"
1655
1671
 
1656
1672
 
@@ -1663,21 +1679,6 @@ _API_DOCS_ARTIFACTS = ("platform-api.md", "terp_core.pyi")
1663
1679
  _API_DOCS_ADOPT_HINT = "commit docs/platform-api.md + docs/terp_core.pyi to enable"
1664
1680
 
1665
1681
 
1666
- def _tracked(root: pathlib.Path, relative: str) -> bool:
1667
- """Whether *relative* is a file git is tracking in *root*."""
1668
- git = shutil.which("git") or "git"
1669
- completed = subprocess.run( # noqa: S603 - fixed argv, shell=False
1670
- [git, "ls-files", "--error-unmatch", "--", relative],
1671
- cwd=root,
1672
- capture_output=True,
1673
- text=True,
1674
- encoding="utf-8",
1675
- errors="replace",
1676
- check=False,
1677
- )
1678
- return completed.returncode == 0
1679
-
1680
-
1681
1682
  def _run_api_docs_drift(root: pathlib.Path) -> tuple[int, str]:
1682
1683
  """Regenerate the API reference and fail on drift from the committed copy.
1683
1684
 
@@ -1707,7 +1708,20 @@ def _run_api_docs_drift(root: pathlib.Path) -> tuple[int, str]:
1707
1708
  f"({_API_DOCS_ADOPT_HINT})",
1708
1709
  )
1709
1710
  relatives = [f"docs/{name}" for name in _API_DOCS_ARTIFACTS]
1710
- tracked = [relative for relative in relatives if _tracked(root, relative)]
1711
+ probes = {
1712
+ relative: _run_argv(["git", "ls-files", "--error-unmatch", "--", relative], root)
1713
+ for relative in relatives
1714
+ }
1715
+ if any(probe.returncode == _NOT_FOUND for probe in probes.values()):
1716
+ # Not "untracked, skipped": without git there is no answer to whether the pair is
1717
+ # tracked, and reading that silence as "no" would turn this check green over a
1718
+ # comparison it never made.
1719
+ return (
1720
+ _NOT_FOUND,
1721
+ "git: executable not found on PATH - the drift check asks git whether "
1722
+ f"{' + '.join(relatives)} are tracked, so it cannot run without it",
1723
+ )
1724
+ tracked = [relative for relative, probe in probes.items() if probe.returncode == 0]
1711
1725
  if not tracked:
1712
1726
  return (
1713
1727
  0,
@@ -1729,16 +1743,7 @@ def _run_api_docs_drift(root: pathlib.Path) -> tuple[int, str]:
1729
1743
  written = [str(path) for path in api_docs(str(docs))]
1730
1744
  finally:
1731
1745
  os.chdir(previous)
1732
- git = shutil.which("git") or "git"
1733
- completed = subprocess.run( # noqa: S603 - fixed argv, shell=False
1734
- [git, "diff", "--exit-code", "--", *relatives],
1735
- cwd=root,
1736
- capture_output=True,
1737
- text=True,
1738
- encoding="utf-8",
1739
- errors="replace",
1740
- check=False,
1741
- )
1746
+ completed = _run_argv(["git", "diff", "--exit-code", "--", *relatives], root)
1742
1747
  output = "\n".join(
1743
1748
  ["\n".join(f"wrote {path}" for path in written), completed.stdout]
1744
1749
  )
@@ -1941,17 +1946,7 @@ def _run_api_client(root: pathlib.Path) -> tuple[int, str]:
1941
1946
  return 1, f"could not export the OpenAPI document: {refusal}"
1942
1947
  finally:
1943
1948
  os.chdir(previous)
1944
- argv = ["npm", "--prefix", "frontend", "run", "generate"]
1945
- executable = shutil.which(argv[0]) or argv[0]
1946
- completed = subprocess.run( # noqa: S603 - fixed argv, shell=False
1947
- [executable, *argv[1:]],
1948
- cwd=root,
1949
- capture_output=True,
1950
- text=True,
1951
- encoding="utf-8",
1952
- errors="replace",
1953
- check=False,
1954
- )
1949
+ completed = _run_argv(["npm", "--prefix", "frontend", "run", "generate"], root)
1955
1950
  output = f"wrote {written.name}\n{completed.stdout}{completed.stderr}"
1956
1951
  if completed.returncode != 0:
1957
1952
  output += (
File without changes
File without changes
File without changes