Skip to content

Commit 573c9dd

Browse files
committed
feat(scadm): add vscode command for extension management (#131)
Adds a new `vscode` command to scadm for managing VS Code extensions and configuration. - New command: `scadm vscode --openscad` to install and configure the OpenSCAD extension - Automatically installs `Leathong.openscad-language-support` extension - Merges VS Code settings intelligently (preserves existing configurations) - Settings are sorted alphabetically for consistency - Cross-platform support (Windows/Linux/macOS) - Clean help output - each command shows only relevant options ⚠️ Commands now require explicit subcommands: - Old: `scadm` → New: `scadm install` - Old: `scadm --check` → New: `scadm install --check` - Running `scadm` without arguments now shows help This is acceptable since we're pre-1.0.0. - All documentation (README files, CONTRIBUTING.md) - GitHub workflows (pre-commit, validate-models) - Copilot instructions - ✅ `scadm` shows help - ✅ `scadm install --check` works - ✅ `scadm vscode --openscad` installs extension and configures settings - ✅ Settings merge correctly - ✅ Pre-commit hooks pass
1 parent 3d60960 commit 573c9dd

File tree

8 files changed

+231
-42
lines changed

8 files changed

+231
-42
lines changed

.github/copilot-instructions.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ HomeRacker is a modular 3D-printable rack-building system. Core components use p
3232
3. **Ask before proceeding** if requirements conflict with best practices
3333
4. **Provide outline** before implementation for confirmation
3434
5. **Make the change** and immediately test it - do NOT announce completion before testing
35-
6. **Run pre-commit hooks** to catch formatting/linting issues before commit. Fix any issues found (no ignores allowed).
36-
7. **On errors**: Step back, check docs, ask user if stuck—don't iterate blindly
35+
6. **Update** existing README.md files (project root, module-specific) and CONTRIBUTING.md and create new ones where applicable
36+
7. **Run pre-commit hooks** to catch formatting/linting issues before commit. Fix any issues found (no ignores allowed).
37+
8. **On errors**: Step back, check docs, ask user if stuck—don't iterate blindly
3738

3839
## OpenSCAD Guidelines
3940
- Use BOSL2 for complex geometry

.github/workflows/pre-commit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
- name: Install OpenSCAD and dependencies
2828
run: |
2929
pip install -e cmd/scadm
30-
scadm
30+
scadm install
3131
3232
- name: Make scripts executable
3333
run: chmod +x cmd/export/export-core-models.sh .githooks/makerworld-export

.github/workflows/validate-models.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
- name: Install OpenSCAD and dependencies
2929
run: |
3030
pip install -e cmd/scadm
31-
scadm
31+
scadm install
3232
3333
- name: Validate models
3434
run: |

CONTRIBUTING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ cd homeracker
1313
pip install -e cmd/scadm
1414

1515
# Install OpenSCAD (Windows/Linux/macOS) + Dependencies
16-
scadm
16+
scadm install
1717

1818
# Optional (VSCode Integration)
19-
./cmd/setup/install-vscode-openscad.sh
19+
scadm vscode --openscad
2020

2121
# Verify installation
2222
./cmd/test/openscad-render.sh
@@ -198,6 +198,7 @@ cmd/ # Command-line utilities (setup, test, lib)
198198

199199
- [Open an issue](https://github.com/kellervater/homeracker/issues) for bugs/features
200200
- [Start a discussion](https://github.com/kellervater/homeracker/discussions) for questions
201+
- See [scadm documentation](cmd/scadm/README.md) for dependency manager details
201202

202203
## 🔔 Discord Integration
203204

cmd/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ For installation, use the **scadm** package (see `cmd/scadm/` or install via `pi
1111
pip install -e cmd/scadm
1212

1313
# Install OpenSCAD + dependencies from scadm.json
14-
scadm
14+
scadm install
1515

1616
# Check if updates are available
17-
scadm --check
17+
scadm install --check
1818

1919
# Run smoke test - validates the current openscad installation against local models
2020
./cmd/test/openscad-render.sh models/core/parts/connector.scad

cmd/scadm/README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pip install scadm
3737
### 2. Install OpenSCAD and dependencies
3838

3939
```bash
40-
scadm
40+
scadm install
4141
```
4242

4343
This will:
@@ -49,8 +49,8 @@ This will:
4949
### Install everything (OpenSCAD + libraries)
5050

5151
```bash
52-
scadm # Install nightly build (default - RECOMMENDED)
53-
scadm --stable # Install stable release (2021.01)
52+
scadm install # Install nightly build (default - RECOMMENDED)
53+
scadm install --stable # Install stable release (2021.01)
5454
```
5555

5656
> [!NOTE]
@@ -59,27 +59,41 @@ scadm --stable # Install stable release (2021.01)
5959
### Check installation status
6060

6161
```bash
62-
scadm --check
62+
scadm install --check
6363
```
6464

6565
### Force reinstall
6666

6767
```bash
68-
scadm --force
68+
scadm install --force
6969
```
7070

7171
### Install only OpenSCAD
7272

7373
```bash
74-
scadm --openscad-only
74+
scadm install --openscad-only
7575
```
7676

7777
### Install only libraries
7878

7979
```bash
80-
scadm --libs-only
80+
scadm install --libs-only
8181
```
8282

83+
### Configure VS Code extensions
84+
85+
```bash
86+
scadm vscode --openscad # Install and configure OpenSCAD extension
87+
```
88+
89+
This will:
90+
- Install the `Leathong.openscad-language-support` extension
91+
- Configure VS Code settings with correct OpenSCAD paths
92+
- Merge with existing settings (preserves unrelated configurations)
93+
94+
> [!NOTE]
95+
> Requires VS Code CLI (`code` command) to be available in PATH. If not found, you'll receive installation instructions.
96+
8397
## Configuration
8498

8599
### `scadm.json` Schema

cmd/scadm/scadm/cli.py

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66

77
from scadm.installer import install_libraries, install_openscad
8+
from scadm.vscode import setup_openscad_extension
89

910
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s", handlers=[logging.StreamHandler()])
1011
logger = logging.getLogger(__name__)
@@ -16,37 +17,62 @@ def main():
1617
prog="scadm",
1718
description="OpenSCAD Dependency Manager - Install OpenSCAD and manage library dependencies",
1819
)
19-
parser.add_argument("--check", action="store_true", help="Check installation status only")
20-
parser.add_argument("--force", action="store_true", help="Force reinstall")
21-
parser.add_argument(
22-
"--stable", action="store_false", dest="nightly", help="Install stable release (2021.01) instead of nightly"
20+
21+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
22+
23+
# Install command
24+
install_parser = subparsers.add_parser("install", help="Install OpenSCAD and libraries")
25+
install_parser.add_argument("--check", action="store_true", help="Check installation status only")
26+
install_parser.add_argument("--force", action="store_true", help="Force reinstall")
27+
install_parser.add_argument(
28+
"--stable",
29+
action="store_false",
30+
dest="nightly",
31+
default=True,
32+
help="Install stable release (2021.01) instead of nightly",
2333
)
24-
parser.add_argument("--openscad-only", action="store_true", help="Install only OpenSCAD binary")
25-
parser.add_argument("--libs-only", action="store_true", help="Install only libraries")
34+
install_parser.add_argument("--openscad-only", action="store_true", help="Install only OpenSCAD binary")
35+
install_parser.add_argument("--libs-only", action="store_true", help="Install only libraries")
36+
37+
# VSCode command
38+
vscode_parser = subparsers.add_parser("vscode", help="Configure VS Code extensions")
39+
vscode_parser.add_argument("--openscad", action="store_true", help="Install and configure OpenSCAD extension")
2640

2741
args = parser.parse_args()
2842

29-
# Set default to nightly (True) unless --stable was specified
30-
if not hasattr(args, "nightly"):
31-
args.nightly = True
32-
33-
success = True
34-
35-
try:
36-
if not args.libs_only:
37-
if not install_openscad(nightly=args.nightly, force=args.force, check_only=args.check):
38-
success = False
39-
if not args.check:
40-
logger.error("OpenSCAD installation failed. Aborting.")
41-
sys.exit(1)
42-
43-
if not args.openscad_only:
44-
if not install_libraries(force=args.force, check_only=args.check):
45-
success = False
46-
except FileNotFoundError as e:
47-
logger.error("%s", e)
48-
logger.error("Create a scadm.json file in your project root to get started.")
49-
sys.exit(1)
43+
# Show help if no command provided
44+
if not args.command:
45+
parser.print_help()
46+
sys.exit(0)
47+
48+
# Handle vscode command
49+
if args.command == "vscode":
50+
if args.openscad:
51+
success = setup_openscad_extension()
52+
sys.exit(0 if success else 1)
53+
else:
54+
vscode_parser.print_help()
55+
sys.exit(0)
56+
57+
# Handle install command
58+
if args.command == "install":
59+
success = True
60+
61+
try:
62+
if not args.libs_only:
63+
if not install_openscad(nightly=args.nightly, force=args.force, check_only=args.check):
64+
success = False
65+
if not args.check:
66+
logger.error("OpenSCAD installation failed. Aborting.")
67+
sys.exit(1)
68+
69+
if not args.openscad_only:
70+
if not install_libraries(force=args.force, check_only=args.check):
71+
success = False
72+
except FileNotFoundError as e:
73+
logger.error("%s", e)
74+
logger.error("Create a scadm.json file in your project root to get started.")
75+
sys.exit(1)
5076

5177
sys.exit(0 if success else 1)
5278

cmd/scadm/scadm/vscode.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
"""VSCode extension management for scadm."""
2+
3+
import json
4+
import logging
5+
import platform
6+
import shutil
7+
import subprocess
8+
from pathlib import Path
9+
10+
from scadm.installer import get_install_paths, get_workspace_root
11+
12+
logger = logging.getLogger(__name__)
13+
14+
EXTENSION_OPENSCAD = "Leathong.openscad-language-support"
15+
16+
17+
def install_extension(extension_id: str) -> bool:
18+
"""Install a VS Code extension.
19+
20+
Args:
21+
extension_id: The extension identifier (e.g., 'publisher.extension').
22+
23+
Returns:
24+
True if installation succeeded, False otherwise.
25+
"""
26+
try:
27+
logger.info("Installing extension %s...", extension_id)
28+
# Use shell=True on Windows to properly find code.cmd
29+
use_shell = platform.system() == "Windows"
30+
subprocess.run(
31+
["code", "--install-extension", extension_id, "--force"],
32+
check=True,
33+
capture_output=True,
34+
text=True,
35+
shell=use_shell,
36+
)
37+
logger.info("Extension %s installed", extension_id)
38+
return True
39+
except FileNotFoundError:
40+
logger.warning("VS Code CLI 'code' command not found")
41+
logger.warning("Install VS Code from: https://code.visualstudio.com/download")
42+
logger.warning("Make sure to enable 'Add to PATH' during installation")
43+
return False
44+
except subprocess.CalledProcessError as e:
45+
logger.error("Failed to install extension %s: %s", extension_id, e.stderr)
46+
return False
47+
48+
49+
def get_openscad_paths(workspace_root: Path) -> tuple[str, str]:
50+
"""Get OpenSCAD executable and library paths for VS Code config.
51+
52+
Args:
53+
workspace_root: Workspace root directory.
54+
55+
Returns:
56+
Tuple of (openscad_path, search_paths) formatted for VS Code settings.
57+
"""
58+
install_dir, libraries_dir = get_install_paths(workspace_root)
59+
60+
system = platform.system()
61+
if system == "Windows":
62+
# Convert to Windows paths with single backslashes (JSON will handle escaping)
63+
openscad_path = str(install_dir / "openscad.exe").replace("/", "\\")
64+
search_paths = str(libraries_dir).replace("/", "\\")
65+
else:
66+
# Linux/macOS: use wrapper script
67+
openscad_path = str(workspace_root / "cmd" / "linux" / "openscad-wrapper.sh")
68+
search_paths = str(libraries_dir)
69+
70+
return openscad_path, search_paths
71+
72+
73+
def update_vscode_settings(workspace_root: Path, openscad: bool = False) -> bool:
74+
"""Update VS Code settings.json with extension configuration.
75+
76+
Args:
77+
workspace_root: Workspace root directory.
78+
openscad: Whether to configure OpenSCAD settings.
79+
80+
Returns:
81+
True if settings were updated successfully, False otherwise.
82+
"""
83+
vscode_dir = workspace_root / ".vscode"
84+
settings_file = vscode_dir / "settings.json"
85+
86+
# Load existing settings or start with empty dict
87+
settings = {}
88+
if settings_file.exists():
89+
try:
90+
with open(settings_file, "r", encoding="utf-8") as f:
91+
settings = json.load(f)
92+
except json.JSONDecodeError:
93+
logger.warning("Invalid JSON in settings.json, will overwrite")
94+
95+
# Update OpenSCAD settings
96+
if openscad:
97+
openscad_path, search_paths = get_openscad_paths(workspace_root)
98+
# Deep merge for files.associations
99+
if "files.associations" not in settings or not isinstance(settings["files.associations"], dict):
100+
settings["files.associations"] = {}
101+
settings["files.associations"]["*.scad"] = "scad"
102+
settings["files.eol"] = "\n"
103+
settings["scad-lsp.launchPath"] = openscad_path
104+
settings["scad-lsp.searchPaths"] = search_paths
105+
106+
# Write settings
107+
vscode_dir.mkdir(parents=True, exist_ok=True)
108+
try:
109+
with open(settings_file, "w", encoding="utf-8") as f:
110+
json.dump(settings, f, indent=2, sort_keys=True)
111+
logger.info("Updated VS Code settings")
112+
return True
113+
except OSError as e:
114+
logger.error("Failed to write settings.json: %s", e)
115+
return False
116+
117+
118+
def setup_openscad_extension() -> bool:
119+
"""Install and configure OpenSCAD extension for VS Code.
120+
121+
Returns:
122+
True if setup succeeded, False otherwise.
123+
"""
124+
# Check VS Code is installed
125+
if not shutil.which("code"):
126+
logger.warning("VS Code CLI 'code' command not found")
127+
logger.warning("Install VS Code from: https://code.visualstudio.com/download")
128+
logger.warning("Make sure to enable 'Add to PATH' during installation")
129+
return False
130+
131+
# Get workspace root
132+
try:
133+
workspace_root = get_workspace_root()
134+
except FileNotFoundError as e:
135+
logger.error("%s", e)
136+
return False
137+
138+
# Install extension
139+
if not install_extension(EXTENSION_OPENSCAD):
140+
return False
141+
142+
# Update settings
143+
if not update_vscode_settings(workspace_root, openscad=True):
144+
return False
145+
146+
logger.info("VS Code configured for OpenSCAD")
147+
return True

0 commit comments

Comments
 (0)