Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1370cc9
fix(github-dev): 🐛 remove deprecated github.copilot extension
baxyz Apr 30, 2026
add4a03
feat(github-dev): ✨ add ms-vscode.remote-repositories extension
baxyz Apr 30, 2026
043cc70
chore(auto-header): 🔧 update install script and improve configuration…
baxyz Apr 30, 2026
a70e93e
chore(essential-dev): 🔧 update version and improve editor settings
baxyz Apr 30, 2026
47af77e
chore(CI-CD): 🔧 remove psi-header configuration and templates
baxyz Apr 30, 2026
c979041
feat(dotfiles-sync): ✨ update dotfiles sync feature with new options
baxyz Apr 30, 2026
59b1b6a
docs(CI-CD): 📝 update commit message generation instructions
baxyz Apr 30, 2026
bef7d5e
chore(vite-plus): 🔧 fix missing newline at end of file
baxyz Apr 30, 2026
126f1ac
docs(dotfiles-sync): 📝 add migration guide for removed feature
baxyz Apr 30, 2026
9def494
chore(CI-CD): 🔧 remove commit message generation instructions
baxyz Apr 30, 2026
3aa28ef
feat(github-dev): ✨ add shared Copilot Chat commit-message instruction
baxyz Apr 30, 2026
eb65013
feat(github-dev): ✨ update shared Copilot Chat instructions for commi…
baxyz Apr 30, 2026
a026a5c
feat(CI-CD): ✨ enhance auto-release workflow for feature detection
baxyz Apr 30, 2026
b02e99a
docs(CI-CD): 📝 update commit message guidelines with detailed emoji u…
baxyz Apr 30, 2026
e93156b
fix(auto-header): 🐛 guard getent under set -e for non-standard users
baxyz Apr 30, 2026
b646d66
docs(dotfiles-sync): 📝 fix copy-if-absent wording for gh config.yml
baxyz Apr 30, 2026
03a9fe7
docs(CI-CD): 📝 clarify release workflow relies on GHCR idempotence
baxyz Apr 30, 2026
3494f90
docs(CI-CD): 📝 sync AGENTS.md scopes and github-dev description
baxyz Apr 30, 2026
384e8ee
feat(auto-header): ✨ implement custom headerType with placeholders
baxyz Apr 30, 2026
4c61f2a
feat(dotfiles-sync): 🔒️ stop bind-mounting gh hosts.yml; remove syncG…
baxyz Apr 30, 2026
95e02bd
test(dotfiles-sync): ✅ cover opt-in flags, hosts.yml exclusion and co…
baxyz Apr 30, 2026
a87571e
fix(CI-CD): 💚 fix bad version increase on auto-header and dotfiles-sync
baxyz Apr 30, 2026
98e1984
fix(CI-CD): 💚 fix auto-header user mismatch and dotfiles-sync mount s…
baxyz Apr 30, 2026
80f60b3
test(dotfiles-sync): ✅ run copy-if-absent test in sandbox without tou…
baxyz Apr 30, 2026
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
11 changes: 10 additions & 1 deletion .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,16 @@ jobs:

- name: Create mount sources for dotfiles-sync
if: matrix.features == 'dotfiles-sync'
run: mkdir -p ~/.ssh ~/.gnupg && touch ~/.gitconfig ~/.npmrc
# All bind sources declared in src/dotfiles-sync/devcontainer-feature.json
# must exist on the host or `docker run` aborts before the test runs.
run: |
mkdir -p ~/.ssh ~/.gnupg \
~/.config/git ~/.config/gh ~/.config/pnpm ~/.config/pip \
~/.cargo ~/.aws ~/.kube ~/.docker
touch ~/.gitconfig ~/.gitignore_global ~/.npmrc ~/.yarnrc.yml \
~/.config/pnpm/rc ~/.config/gh/config.yml \
~/.cargo/config.toml ~/.config/pip/pip.conf \
~/.aws/config ~/.kube/config ~/.docker/config.json

- name: "Test feature '${{ matrix.features }}' against '${{ matrix.baseImage }}'"
run: devcontainer features test --features ${{ matrix.features }} --base-image
Expand Down
131 changes: 115 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,92 @@
name: "Release"

# Auto-release: when a PR is merged into main, detect every feature whose
# `version` field changed in `src/<feature>/devcontainer-feature.json`. The
# `devcontainers/action` step always scans the full `./src` tree, but GHCR
# publishing is idempotent: only versions not already present on GHCR are
# pushed, so unchanged features are effectively skipped at the registry level.
# This workflow uses the diff to (1) skip the whole job when nothing was
# bumped, (2) create per-feature git tags `<feature>-v<version>` for the
# bumped ones, and (3) dispatch one website rebuild event per bumped feature.

on:
workflow_dispatch:
inputs:
force-all:
description: "Republish ALL features regardless of version diff (recovery only)"
type: boolean
default: false
push:
tags:
- "v*"
branches:
- main
paths:
- "src/*/devcontainer-feature.json"

jobs:
deploy:
if: ${{ github.ref_type == 'tag'}}
detect:
runs-on: ubuntu-latest
permissions:
contents: write # Need write permission to create feature tags
packages: write
outputs:
changed: ${{ steps.diff.outputs.changed }}
count: ${{ steps.diff.outputs.count }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 2 # need previous commit for diff (push) — workflow_dispatch falls back to "all"

- name: Detect features with bumped version
id: diff
shell: bash
env:
FORCE_ALL: ${{ github.event_name == 'workflow_dispatch' && inputs.force-all }}
run: |
set -euo pipefail
changed_json='[]'

if [ "${FORCE_ALL}" = "true" ]; then
echo "::notice::workflow_dispatch with force-all=true → republishing every feature"
mapfile -t files < <(ls src/*/devcontainer-feature.json 2>/dev/null || true)
else
# All feature manifests touched by the merge commit.
mapfile -t files < <(git diff --name-only HEAD~1..HEAD -- 'src/*/devcontainer-feature.json' || true)
fi
Comment thread
baxyz marked this conversation as resolved.

for f in "${files[@]}"; do
[ -f "$f" ] || continue
name=$(jq -r '.id' "$f")
new_version=$(jq -r '.version' "$f")

if [ "${FORCE_ALL}" = "true" ]; then
old_version="<forced>"
else
# Best-effort previous version (file may not exist on previous commit → new feature)
old_version=$(git show "HEAD~1:$f" 2>/dev/null | jq -r '.version' 2>/dev/null || echo "")
fi

if [ "${FORCE_ALL}" != "true" ] && [ "$old_version" = "$new_version" ]; then
echo "→ $name unchanged ($new_version), skipping"
continue
fi

echo "→ $name: ${old_version:-<new>} → $new_version"
changed_json=$(jq -c --arg n "$name" --arg v "$new_version" '. + [{name:$n, version:$v}]' <<< "$changed_json")
done

count=$(jq 'length' <<< "$changed_json")
echo "count=$count" >> "$GITHUB_OUTPUT"
echo "changed=$changed_json" >> "$GITHUB_OUTPUT"
echo "Detected $count feature(s) to publish."

release:
needs: detect
if: ${{ needs.detect.outputs.count != '0' }}
runs-on: ubuntu-latest
permissions:
contents: write # tags
packages: write # GHCR
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Fetch full history for proper git operations
fetch-depth: 0

- name: Configure Git
run: |
Expand All @@ -31,16 +100,33 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Publish Features
- name: Publish features
uses: devcontainers/action@v1
with:
publish-features: "true"
base-path-to-features: "./src"
generate-docs: "true"
Comment thread
baxyz marked this conversation as resolved.

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create per-feature git tags
env:
CHANGED: ${{ needs.detect.outputs.changed }}
run: |
set -euo pipefail
echo "$CHANGED" | jq -c '.[]' | while read -r row; do
name=$(jq -r '.name' <<< "$row")
version=$(jq -r '.version' <<< "$row")
tag="${name}-v${version}"
if git rev-parse -q --verify "refs/tags/${tag}" > /dev/null; then
echo "→ tag ${tag} already exists, skipping"
continue
fi
git tag -a "${tag}" -m "Release ${name} v${version}"
git push origin "${tag}"
echo "→ created tag ${tag}"
done

- name: Get Trigganator token
id: trigganator
uses: actions/create-github-app-token@v3
Expand All @@ -53,9 +139,22 @@ jobs:

- name: Trigger website update
continue-on-error: true
uses: peter-evans/repository-dispatch@v4
with:
token: ${{ steps.trigganator.outputs.token }}
repository: helpers4/website
event-type: devcontainer-release
client-payload: "{\"version\": \"${{ github.ref_name }}\"}"
env:
CHANGED: ${{ needs.detect.outputs.changed }}
GH_TOKEN: ${{ steps.trigganator.outputs.token }}
run: |
set -euo pipefail
# One dispatch per feature so the website workflow can react per-feature.
echo "$CHANGED" | jq -c '.[]' | while read -r row; do
name=$(jq -r '.name' <<< "$row")
version=$(jq -r '.version' <<< "$row")
payload=$(jq -nc --arg n "$name" --arg v "$version" --arg sha "${{ github.sha }}" \
'{feature:$n, version:$v, sha:$sha, source:"devcontainer-release"}')
echo "→ dispatch website: $name v$version"
gh api \
-X POST \
-H "Accept: application/vnd.github+json" \
"/repos/helpers4/website/dispatches" \
-f event_type=devcontainer-release \
--raw-field "client_payload=$payload" || echo "⚠️ dispatch failed for $name"
done
65 changes: 3 additions & 62 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,75 +1,16 @@
{
"psi-header.config": {
"author": "baxyz",
"authorEmail": "baxy@etik.com",
"license": "LGPL-3.0-or-later",
"company": "",
"copyrightHolder": "baxyz",
"forceToTop": true
},
"psi-header.templates": [
{
"language": "typescript",
"template": [
"This file is part of helpers4.",
"Copyright (C) <<yeartoyear>> <<copyrightHolder>>",
"SPDX-License-Identifier: <<spdxid>>",
]
},
{
"language": "javascript",
"template": [
"This file is part of helpers4.",
"Copyright (C) <<yeartoyear>> <<copyrightHolder>>",
"SPDX-License-Identifier: <<spdxid>>",
]
}
],
"psi-header.changes-tracking": {
"isActive": true,
"modAuthor": "baxyz",
"modDate": " - modDate",
"modDateFormat": "dd/MM/yyyy",
"include": [
"typescript",
"javascript"
],
"exclude": [
"plaintext"
]
},
"psi-header.lang-config": [
{
"language": "typescript",
"begin": "/**",
"prefix": " * ",
"end": " */",
"blankLinesAfter": 1
},
{
"language": "javascript",
"begin": "/**",
"prefix": " * ",
"end": " */",
"blankLinesAfter": 1
}
],
"conventionalCommits.scopes": [
"angular-dev",
"auto-header",
"dotfiles-sync",
"essential-dev",
"git-absorb",
"local-mounts",
"github-dev",
"package-auto-install",
"peon-ping",
"shell-history-per-project",
"typescript-dev",
"vite-plus",
Comment thread
baxyz marked this conversation as resolved.
"CI-CD"
],
"github.copilot.chat.commitMessageGeneration.instructions": [
{
"text": "Generate commit messages in English only. Use Conventional Commits format with a gitmoji between the scope and the description: `<type>(<scope>): <emoji> <description>`. The scope is optional but when used MUST be one of: angular-dev, auto-header, essential-dev, git-absorb, local-mounts, package-auto-install, peon-ping, shell-history-per-project, typescript-dev, vite-plus, CI-CD. Types and their emoji: feat → ✨, fix → 🐛, docs → 📝, refactor → ♻️, test → ✅, chore → 🔧, perf → 🚀, style → 💄, ci → 👷, build → 📦, revert → ⏪. Always include the emoji. Keep the description short (≤72 chars), lowercase, imperative mood, no period at the end. If there are multiple logical changes, use a bullet list in the body. Examples: `feat(git-absorb): ✨ add version selection option`, `fix(local-mounts): 🐛 fix symlink creation`, `docs(typescript-dev): 📝 update README`."
}
]
}
}
40 changes: 21 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,25 @@ Follow [Conventional Commits](https://www.conventionalcommits.org/) with a gitmo

**Scopes:** angular-dev, auto-header, dotfiles-sync, essential-dev, git-absorb, github-dev, package-auto-install, peon-ping, shell-history-per-project, typescript-dev, vite-plus, CI-CD

| Emoji | Type | Description |
|-------|------|-------------|
| ✨ | feat | New feature |
| 🐛 | fix | Bug fix |
| 📝 | docs | Documentation |
| ♻️ | refactor | Code refactoring |
| ✅ | test | Tests |
| 🔧 | chore | Maintenance |
| 🚀 | perf | Performance |
| 💄 | style | Code style |
| 👷 | ci | CI/CD |
| 📦 | build | Build system |
| ⏪ | revert | Revert |
| Type | Primary | Alternatives (gitmoji.dev) | When to use |
|------|---------|---------------------------|-------------|
| feat | ✨ | 🚸 UX, ♿️ a11y, 🌐 i18n, 💬 text/literals | New feature |
| fix | 🐛 | 🚑️ hotfix, 🔒️ security, 🩹 trivial, 🥅 errors, 🚨 warnings, ✏️ typo | Bug fix |
| docs | 📝 | 💡 source comments, 📄 license | Documentation |
| refactor | ♻️ | 🎨 structure, 🔥 remove code, ⚰️ dead code, 🚚 move/rename | Code refactoring |
| test | ✅ | 🧪 failing test, 💚 fix CI test | Tests |
| chore | 🔧 | 🙈 gitignore, 🔖 tag/release, 📌 pin deps, 🩺 healthcheck | Maintenance |
| perf | ⚡️ | — | Performance |
| style | 💄 | 🎨 code style | Code style / UI |
| ci | 👷 | 💚 fix CI | CI/CD |
| build | 📦️ | ➕ add dep, ➖ remove dep, ⬆️ upgrade dep, ⬇️ downgrade dep | Build system |
| revert | ⏪️ | — | Revert |

> Pick the **most specific** gitmoji that matches the change. The primary is the safe default; reach for an alternative when it adds real signal. Full list: https://gitmoji.dev

**Examples:**
- `feat(git-absorb): ✨ add version selection option`
- `fix(local-mounts): 🐛 fix symlink creation`
- `fix(dotfiles-sync): 🐛 fix symlink creation`
- `docs(typescript-dev): 📝 update README`
- `chore(CI-CD): 🔧 update dependencies`

Expand All @@ -56,7 +58,7 @@ devcontainer/
│ ├── package-auto-install/ # Automatic package installation
│ ├── auto-header/ # Automatic LGPL-3.0 file headers
│ ├── git-absorb/ # git-absorb tool installation
│ ├── local-mounts/ # Mount local Git/SSH/GPG/npm config
│ ├── local-mounts/ # REMOVED — README-only redirect to dotfiles-sync
│ ├── peon-ping/ # Health check endpoint
│ └── shell-history-per-project/ # Persistent shell history per project
├── test/ # One test.sh per feature
Expand Down Expand Up @@ -110,14 +112,14 @@ devcontainer features test . # Test all
| Feature | Version | Description | Dependencies |
|---------|---------|-------------|--------------|
| essential-dev | 1.0.2 | Git visualization, editor enhancements, Markdown | — |
| github-dev | 1.0.0 | GitHub CLI (gh), Copilot, PR & Issues, Actions, RemoteHub | — |
| github-dev | 1.0.3 | GitHub CLI (gh), Copilot Chat, PR & Issues, GitHub Actions, RemoteHub, Remote Repositories, shared Copilot Chat instructions for commit messages and PR titles/descriptions (reads `conventionalCommits.scopes` per repo, follows the active repo's own PR template) | — |
| typescript-dev | 1.0.5 | TypeScript/JS dev with import management | essential-dev |
| angular-dev | 1.0.2 | Angular dev, port 4200 forwarding | — |
| vite-plus | | Vite development setup | — |
| vite-plus | 1.0.3 | Vite+ unified CLI (vp), Oxlint/Oxfmt, Vitest, optional system-wide symlink | — |
| package-auto-install | — | Auto-detect and install packages | — |
| auto-header | — | LGPL-3.0 license headers | — |
| git-absorb | 1.0.2 | git-absorb from GitHub releases | — |
| dotfiles-sync | 1.0.0 | Sync local Git/SSH/GPG/npm config — macOS, Linux, WSL, Codespaces | — |
| dotfiles-sync | 1.1.0 | Sync local Git/SSH/GPG/npm/gh/cargo/pip/yarn/pnpm config — opt-in cloud creds (AWS, kube, Docker, gh OAuth) — macOS, Linux, WSL, Codespaces | — |
Comment thread
baxyz marked this conversation as resolved.
| peon-ping | — | Health check endpoint | — |
| shell-history-per-project | 1.0.2 | Persistent shell history (zsh/bash/fish) | — |

Expand Down Expand Up @@ -155,7 +157,7 @@ devcontainer features test . # Test all
"ghcr.io/helpers4/devcontainer/vite-plus:1": {},
"ghcr.io/helpers4/devcontainer/package-auto-install:1": {},
"ghcr.io/helpers4/devcontainer/git-absorb:1": {},
"ghcr.io/helpers4/devcontainer/local-mounts:1": {},
"ghcr.io/helpers4/devcontainer/dotfiles-sync:1": {},
"ghcr.io/helpers4/devcontainer/shell-history-per-project:1": {}
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/auto-header/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ This command:
| `company` | string | *optional* | Company or organization name |
| `contributors` | string | *optional* | Comma-separated contributor names |
| `sinceYear` | string | *current year* | Copyright start year |
| `customHeaderLines` | string | *required for custom* | Custom header lines (linefeed separated) |
| `customHeaderLines` | string | *required for custom* | Custom header lines (separated by literal `\n`). Supports placeholders: `{{projectName}}`, `{{license}}`, `{{company}}`, `{{contributors}}`, `{{sinceYear}}`, `{{currentYear}}`, `{{copyrightYears}}`, `{{author}}` (= company or projectName). |

## Examples

Expand Down Expand Up @@ -124,7 +124,10 @@ This command:
"ghcr.io/helpers4/devcontainer/auto-header:latest": {
"headerType": "custom",
"projectName": "my-lib",
"customHeaderLines": "/*!\n * @file\n * @description Custom file header\n * @version 1.0.0\n */"
"license": "LGPL-3.0-or-later",
"company": "ACME",
"sinceYear": "2024",
"customHeaderLines": "/*!\n * @file part of {{projectName}}\n * @copyright Copyright (C) {{copyrightYears}} {{author}}\n * @license SPDX-License-Identifier: {{license}}\n */"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/auto-header/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "auto-header",
"version": "1.0.1",
"version": "1.0.2",
"name": "Automatic File Headers",
"description": "Automatically configures VS Code file headers with customizable templates based on project, license, company, and contributors information.",
"documentationURL": "https://github.com/helpers4/devcontainer/tree/main/features/auto-header",
Expand Down Expand Up @@ -52,4 +52,4 @@
]
}
}
}
}
Loading
Loading