Skip to content

# T-002 — init: idempotent + --force / --out #4

@ali90h

Description

@ali90h

T-002 — init: idempotent + --force / --out

Goal

Make autorepro init create a default devcontainer.json, idempotently. It must not overwrite an existing file unless --force is provided, and it must support writing the file to an alternate path via --out. Print clear messages and return correct exit codes.


Scope (concise implementation)

1) env.py

  • default_devcontainer() -> dict
    • Returns:
      • name: "autorepro-dev"
      • features:
        • ghcr.io/devcontainers/features/python:1 = {"version": "3.11"}
        • ghcr.io/devcontainers/features/node:1 = {"version": "20"}
        • ghcr.io/devcontainers/features/go:1 = {"version": "1.22"}
      • postCreateCommand:
        python -m venv .venv && . .venv/bin/activate && python -m pip install -e . && python -m pip install pytest
  • write_devcontainer(repo_dir: str | Path, *, force: bool = False, out: str | Path | None = None) -> Path
    • Compute target:
      • If out is provided → use it as an absolute or relative file path.
      • Else → <repo_dir>/devcontainer.json.
    • Ensure parent dirs: target.parent.mkdir(parents=True, exist_ok=True).
    • Build content = json.dumps(default_devcontainer(), indent=2).
    • Idempotent / atomic write:
      • If target exists and force == False → do not write; just return target.
      • If writing: write to a temporary file in the same directory, then replace() into target (best-effort atomic on the platform).
    • Always return Path(target).
    • Note: no printing here; printing is the CLI’s responsibility.

2) cli.py

  • Add subcommand: init
    • Flags:
      • --out PATH
      • --force (bool)
  • Behavior:
    • Set repo_dir = "." and forward flags to write_devcontainer.
    • Light validation:
      • If --out points to an existing directory (not a file), treat as misuse → print a clear message and return exit code 2.
    • Output:
      • If not written due to existing file and no --force:
        devcontainer.json already exists at <path>.
        Use --force to overwrite or --out <path> to write elsewhere.
        
        Exit 0.
      • If written the first time:
        Wrote devcontainer to <path>
        
        Exit 0.
      • If overwritten with --force:
        Overwrote devcontainer at <path>
        
        Exit 0.
    • Unexpected filesystem errors → print a short message and return exit code 1.
  • Reminder: main() stays in the current style (parse once + dispatch), and SystemExit is mapped to an int return as previously fixed.

Acceptance Criteria (Definition of Done)

  • Running autorepro init in an empty directory creates a valid JSON devcontainer.json and prints its path.
  • Re-running without --force does not change the file and prints “already exists …” (exit 0).
  • Running with --force overwrites and prints “Overwrote …”.
  • --out dev/devcontainer.json writes to that path and creates missing parent directories.
  • All tests pass on CI (Ubuntu, Python 3.11).

Test Plan (tests/test_init.py)

  1. Create new
    • In tmp_path, run CLI: init.
    • Assert devcontainer.json exists.
    • Load JSON and assert keys (features, postCreateCommand).
  2. Idempotent (no --force)
    • Run init twice.
    • Compare mtime or contents before/after to confirm no change.
    • Assert “already exists” text.
  3. Force
    • Record mtime/content; run init --force; assert mtime changed (or content re-written) and “Overwrote …” is printed.
  4. Out path
    • init --out dev/devcontainer.json
    • File is written to tmp_path/"dev/devcontainer.json"; directory dev/ is created automatically.
  5. Invalid --out (points to a directory)
    • Create a directory foo/; run init --out foo
    • Return exit 2 with a clear message.
  6. Exit codes
    • All successful cases (create/skip/overwrite) return 0.
    • Misuse (--out is a directory) returns 2.
    • Optional: simulate I/O error returns 1.

Implementation note: run CLI tests via capsys or by calling main() with monkeypatch.setenv / sys.argv. Optionally unit-test env.write_devcontainer separately (without CLI).


README Examples

$ autorepro init
Wrote devcontainer to ./devcontainer.json

$ autorepro init
devcontainer.json already exists at ./devcontainer.json
Use --force to overwrite or --out <path> to write elsewhere.

$ autorepro init --force
Overwrote devcontainer at ./devcontainer.json

$ autorepro init --out dev/devcontainer.json
Wrote devcontainer to dev/devcontainer.json

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions