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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: CI

on:
push:
branches: ["**"]
branches: ["main"]
pull_request:
branches: ["**"]
branches: ["main"]

jobs:
test:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
name: Deploy Jekyll with GitHub Pages dependencies preinstalled
name: Deploy GitHub Pages

on:
workflow_run:
workflows: ["publish"]
workflows: ["Publish to PyPI"]
types: [completed]

# Allows you to run this workflow manually from the Actions tab
Expand All @@ -25,6 +25,7 @@ jobs:
# Build job
build:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Publish
name: Publish to PyPi

on:
push:
Expand Down
38 changes: 38 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,41 @@ pyrightconfig.json

# End of https://www.toptal.com/developers/gitignore/api/python

# Created by https://www.toptal.com/developers/gitignore/api/macos
# Edit at https://www.toptal.com/developers/gitignore?templates=macos

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### macOS Patch ###
# iCloud generated files
*.icloud

# End of https://www.toptal.com/developers/gitignore/api/macos
uv.lock
42 changes: 42 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.PHONY: lint typecheck test all \
changelog changelog-preview changelog-bump release

# ── CI / quality ────────────────────────────────────────────────────────────

lint:
ruff check .

typecheck:
mypy src/

test:
pytest --cov=src --cov-report=term-missing

all: lint typecheck test

# ── Changelog / release ──────────────────────────────────────────────────────

# Preview what the next changelog entry will look like (dry-run, no files changed)
changelog-preview:
git cliff --unreleased --bump

# Show what the next version number will be
changelog-bump:
git cliff --bumped-version

# Prepend new entries to CHANGELOG.md for the next release
changelog:
$(eval VERSION := $(shell git cliff --bumped-version 2>/dev/null))
touch CHANGELOG.md
git cliff --unreleased --prepend CHANGELOG.md --bump
@echo "CHANGELOG.md updated to $(VERSION). Review, then run: make release"

# Full release: update changelog, commit, tag, push
release:
$(eval VERSION := $(shell git cliff --bumped-version 2>/dev/null))
git add CHANGELOG.md
@git commit -m "chore: release $(VERSION)" || { echo "Error: CHANGELOG.md unchanged. Run 'make changelog' first."; exit 1; }
git tag $(VERSION)
git push origin main
git push origin $(VERSION)
@echo "Released $(VERSION)"
91 changes: 50 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Reusable Dev Containers for any project — without modifying the repository.

---

![](https://github.com/dacrystal/dev-code/raw/main/demo/demo.gif "Demo gif")
![](assets/demo.gif "Demo gif")

`devcode` is a CLI that opens any project in VS Code Dev Containers using reusable, local templates.

Expand Down Expand Up @@ -50,11 +50,11 @@ Typical Dev Container workflows involve:
# Install
pip install dev-code

# Create default template
devcode init
# Open a project (auto-detects template from container history, or uses default)
devcode open ~/projects/my-app

# Open a project
devcode open dev-code ~/projects/my-app
# Open with an explicit template
devcode open ~/projects/my-app dev-code

# Reopen projects later
devcode ps -a -i
Expand All @@ -81,14 +81,7 @@ Default location:
~/.local/share/dev-code/templates/
```

Override search paths:

```bash
DEVCODE_TEMPLATE_PATH=/my/templates:/team/templates
```

* The first path is used for writes
* Additional paths are read-only
Override search paths via `settings.json` (see [Configuration](#configuration)).

---

Expand All @@ -105,25 +98,29 @@ DEVCODE_TEMPLATE_PATH=/my/templates:/team/templates
### devcode open

```bash
devcode open <template> <path> [options]
devcode open <path> [template] [options]
```

Open a project using a template.
Open a project in VS Code using a devcontainer template.

#### Arguments

* `<template>`
* `<path>` — Project directory (must exist)

* `[template]` *(optional)*

* Template name, or
* Path to a `devcontainer.json`, or
* Path to a directory containing it

Paths must start with `./`, `../`, `/`, or `~/`.

If both a template and directory match, the template takes precedence and a warning is shown.
If both a template name and a local directory match, the template takes precedence and a warning is shown.

* `<path>`
Project directory
**If omitted**, devcode auto-detects the template in this order:
1. Most recently running container for this project path (uses its stored config)
2. Most recently stopped container for this project path
3. `default_template` from `settings.json` (error if not set)

#### Options

Expand All @@ -135,16 +132,6 @@ Open a project using a template.

---

### devcode init

```bash
devcode init
```

Creates the default template.

---

### devcode new

```bash
Expand Down Expand Up @@ -229,32 +216,54 @@ eval "$(devcode completion bash)"
## Typical Workflow

```bash
devcode init
devcode new python-dev
devcode edit python-dev
devcode open python-dev ~/projects/my-app
devcode open ~/projects/my-app python-dev
```

---

## Template System
## Configuration

### Default Location
devcode reads `settings.json` from:

```
~/.local/share/dev-code/templates/
~/.config/dev-code/settings.json
```

### Custom Paths
Override the config directory:

```bash
DEVCODE_TEMPLATE_PATH=$HOME/my/templates:/team/shared/templates
DEVCODE_CONF_DIR=/custom/path devcode open ~/projects/my-app
```

The file is created automatically with defaults on first run.

### settings.json

```json
{
"template_sources": ["~/.local/share/dev-code/templates"],
"default_template": "dev-code"
}
```

Resolution order:
| Key | Description |
| --- | --- |
| `template_sources` | Ordered list of template directories. First is the write target; rest are read-only. |
| `default_template` | Template used when `devcode open` is called without a template argument and no container history is found. Error if unset. |

---

## Template System

### Default Location

```
~/.local/share/dev-code/templates/
```

1. First directory is the write target
2. Remaining directories are used for lookup
Configure additional paths via `template_sources` in `settings.json`.

---

Expand Down Expand Up @@ -348,8 +357,8 @@ Lists containers and allows reopening projects interactively.

## Internal Flow

1. Resolve template
2. Resolve project path
1. Validate project path (must exist)
2. Resolve template (explicit → container history → settings default)
3. Launch VS Code Dev Container
4. Apply file injection rules

Expand Down
Binary file added assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# git-cliff configuration
# https://git-cliff.org/docs/configuration

[changelog]
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [Unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
trim = true
render_always = true
postprocessors = [
{ pattern = "<REPO>", replace = "https://github.com/dacrystal/dev-code" },
]

[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))" },
]
commit_parsers = [
{ message = "^feat", group = "Added" },
{ message = "^fix", group = "Fixed" },
{ message = "^refactor", group = "Changed" },
{ message = "^perf", group = "Changed" },
{ message = "^revert", group = "Fixed" },
{ message = "^docs|^chore|^ci|^style|^test", skip = true },
]
filter_commits = true
protect_breaking_commits = true
sort_commits = "oldest"
topo_order = false
skip_tags = "v*.*.*.post"
32 changes: 20 additions & 12 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
[build-system]
requires = ["hatchling", "hatch-vcs"]
requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"

[project]
name = "dev-code"
dynamic = ["version"]
dynamic = ["version", "readme"]
description = "Project · editor · container — simplified"
readme = "README.md"
requires-python = ">=3.10"
dependencies = []
authors = [
{name = "Nasser Alansari", email = "dacrystal@users.noreply.github.com"},
]
dependencies = ["click>=8.0"]
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
Expand All @@ -24,7 +26,7 @@ classifiers = [
"Issues" = "https://github.com/dacrystal/dev-code/issues"

[project.scripts]
devcode = "devcode:main"
devcode = "devcode:cli"

[tool.hatch.version]
source = "vcs"
Expand All @@ -34,12 +36,18 @@ force-include = {"src/devcode.py" = "devcode.py", "src/templates" = "dev_code_te

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]

[dependency-groups]
dev = [
"pytest>=7.0.1",
"pyfiglet>=1.0.4",
"pytest-cov>=4.0",
"tox>=4.0",
"pyyaml>=6.0",
]
test = ["pytest>=7", "pytest-cov>=4"]
dev = ["tox>=4", "pyyaml>=6", "git-cliff>=2"]

[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"

[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"

[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\(assets/'
replacement = "(https://raw.githubusercontent.com/dacrystal/dev-code/main/assets/"
Loading
Loading