Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/base-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ jobs:
ref: 2c5ef2c3a9edfbe2cf68d0645be65b920255abff
path: .base/base-bash-libs

- name: Checkout base-cli source
if: ${{ inputs.setup-mode == 'source-checkout' }}
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: basefoundry/base-cli
ref: 9f3ed25fc0991e3aa9de8411878805c122f90a11
path: .base/base-cli

- name: Set up Python
if: ${{ inputs.setup-mode == 'source-checkout' }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
Expand Down Expand Up @@ -134,6 +142,7 @@ jobs:
run: |
python -m venv "$HOME/.base.d/base/.venv"
"$HOME/.base.d/base/.venv/bin/python" -m pip install --upgrade pip
"$HOME/.base.d/base/.venv/bin/python" -m pip install --no-deps "$GITHUB_WORKSPACE/.base/base-cli"
"$HOME/.base.d/base/.venv/bin/python" -m pip install -r requirements-dev.txt

- name: Run basectl check --ci
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ jobs:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

- name: Checkout base-cli source
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: basefoundry/base-cli
ref: 9f3ed25fc0991e3aa9de8411878805c122f90a11
path: .dependencies/base-cli

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
Expand All @@ -33,6 +40,7 @@ jobs:
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --no-deps .dependencies/base-cli
python -m pip install -r requirements-dev.txt

- name: Run lightweight Base validation
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: basefoundry/base-cli
ref: fc3ac1826b62de9c84d40c77b131ea3c5e660f14
ref: 9f3ed25fc0991e3aa9de8411878805c122f90a11
path: .dependencies/base-cli

- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -36,6 +36,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --no-deps .dependencies/base-cli
python -m pip install -r requirements-dev.txt

- name: Run Pylint
Expand Down
28 changes: 22 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: basefoundry/base-cli
ref: fc3ac1826b62de9c84d40c77b131ea3c5e660f14
ref: 9f3ed25fc0991e3aa9de8411878805c122f90a11
path: .dependencies/base-cli

- name: Set up Python
Expand All @@ -42,6 +42,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --no-deps .dependencies/base-cli
python -m pip install -r requirements-dev.txt

- name: Run Python tests
Expand Down Expand Up @@ -72,7 +73,7 @@ jobs:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: basefoundry/base-cli
ref: fc3ac1826b62de9c84d40c77b131ea3c5e660f14
ref: 9f3ed25fc0991e3aa9de8411878805c122f90a11
path: .dependencies/base-cli

- name: Expose standalone Python package checkout as sibling
Expand Down Expand Up @@ -124,7 +125,7 @@ jobs:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: basefoundry/base-cli
ref: fc3ac1826b62de9c84d40c77b131ea3c5e660f14
ref: 9f3ed25fc0991e3aa9de8411878805c122f90a11
path: .dependencies/base-cli

- name: Expose standalone Python package checkout as sibling
Expand Down Expand Up @@ -216,7 +217,7 @@ jobs:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: basefoundry/base-cli
ref: fc3ac1826b62de9c84d40c77b131ea3c5e660f14
ref: 9f3ed25fc0991e3aa9de8411878805c122f90a11
path: .dependencies/base-cli

- name: Expose standalone Python package checkout as sibling
Expand All @@ -240,10 +241,12 @@ jobs:
run: |
python -m venv .integration-venv
.integration-venv/bin/python -m pip install --upgrade pip
.integration-venv/bin/python -m pip install --no-deps .dependencies/base-cli
.integration-venv/bin/python -m pip install -r requirements-dev.txt
mkdir -p "$HOME/.base.d/base"
python -m venv "$HOME/.base.d/base/.venv"
"$HOME/.base.d/base/.venv/bin/python" -m pip install --upgrade pip
"$HOME/.base.d/base/.venv/bin/python" -m pip install --no-deps .dependencies/base-cli
"$HOME/.base.d/base/.venv/bin/python" -m pip install -r requirements-dev.txt
sudo apt-get update
sudo apt-get install -y bats
Expand Down Expand Up @@ -278,7 +281,7 @@ jobs:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: basefoundry/base-cli
ref: fc3ac1826b62de9c84d40c77b131ea3c5e660f14
ref: 9f3ed25fc0991e3aa9de8411878805c122f90a11
path: .dependencies/base-cli

- name: Expose reusable Bash library checkout as sibling
Expand Down Expand Up @@ -317,6 +320,7 @@ jobs:
mkdir -p "$HOME/.base.d/base"
python3 -m venv "$HOME/.base.d/base/.venv"
"$HOME/.base.d/base/.venv/bin/python" -m pip install --upgrade pip
"$HOME/.base.d/base/.venv/bin/python" -m pip install --no-deps .dependencies/base-cli
"$HOME/.base.d/base/.venv/bin/python" -m pip install -r requirements-dev.txt

- name: Run Ubuntu source-checkout validation
Expand All @@ -336,6 +340,12 @@ jobs:
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: basefoundry/base-cli
ref: 9f3ed25fc0991e3aa9de8411878805c122f90a11
path: .dependencies/base-cli

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
Expand All @@ -352,6 +362,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --no-deps .dependencies/base-cli
python -m pip install -r requirements-dev.txt
sudo apt-get update
sudo apt-get install -y shellcheck
Expand All @@ -362,7 +373,12 @@ jobs:

- name: Run pip-audit
run: |
python -m pip_audit --cache-dir "$RUNNER_TEMP/pip-audit" -r requirements-dev.txt
# Start from -r requirements-dev.txt, but omit base-cli because it
# is checked out locally and may not be published during review.
grep -Eiv '^base-cli[[:space:]]*=' requirements-dev.txt \
> "$RUNNER_TEMP/base-audit-requirements.txt"
python -m pip_audit --cache-dir "$RUNNER_TEMP/pip-audit" \
-r "$RUNNER_TEMP/base-audit-requirements.txt"

- name: Run ShellCheck
run: |
Expand Down
20 changes: 12 additions & 8 deletions cli/python/base_cli_adapters/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pathlib import Path
from typing import Any

from base_cli import ConfigurationError
from base_cli.config import load_yaml_file as load_cli_yaml_file
from base_setup.ide_schema import parse_ide_extensions
from base_setup.ide_schema import parse_ide_settings
Expand Down Expand Up @@ -67,14 +68,17 @@ def read_user_config(
*,
supported_ides: frozenset[str] | None = SUPPORTED_IDES,
) -> UserConfig:
raw = load_user_config(home)
path = user_config_path(home)
return UserConfig(
raw=raw,
workspace=_read_user_workspace_config(path, raw.get("workspace")),
github=_read_user_github_config(path, raw.get("github")),
ide=_read_user_ide_config(path, raw.get("ide"), supported_ides=supported_ides),
)
try:
raw = load_user_config(home)
path = user_config_path(home)
return UserConfig(
raw=raw,
workspace=_read_user_workspace_config(path, raw.get("workspace")),
github=_read_user_github_config(path, raw.get("github")),
ide=_read_user_ide_config(path, raw.get("ide"), supported_ides=supported_ides),
)
except ValueError as exc:
raise ConfigurationError(str(exc)) from exc


def _read_user_workspace_config(path: Path, workspace_data: Any) -> UserWorkspaceConfig:
Expand Down
2 changes: 1 addition & 1 deletion cli/python/base_cli_adapters/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from pathlib import Path

from base_cli._runtime import RuntimeLayout
from base_cli.runtime import RuntimeLayout

from .paths import runtime_owner_root
from .paths import runtime_run_directory_name
Expand Down
18 changes: 18 additions & 0 deletions cli/python/base_github_projects/project_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ def parse_project_options(
state.allow_cross_repo = True
index += 1
continue
if apply_inline_option(state, arg, allow_fields=allow_fields):
index += 1
continue
consumed = apply_spaced_option(state, remaining, index, allow_fields=allow_fields)
if consumed:
index += consumed
Expand Down Expand Up @@ -183,6 +186,21 @@ def apply_spaced_option(state: OptionState, remaining: list[str], index: int, *,
return OPTION_NOT_CONSUMED


def apply_inline_option(state: OptionState, argument: str, *, allow_fields: bool) -> bool:
"""Apply Click-compatible ``--option=value`` syntax to delegated options."""
if not argument.startswith("--") or "=" not in argument:
return False

option, value = argument.split("=", 1)
if option in PROJECT_VALUE_OPTIONS:
apply_project_option(state, option, value)
return True
if allow_fields and option in ISSUE_FIELD_OPTIONS:
state.field_values[option[2:]] = value
return True
return False


def option_value(remaining: list[str], index: int) -> str:
option = remaining[index]
if index + 1 >= len(remaining):
Expand Down
18 changes: 6 additions & 12 deletions cli/python/base_github_projects/tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,13 @@ def test_delegated_usage_uses_basectl_gh_project_prefix(
assert "base_github_projects" not in captured.err


def test_main_rejects_equals_form_project_options(
capsys: pytest.CaptureFixture[str],
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("BASE_CACHE_DIR", str(tmp_path / ".cache" / "base"))

status = engine.main(["project", "configure", "--project=Base Roadmap", "--dry-run"])
def test_parse_project_configure_accepts_equals_form_options() -> None:
args = engine.parse_args(
("project", "configure", "--project=Base Roadmap", "--dry-run"),
)

captured = capsys.readouterr()
assert status == 2
assert "Option '--project' uses unsupported equals syntax." in captured.err
assert args.project_title == "Base Roadmap"
assert args.dry_run is True


def test_parse_project_configure_arguments() -> None:
Expand Down
4 changes: 3 additions & 1 deletion cli/python/base_history/tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def test_text_output_lists_recent_history_and_missing_log_marker(self) -> None:
)

status, stdout, stderr = invoke([], cache_root)
records = engine.recent_history(cache_root)

self.assertEqual(status, 0)
self.assertEqual(stderr, "")
Expand All @@ -117,7 +118,8 @@ def test_text_output_lists_recent_history_and_missing_log_marker(self) -> None:
self.assertIn("PROJECT", stdout)
self.assertIn("check", stdout)
self.assertIn("error", stdout)
self.assertIn("missing", stdout)
self.assertEqual(len(records), 1)
self.assertTrue(engine.display_log_path(records[0]).endswith(" (missing)"))

def test_text_table_expands_columns_for_long_command_and_project(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
Expand Down
9 changes: 6 additions & 3 deletions cli/python/base_pr_policy/tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ def test_explicit_manifest_populates_history_project_metadata(tmp_path) -> None:
assert record["manifest"] == str(manifest_path.resolve())


def test_main_rejects_equals_form_options(capsys) -> None:
def test_main_accepts_equals_form_options(capsys, monkeypatch, tmp_path) -> None:
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.setenv("BASE_CACHE_DIR", str(tmp_path / ".cache" / "base"))

status = engine.main(["body", "--issue=403"])

captured = capsys.readouterr()
assert status == 2
assert "Option '--issue' uses unsupported equals syntax." in captured.err
assert status == 0
assert "Fixes #403" in captured.out


def test_render_pr_body_uses_default_label_and_path_sections() -> None:
Expand Down
2 changes: 1 addition & 1 deletion cli/python/base_projects/tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ def test_main_reports_config_errors_without_traceback(self) -> None:
user_config="workspace: [not-a-mapping]\n",
)

self.assertEqual(status, 1)
self.assertEqual(status, 2)
self.assertIn("workspace must be a mapping", stderr)
self.assertNotIn("Traceback", stderr)

Expand Down
2 changes: 1 addition & 1 deletion cli/python/base_trust/tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def test_require_explicit_manifest_populates_history_project_metadata(self) -> N
env={"BASE_HOME": str(root / "base")},
)

self.assertEqual(result.exit_code, 0)
self.assertEqual(result.exit_code, 1)
self.assertIn("Manifest-declared commands are not allowed", result.stderr)
self.assertEqual(len(captured), 1)
record = build_finished_record(*captured[0])
Expand Down
20 changes: 12 additions & 8 deletions docs/base-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ if __name__ == "__main__":

The command function receives `ctx` as its first argument. Infrastructure is
created immediately before command execution and cleaned up afterward.
`base_cli.run_app()` applies Base's command syntax guard before Click parses
arguments: long options with values must use space-separated syntax, such as
`--name Ada`; equals-form values such as `--name=Ada` are rejected.
`base_cli.run_app()` preserves Click's native option syntax, including both
space-separated values such as `--name Ada` and equals-form values such as
`--name=Ada`.

## Package Layout

Expand Down Expand Up @@ -141,7 +141,6 @@ registration.
```python
ctx.cli_name # str
ctx.run_id # str
ctx.base_home # Path | None
ctx.application_home # Path | None; neutral application-home alias
ctx.project_name # selected project name, or None
ctx.project_root # Path | None
Expand All @@ -157,14 +156,20 @@ ctx.log_dir # run_root/logs
ctx.cache_dir # owner_root/cache/components/<cli-name>
ctx.temp_dir # run_root/tmp/<cli-name>/<run-id>
ctx.log_file # run_root/logs/primary.log, or None when disabled
ctx.config # dict
ctx.config # consumer-owned configuration payload
ctx.framework_config # validated framework lifecycle settings, or None
ctx.config_provenance # configuration source mapping, when provided
ctx.user_config # typed user config from ~/.base.d/config.yaml
ctx.application_context # optional consumer application state
ctx.services # optional consumer services
ctx.history_display_command # consumer policy for persisted command labels
ctx.environment # str
ctx.debug # bool
ctx.dry_run # bool
ctx.keep_temp # bool
ctx.quiet # bool
ctx.json_output # bool
ctx.rich # bool; optional Rich integration enabled
ctx.log # logging.Logger
```

Expand Down Expand Up @@ -368,9 +373,8 @@ Direct `base_cli.App` command packages get:
| `--version` | show the CLI version when configured |
| `--help` | Click help |

Long option values must use the space-separated form, for example
`--environment prod`. Base rejects `--option=value` before Click parses
arguments.
Long option values accept either Click form, for example `--environment prod`
or `--environment=prod`.

These are direct Python package options. Public `basectl` launchers expose
`-v` for command-level debug logs and command-specific flags from
Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pylint==3.3.9
click==8.4.1
base-cli==0.2.0
base-cli==0.4.0
PyYAML==6.0.3
pytest==9.0.3
pytest-cov==7.1.0
Expand Down
Loading
Loading