Local dev guide and script updates
This commit is contained in:
@@ -12,6 +12,7 @@ Spec-Driven Development **flips the script** on traditional software development
|
|||||||
|
|
||||||
- [Installation Guide](installation.md)
|
- [Installation Guide](installation.md)
|
||||||
- [Quick Start Guide](quickstart.md)
|
- [Quick Start Guide](quickstart.md)
|
||||||
|
- [Local Development](local-development.md)
|
||||||
|
|
||||||
## Core Philosophy
|
## Core Philosophy
|
||||||
|
|
||||||
|
|||||||
131
docs/local-development.md
Normal file
131
docs/local-development.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# Local Development Guide
|
||||||
|
|
||||||
|
This guide shows how to iterate on the `specify` CLI locally without publishing a release or committing to `main` first.
|
||||||
|
|
||||||
|
## 1. Clone and Switch Branches
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/github/spec-kit.git
|
||||||
|
cd spec-kit
|
||||||
|
# Work on a feature branch
|
||||||
|
git checkout -b your-feature-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Run the CLI Directly (Fastest Feedback)
|
||||||
|
|
||||||
|
You can execute the CLI via the module entrypoint without installing anything:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From repo root
|
||||||
|
python -m src.specify_cli --help
|
||||||
|
python -m src.specify_cli init demo-project --ai claude --ignore-agent-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
If you prefer invoking the script file style (uses shebang):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python src/specify_cli/__init__.py init demo-project
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Use Editable Install (Isolated Environment)
|
||||||
|
|
||||||
|
Create an isolated environment using `uv` so dependencies resolve exactly like end users get them:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create & activate virtual env (uv auto-manages .venv)
|
||||||
|
uv venv
|
||||||
|
source .venv/bin/activate # or on Windows: .venv\\Scripts\\activate
|
||||||
|
|
||||||
|
# Install project in editable mode
|
||||||
|
uv pip install -e .
|
||||||
|
|
||||||
|
# Now 'specify' entrypoint is available
|
||||||
|
specify --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Re-running after code edits requires no reinstall because of editable mode.
|
||||||
|
|
||||||
|
## 4. Invoke with uvx Directly From Git (Current Branch)
|
||||||
|
|
||||||
|
`uvx` can run from a local path (or a Git ref) to simulate user flows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uvx --from . specify init demo-uvx --ai copilot --ignore-agent-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also point uvx at a specific branch without merging:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Push your working branch first
|
||||||
|
git push origin your-feature-branch
|
||||||
|
uvx --from git+https://github.com/github/spec-kit.git@your-feature-branch specify init demo-branch-test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Testing Script Permission Logic
|
||||||
|
After running an `init`, check that shell scripts are executable on POSIX systems:
|
||||||
|
```bash
|
||||||
|
ls -l scripts | grep .sh
|
||||||
|
# Expect owner execute bit (e.g. -rwxr-xr-x)
|
||||||
|
```
|
||||||
|
On Windows this step is a no-op.
|
||||||
|
|
||||||
|
## 6. Run Lint / Basic Checks (Add Your Own)
|
||||||
|
Currently no enforced lint config is bundled, but you can quickly sanity check importability:
|
||||||
|
```bash
|
||||||
|
python -c "import specify_cli; print('Import OK')"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Build a Wheel Locally (Optional)
|
||||||
|
Validate packaging before publishing:
|
||||||
|
```bash
|
||||||
|
uv build
|
||||||
|
ls dist/
|
||||||
|
```
|
||||||
|
Install the built artifact into a fresh throwaway environment if needed.
|
||||||
|
|
||||||
|
## 8. Using a Temporary Workspace
|
||||||
|
When testing `init --here` in a dirty directory, create a temp workspace:
|
||||||
|
```bash
|
||||||
|
mkdir /tmp/spec-test && cd /tmp/spec-test
|
||||||
|
python -m src.specify_cli init --here --ai claude --ignore-agent-tools # if repo copied here
|
||||||
|
```
|
||||||
|
Or copy only the modified CLI portion if you want a lighter sandbox.
|
||||||
|
|
||||||
|
## 9. Debug Network / TLS Skips
|
||||||
|
If you need to bypass TLS validation while experimenting:
|
||||||
|
```bash
|
||||||
|
specify check --skip-tls
|
||||||
|
specify init demo --skip-tls --ai gemini --ignore-agent-tools
|
||||||
|
```
|
||||||
|
(Use only for local experimentation.)
|
||||||
|
|
||||||
|
## 10. Rapid Edit Loop Summary
|
||||||
|
| Action | Command |
|
||||||
|
|--------|---------|
|
||||||
|
| Run CLI directly | `python -m src.specify_cli --help` |
|
||||||
|
| Editable install | `uv pip install -e .` then `specify ...` |
|
||||||
|
| Local uvx run | `uvx --from . specify ...` |
|
||||||
|
| Git branch uvx | `uvx --from git+URL@branch specify ...` |
|
||||||
|
| Build wheel | `uv build` |
|
||||||
|
|
||||||
|
## 11. Cleaning Up
|
||||||
|
Remove build artifacts / virtual env quickly:
|
||||||
|
```bash
|
||||||
|
rm -rf .venv dist build *.egg-info
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Common Issues
|
||||||
|
| Symptom | Fix |
|
||||||
|
|---------|-----|
|
||||||
|
| `ModuleNotFoundError: typer` | Run `uv pip install -e .` |
|
||||||
|
| Scripts not executable (Linux) | Re-run init (logic adds bits) or `chmod +x scripts/*.sh` |
|
||||||
|
| Git step skipped | You passed `--no-git` or Git not installed |
|
||||||
|
| TLS errors on corporate network | Try `--skip-tls` (not for production) |
|
||||||
|
|
||||||
|
## 13. Next Steps
|
||||||
|
- Update docs and run through Quick Start using your modified CLI
|
||||||
|
- Open a PR when satisfied
|
||||||
|
- (Optional) Tag a release once changes land in `main`
|
||||||
|
|
||||||
|
---
|
||||||
|
Feel free to expand this guide with additional local workflows (debugging, profiling, test automation) as the project matures.
|
||||||
19
docs/toc.yml
19
docs/toc.yml
@@ -1,10 +1,17 @@
|
|||||||
|
# Home page
|
||||||
- name: Home
|
- name: Home
|
||||||
href: index.md
|
href: index.md
|
||||||
- name: Installation
|
|
||||||
|
# Getting started section
|
||||||
|
- name: Getting Started
|
||||||
|
items:
|
||||||
|
- name: Installation
|
||||||
href: installation.md
|
href: installation.md
|
||||||
- name: Quick Start
|
- name: Quick Start
|
||||||
href: quickstart.md
|
href: quickstart.md
|
||||||
- name: Contributing
|
|
||||||
href: CONTRIBUTING.md
|
# Development workflows
|
||||||
- name: Support
|
- name: Development
|
||||||
href: SUPPORT.md
|
items:
|
||||||
|
- name: Local Development
|
||||||
|
href: local-development.md
|
||||||
|
|||||||
@@ -635,6 +635,67 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, is_curr
|
|||||||
return project_path
|
return project_path
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = None) -> None:
|
||||||
|
"""Ensure POSIX .sh scripts in the project scripts directory have execute bits (no-op on Windows)."""
|
||||||
|
if os.name == "nt":
|
||||||
|
return # Windows: skip silently
|
||||||
|
scripts_dir = project_path / "scripts"
|
||||||
|
if not scripts_dir.is_dir():
|
||||||
|
return
|
||||||
|
failures: list[str] = []
|
||||||
|
updated = 0
|
||||||
|
for script in scripts_dir.glob("*.sh"):
|
||||||
|
try:
|
||||||
|
# Skip symlinks
|
||||||
|
if script.is_symlink():
|
||||||
|
continue
|
||||||
|
# Must be a regular file
|
||||||
|
if not script.is_file():
|
||||||
|
continue
|
||||||
|
# Quick shebang check
|
||||||
|
try:
|
||||||
|
with script.open("rb") as f:
|
||||||
|
first_two = f.read(2)
|
||||||
|
if first_two != b"#!":
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
st = script.stat()
|
||||||
|
mode = st.st_mode
|
||||||
|
# If already any execute bit set, skip
|
||||||
|
if mode & 0o111:
|
||||||
|
continue
|
||||||
|
# Only add execute bits that correspond to existing read bits
|
||||||
|
new_mode = mode
|
||||||
|
if mode & 0o400: # owner read
|
||||||
|
new_mode |= 0o100
|
||||||
|
if mode & 0o040: # group read
|
||||||
|
new_mode |= 0o010
|
||||||
|
if mode & 0o004: # other read
|
||||||
|
new_mode |= 0o001
|
||||||
|
# Fallback: ensure at least owner execute
|
||||||
|
if not (new_mode & 0o100):
|
||||||
|
new_mode |= 0o100
|
||||||
|
os.chmod(script, new_mode)
|
||||||
|
updated += 1
|
||||||
|
except Exception as e:
|
||||||
|
failures.append(f"{script.name}: {e}")
|
||||||
|
if tracker:
|
||||||
|
detail = f"{updated} updated" + (f", {len(failures)} failed" if failures else "")
|
||||||
|
tracker.add("chmod", "Set script permissions")
|
||||||
|
if failures:
|
||||||
|
tracker.error("chmod", detail)
|
||||||
|
else:
|
||||||
|
tracker.complete("chmod", detail)
|
||||||
|
else:
|
||||||
|
if updated:
|
||||||
|
console.print(f"[cyan]Updated execute permissions on {updated} script(s)[/cyan]")
|
||||||
|
if failures:
|
||||||
|
console.print("[yellow]Some scripts could not be updated:[/yellow]")
|
||||||
|
for f in failures:
|
||||||
|
console.print(f" - {f}")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def init(
|
def init(
|
||||||
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
|
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"),
|
||||||
@@ -761,6 +822,7 @@ def init(
|
|||||||
("extract", "Extract template"),
|
("extract", "Extract template"),
|
||||||
("zip-list", "Archive contents"),
|
("zip-list", "Archive contents"),
|
||||||
("extracted-summary", "Extraction summary"),
|
("extracted-summary", "Extraction summary"),
|
||||||
|
("chmod", "Ensure scripts executable"),
|
||||||
("cleanup", "Cleanup"),
|
("cleanup", "Cleanup"),
|
||||||
("git", "Initialize git repository"),
|
("git", "Initialize git repository"),
|
||||||
("final", "Finalize")
|
("final", "Finalize")
|
||||||
@@ -778,6 +840,9 @@ def init(
|
|||||||
|
|
||||||
download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker, client=local_client)
|
download_and_extract_template(project_path, selected_ai, here, verbose=False, tracker=tracker, client=local_client)
|
||||||
|
|
||||||
|
# Ensure scripts are executable (POSIX)
|
||||||
|
ensure_executable_scripts(project_path, tracker=tracker)
|
||||||
|
|
||||||
# Git step
|
# Git step
|
||||||
if not no_git:
|
if not no_git:
|
||||||
tracker.start("git")
|
tracker.start("git")
|
||||||
|
|||||||
Reference in New Issue
Block a user