diff --git a/src/create_dlthub_workspace/cli.py b/src/create_dlthub_workspace/cli.py index fd62cce..1dd2acb 100644 --- a/src/create_dlthub_workspace/cli.py +++ b/src/create_dlthub_workspace/cli.py @@ -81,26 +81,21 @@ def build_parser() -> argparse.ArgumentParser: choices=AGENTS, help=f"Coding agent to set up ({', '.join(AGENTS)}). If omitted, you'll be prompted to choose (default: {RECOMMENDED.agent}).", ) - # Hidden, non-interactive shortcut for tests/CI only. Absent from --help so the - # interactive flow is the sole documented path. See MSG_TESTING_SHORTCUT_NOTE. - parser.add_argument( - "--yes", - "-y", - action="store_true", - help=argparse.SUPPRESS, - ) parser.add_argument( "--verbose", "-v", action="store_true", help="Stream output from underlying subprocesses (uv, dlthub).", ) - # Hidden, like --yes: a non-interactive shortcut for tests/CI that stops - # before dependency sync (and, with it, the guided first run), leaving an - # incomplete workspace. Kept functional but absent from --help so the - # interactive flow is the sole documented path. See MSG_TESTING_SHORTCUT_NOTE. + # Dev/CI only; hidden from --help. Both skip the first run. + # --setup-only installs deps; --scaffold-only skips deps too. + parser.add_argument( + "--setup-only", + action="store_true", + help=argparse.SUPPRESS, + ) parser.add_argument( - "--skip-uv-sync", + "--scaffold-only", action="store_true", help=argparse.SUPPRESS, ) @@ -139,7 +134,7 @@ def run(args: argparse.Namespace) -> None: print_banner() console.print() - if args.yes or args.skip_uv_sync: + if args.setup_only or args.scaffold_only: err_console.print(strings.MSG_TESTING_SHORTCUT_NOTE) verbose = args.verbose @@ -165,7 +160,7 @@ def run(args: argparse.Namespace) -> None: uv_executable = find_uv() if uv_executable is None: - if args.yes or confirm(strings.PROMPT_INSTALL_UV, recommended=RECOMMENDED.install_uv): + if args.setup_only or confirm(strings.PROMPT_INSTALL_UV, recommended=RECOMMENDED.install_uv): uv_executable = execute_uv_install(verbose=verbose) else: _finalize_agent(project_dir, scaffold, args, verbose=verbose) @@ -173,7 +168,7 @@ def run(args: argparse.Namespace) -> None: print_resume_steps(project_dir, uv_installed=False) return - if args.skip_uv_sync: + if args.scaffold_only: _finalize_agent(project_dir, scaffold, args, verbose=verbose) console.print(strings.MSG_SKIPPED_SYNC) print_resume_steps(project_dir, uv_installed=True) @@ -182,11 +177,11 @@ def run(args: argparse.Namespace) -> None: with substep(strings.MSG_INSTALLING_DEPS, strings.MSG_INSTALLED_DEPS, verbose=verbose): run_uv_sync(uv_executable, project_dir, verbose=verbose) - # Skipped under --yes: the first run's login is interactive. A run that exits + # Skipped under --setup-only: the first run's login is interactive. A run that exits # non-zero degrades to a warning so the rest of setup still completes. # LIMITATION: `dlthub run --follow` can exit 0 on a failed remote run, so a # genuine run failure isn't caught here — we'd still report success. Revisit. - first_pipeline_ran = not args.yes + first_pipeline_ran = not args.setup_only if first_pipeline_ran: try: _run_first_pipeline(uv_executable, project_dir, verbose=verbose) @@ -206,8 +201,8 @@ def run(args: argparse.Namespace) -> None: def _finalize_agent(project_dir: Path, scaffold: str, args: argparse.Namespace, *, verbose: bool) -> str: - """Resolve the agent (prompting unless --agent/--yes set it) and lay down its AI files.""" - agent = args.agent or (RECOMMENDED.agent if args.yes else choose_agent()) + """Resolve the agent (prompting unless --agent/--setup-only set it) and lay down its AI files.""" + agent = args.agent or (RECOMMENDED.agent if args.setup_only else choose_agent()) with substep( strings.MSG_ADDING_AGENT_FILES.format(agent=agent), strings.MSG_ADDED_AGENT_FILES.format(agent=agent), diff --git a/src/create_dlthub_workspace/config.py b/src/create_dlthub_workspace/config.py index d9efc8a..32fe3dc 100644 --- a/src/create_dlthub_workspace/config.py +++ b/src/create_dlthub_workspace/config.py @@ -37,7 +37,7 @@ @dataclass(frozen=True) class RecommendedPath: - """The path we recommend new users follow. Also the path `--yes` runs.""" + """The path we recommend new users follow. Also the path `--setup-only` runs.""" scaffold: str install_uv: bool diff --git a/src/create_dlthub_workspace/display.py b/src/create_dlthub_workspace/display.py index 7ec36fb..9103c0c 100644 --- a/src/create_dlthub_workspace/display.py +++ b/src/create_dlthub_workspace/display.py @@ -19,7 +19,7 @@ from .config import VERSION console = Console() -# Separate stderr stream for out-of-band notes (e.g. the hidden --yes warning), +# Separate stderr stream for out-of-band notes (e.g. the hidden testing-shortcut warning), # so they don't interleave with the primary stdout output. err_console = Console(stderr=True) diff --git a/src/create_dlthub_workspace/strings.py b/src/create_dlthub_workspace/strings.py index 8e7973f..829cb0d 100644 --- a/src/create_dlthub_workspace/strings.py +++ b/src/create_dlthub_workspace/strings.py @@ -44,13 +44,9 @@ # Status / info messages ------------------------------------------------ -# Printed to stderr when a hidden testing shortcut (--yes or --skip-uv-sync) is -# used. Both are non-interactive testing/CI affordances only and cut the guided -# setup short — leaving the workspace scaffolded but not run (and, for -# --skip-uv-sync, dependencies not installed). The normal, complete path is the -# interactive one (no flag). Hidden from --help so it stays the sole documented path. +# Out-of-band stderr note shown when a hidden dev flag (--setup-only / --scaffold-only) is used. MSG_TESTING_SHORTCUT_NOTE = ( - "[yellow]Note:[/yellow] --yes / --skip-uv-sync are non-interactive shortcuts for testing/CI. " + "[yellow]Note:[/yellow] --setup-only / --scaffold-only are non-interactive shortcuts for testing/CI. " "They cut the guided setup short, so onboarding is incomplete. " "Run without them for the full interactive setup." ) diff --git a/tests/test_cli.py b/tests/test_cli.py index 986eaef..0f3bece 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -35,32 +35,26 @@ def test_rejects_unknown_agent(self) -> None: with _silenced(), self.assertRaises(SystemExit): parser.parse_args(["my_workspace", "--agent", "not-an-agent"]) - def test_yes_flag_still_parses_to_true(self) -> None: - # --yes stays functional for tests/CI even though it's hidden from --help. + def test_setup_only_flag_parses_to_true(self) -> None: + # --setup-only stays functional for tests/CI even though it's hidden from --help. parser = build_parser() - args = parser.parse_args(["my_workspace", "--yes"]) - self.assertTrue(args.yes) + args = parser.parse_args(["my_workspace", "--setup-only"]) + self.assertTrue(args.setup_only) - def test_short_yes_flag_parses_to_true(self) -> None: - parser = build_parser() - args = parser.parse_args(["my_workspace", "-y"]) - self.assertTrue(args.yes) - - def test_yes_defaults_to_false(self) -> None: + def test_setup_only_defaults_to_false(self) -> None: parser = build_parser() args = parser.parse_args(["my_workspace"]) - self.assertFalse(args.yes) + self.assertFalse(args.setup_only) def test_testing_shortcuts_are_hidden_from_help(self) -> None: help_text = build_parser().format_help() - self.assertNotIn("--yes", help_text) - self.assertNotIn("-y", help_text) - self.assertNotIn("--skip-uv-sync", help_text) + self.assertNotIn("--setup-only", help_text) + self.assertNotIn("--scaffold-only", help_text) - def test_skip_uv_sync_flag_parses_to_true(self) -> None: + def test_scaffold_only_flag_parses_to_true(self) -> None: parser = build_parser() - args = parser.parse_args(["my_workspace", "--skip-uv-sync"]) - self.assertTrue(args.skip_uv_sync) + args = parser.parse_args(["my_workspace", "--scaffold-only"]) + self.assertTrue(args.scaffold_only) def test_agent_parses_single_value(self) -> None: parser = build_parser() @@ -114,9 +108,9 @@ def _make_args(**overrides: object) -> argparse.Namespace: defaults: dict[str, object] = { "project_dir": "/tmp/test_workspace", "agent": None, - "yes": False, + "setup_only": False, "verbose": False, - "skip_uv_sync": False, + "scaffold_only": False, } defaults.update(overrides) return argparse.Namespace(**defaults) @@ -211,8 +205,8 @@ def test_explicit_agent_skips_the_prompt(self) -> None: self.m["choose_agent"].assert_not_called() self.assertEqual(self.m["overlay_agent"].call_args.kwargs["agent"], "codex") - def test_yes_skips_first_run_and_prompt_but_still_adds_agent_files(self) -> None: - self._run(yes=True) + def test_setup_only_skips_first_run_and_prompt_but_still_adds_agent_files(self) -> None: + self._run(setup_only=True) self.m["run_uv_command"].assert_not_called() self.m["choose_agent"].assert_not_called() self.assertEqual(self.m["overlay_agent"].call_args.kwargs["agent"], RECOMMENDED.agent) @@ -230,8 +224,8 @@ def test_uv_declined_stops_at_scaffold_only_but_adds_agent_files(self) -> None: self.m["print_next_steps"].assert_not_called() self.m["print_resume_steps"].assert_called_once_with(Path("/tmp/test_workspace"), uv_installed=False) - def test_skip_uv_sync_stops_after_install_but_adds_agent_files(self) -> None: - self._run(skip_uv_sync=True) + def test_scaffold_only_stops_after_install_but_adds_agent_files(self) -> None: + self._run(scaffold_only=True) self.m["run_uv_sync"].assert_not_called() self.m["overlay_agent"].assert_called_once() self.m["print_next_steps"].assert_not_called() @@ -311,14 +305,14 @@ def _run_with(self, **flags: bool) -> MagicMock: run(_make_args(**flags)) return err_console - def test_yes_prints_testing_notice(self) -> None: - self._run_with(yes=True).print.assert_called_once() + def test_setup_only_prints_testing_notice(self) -> None: + self._run_with(setup_only=True).print.assert_called_once() - def test_skip_uv_sync_prints_testing_notice(self) -> None: - self._run_with(skip_uv_sync=True).print.assert_called_once() + def test_scaffold_only_prints_testing_notice(self) -> None: + self._run_with(scaffold_only=True).print.assert_called_once() def test_both_shortcuts_print_a_single_notice(self) -> None: - self._run_with(yes=True, skip_uv_sync=True).print.assert_called_once() + self._run_with(setup_only=True, scaffold_only=True).print.assert_called_once() def test_interactive_run_prints_no_notice(self) -> None: self._run_with().print.assert_not_called() diff --git a/tests_integration/test_e2e_workspace.py b/tests_integration/test_e2e_workspace.py index eee144a..ec754b2 100644 --- a/tests_integration/test_e2e_workspace.py +++ b/tests_integration/test_e2e_workspace.py @@ -22,17 +22,17 @@ class WorkspaceCreationFastTests(unittest.TestCase): - """E2E paths that use --skip-uv-sync: no real `uv sync`, runs in ~1s. + """E2E paths that use --scaffold-only: no real `uv sync`, runs in ~1s. Validates the orchestration layer (argparse → run → copy_scaffold) end-to-end without paying the sync cost. """ - def test_yes_skip_sync_creates_workspace_without_venv(self) -> None: + def test_scaffold_only_creates_workspace_without_venv(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: ws = Path(tmpdir) / "test_ws" with silenced(): - exit_code = main([str(ws), "--yes", "--skip-uv-sync"]) + exit_code = main([str(ws), "--setup-only", "--scaffold-only"]) self.assertEqual(exit_code, 0) self.assertTrue(ws.is_dir()) @@ -40,7 +40,7 @@ def test_yes_skip_sync_creates_workspace_without_venv(self) -> None: self.assertTrue((ws / "pipeline.py").exists()) self.assertFalse( (ws / ".venv").exists(), - "--skip-uv-sync should prevent .venv creation", + "--scaffold-only should prevent .venv creation", ) @unittest.skipUnless( @@ -52,7 +52,7 @@ def test_single_agent_selection_filters_other_agents(self) -> None: ws = Path(tmpdir) / "test_ws" with silenced(): exit_code = main( - [str(ws), "--yes", "--skip-uv-sync", "--agent", "claude"], + [str(ws), "--setup-only", "--scaffold-only", "--agent", "claude"], ) self.assertEqual(exit_code, 0) @@ -76,14 +76,14 @@ def test_single_agent_selection_filters_other_agents(self) -> None: scaffold_has_ai_files(), "AI workbench files not committed yet — run `make generate-ai` first.", ) - def test_yes_default_brings_in_only_the_recommended_agent(self) -> None: + def test_setup_only_brings_in_only_the_recommended_agent(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: ws = Path(tmpdir) / "test_ws" with silenced(): - exit_code = main([str(ws), "--yes", "--skip-uv-sync"]) + exit_code = main([str(ws), "--setup-only", "--scaffold-only"]) self.assertEqual(exit_code, 0) - # --yes uses the recommended agent (claude) and only that agent. + # --setup-only uses the recommended agent (claude) and only that agent. for entry in EXPECTED_AGENT_ROOT_ENTRIES["claude"]: self.assertTrue((ws / entry).exists(), f"recommended agent's {entry!r} should be present") for entry in (*EXPECTED_AGENT_ROOT_ENTRIES["cursor"], *EXPECTED_AGENT_ROOT_ENTRIES["codex"]): @@ -93,11 +93,11 @@ def test_yes_default_brings_in_only_the_recommended_agent(self) -> None: class WorkspaceCreationSlowTests(unittest.TestCase): """E2E paths that run a real `uv sync` (~30-60s, network needed).""" - def test_yes_default_runs_uv_sync_to_completion(self) -> None: + def test_setup_only_runs_uv_sync_to_completion(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: ws = Path(tmpdir) / "test_ws" with silenced(): - exit_code = main([str(ws), "--yes"]) + exit_code = main([str(ws), "--setup-only"]) self.assertEqual(exit_code, 0) self.assertTrue((ws / ".venv").is_dir(), "uv sync should have created .venv") @@ -112,12 +112,12 @@ def test_second_run_at_same_path_relocates_to_sibling(self) -> None: ws = Path(tmpdir) / "collision_test" with silenced(): - first_exit = main([str(ws), "--yes", "--skip-uv-sync"]) + first_exit = main([str(ws), "--setup-only", "--scaffold-only"]) self.assertEqual(first_exit, 0, "First run should succeed") self.assertTrue(ws.is_dir()) with silenced(): - second_exit = main([str(ws), "--yes", "--skip-uv-sync"]) + second_exit = main([str(ws), "--setup-only", "--scaffold-only"]) self.assertEqual(second_exit, 0, "Second run should succeed by relocating, not fail") sibling = ws.with_name("collision_test-1") @@ -129,7 +129,7 @@ def test_second_run_at_same_path_relocates_to_sibling(self) -> None: class InstalledEntryPointTests(unittest.TestCase): """Spawns the actual CLI binary via subprocess to validate the installed - entry point (`dlthub-start` on PATH). Uses --skip-uv-sync to stay fast — + entry point (`dlthub-start` on PATH). Uses --scaffold-only to stay fast — the sync itself is covered by WorkspaceCreationSlowTests. """ @@ -142,8 +142,8 @@ def test_subprocess_invocation_succeeds(self) -> None: "-m", "create_dlthub_workspace", str(ws), - "--yes", - "--skip-uv-sync", + "--setup-only", + "--scaffold-only", ], capture_output=True, check=False, @@ -170,8 +170,8 @@ def test_subprocess_relocates_occupied_target(self) -> None: "-m", "create_dlthub_workspace", str(ws), - "--yes", - "--skip-uv-sync", + "--setup-only", + "--scaffold-only", ], capture_output=True, check=False,