From 7cd9c44a29a12c60b372e6eacca3009ec7f9590b Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Sat, 9 May 2026 20:29:16 +0100 Subject: [PATCH 1/3] docs(plugin-dev): canonical workflow templates for Indigo plugins Add reference version-check.yml + create-release.yml that auto-detect the plugin bundle via find, so the same files work in any plugin repo without edits. Will roll out to all 11 Indigo plugin repos. Bumps to 1.9.2 (docs). Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- docs/plugin-dev/workflows/README.md | 30 ++++++++ docs/plugin-dev/workflows/create-release.yml | 81 ++++++++++++++++++++ docs/plugin-dev/workflows/version-check.yml | 53 +++++++++++++ 5 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 docs/plugin-dev/workflows/README.md create mode 100644 docs/plugin-dev/workflows/create-release.yml create mode 100644 docs/plugin-dev/workflows/version-check.yml diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index d2912ae..2705aca 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,7 @@ "name": "indigo", "source": "./", "description": "Indigo home automation development toolkit \u2014 plugin development, API integration, and control page building", - "version": "1.9.1", + "version": "1.9.2", "repository": "https://github.com/simons-plugins/indigo-claude-plugin", "license": "MIT", "keywords": [ diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 3a10393..cc07d82 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "indigo", - "version": "1.9.1", + "version": "1.9.2", "description": "Indigo home automation development toolkit \u2014 plugin development, API integration, and control page building", "repository": "https://github.com/simons-plugins/indigo-claude-plugin" } diff --git a/docs/plugin-dev/workflows/README.md b/docs/plugin-dev/workflows/README.md new file mode 100644 index 0000000..c3d2ad3 --- /dev/null +++ b/docs/plugin-dev/workflows/README.md @@ -0,0 +1,30 @@ +# Canonical CI workflows for Indigo plugins + +Drop-in GitHub Actions workflows for any Indigo plugin in the workspace. They auto-detect the plugin bundle (`*.indigoPlugin`) so the same files work in every repo without edits. + +## Files + +- [`version-check.yml`](version-check.yml) — runs on PR. Extracts `PluginVersion` from `Info.plist` and fails the check if the resulting tag (`v$VERSION` or bare `$VERSION`) already exists. Enforces the workspace rule that every PR bumps the version. +- [`create-release.yml`](create-release.yml) — runs on push to `main`/`master`. If a tag for the current `PluginVersion` doesn't yet exist, it zips the plugin bundle (excluding `.pyc`, `__pycache__`, IDE files) and creates a GitHub release tagged `v$VERSION` with auto-generated release notes and the zip attached. + +## Conventions + +- **Tag prefix**: `v$VERSION` (e.g. `v2026.0.3`). The version-check is conservative and rejects either prefixed or bare tags clashing with the new version. +- **Plugin bundle detection**: `find . -maxdepth 1 -name "*.indigoPlugin" -type d`. The bundle must live at the repo root. +- **Action versions**: `actions/checkout@v4`, `softprops/action-gh-release@v2`. Both run on Node 20. + +## Usage + +Copy both YAML files to `.github/workflows/` in the target repo. No edits required — the workflows discover the plugin name at runtime. + +```bash +mkdir -p .github/workflows +cp /path/to/indigo-claude-plugin/docs/plugin-dev/workflows/version-check.yml .github/workflows/ +cp /path/to/indigo-claude-plugin/docs/plugin-dev/workflows/create-release.yml .github/workflows/ +``` + +Bump `PluginVersion` in `*.indigoPlugin/Contents/Info.plist`, open a PR. CI will gate the merge on a fresh version; merge to `main` triggers the release. + +## Coexistence with other CI + +These workflows do not interact with linting/test workflows. Repos with `tests.yml`, `test.yml`, `lint.yml`, etc. can keep them — they run as independent jobs. diff --git a/docs/plugin-dev/workflows/create-release.yml b/docs/plugin-dev/workflows/create-release.yml new file mode 100644 index 0000000..da4ad97 --- /dev/null +++ b/docs/plugin-dev/workflows/create-release.yml @@ -0,0 +1,81 @@ +name: Create Release + +on: + push: + branches: + - main + - master + +jobs: + create-release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine plugin name + id: get_plugin_name + run: | + PLUGIN_BUNDLE=$(find . -maxdepth 1 -name "*.indigoPlugin" -type d | head -1) + if [ -z "$PLUGIN_BUNDLE" ]; then + echo "Error: No .indigoPlugin directory found in repository root." >&2 + exit 1 + fi + PLUGIN_NAME=$(basename "$PLUGIN_BUNDLE" .indigoPlugin) + echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT + echo "Detected plugin name: $PLUGIN_NAME" + + - name: Extract version from Info.plist + id: get_version + run: | + PLUGIN_NAME="${{ steps.get_plugin_name.outputs.plugin_name }}" + VERSION=$(grep -A1 'PluginVersion' "${PLUGIN_NAME}.indigoPlugin/Contents/Info.plist" | grep '' | sed 's/.*\(.*\)<\/string>.*/\1/') + if [ -z "$VERSION" ]; then + echo "Error: Could not extract PluginVersion from Info.plist." >&2 + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Check if release already exists + id: check_release + run: | + VERSION="${{ steps.get_version.outputs.version }}" + if git rev-parse "v$VERSION" >/dev/null 2>&1; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "Release v$VERSION already exists, skipping." + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "Release v$VERSION does not exist, will create." + fi + + - name: Create plugin bundle zip + if: steps.check_release.outputs.exists == 'false' + run: | + PLUGIN_NAME="${{ steps.get_plugin_name.outputs.plugin_name }}" + zip -r "${PLUGIN_NAME}.indigoPlugin.zip" "${PLUGIN_NAME}.indigoPlugin" \ + -x "*.pyc" \ + -x "*/__pycache__" \ + -x "*/__pycache__/*" \ + -x "*.sublime-project" \ + -x "*.sublime-workspace" \ + -x "*/.idea" \ + -x "*/.idea/*" \ + -x "*.bbproject" \ + -x "*.bbproject/*" + echo "Created ${PLUGIN_NAME}.indigoPlugin.zip" + + - name: Create GitHub Release + if: steps.check_release.outputs.exists == 'false' + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.get_version.outputs.version }} + name: Release v${{ steps.get_version.outputs.version }} + generate_release_notes: true + draft: false + prerelease: false + files: ${{ steps.get_plugin_name.outputs.plugin_name }}.indigoPlugin.zip diff --git a/docs/plugin-dev/workflows/version-check.yml b/docs/plugin-dev/workflows/version-check.yml new file mode 100644 index 0000000..ebfa43c --- /dev/null +++ b/docs/plugin-dev/workflows/version-check.yml @@ -0,0 +1,53 @@ +name: Version Check + +on: + pull_request: + branches: + - main + - master + +jobs: + check-version: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine plugin name + id: get_plugin_name + run: | + PLUGIN_BUNDLE=$(find . -maxdepth 1 -name "*.indigoPlugin" -type d | head -1) + if [ -z "$PLUGIN_BUNDLE" ]; then + echo "Error: No .indigoPlugin directory found in repository root." >&2 + exit 1 + fi + PLUGIN_NAME=$(basename "$PLUGIN_BUNDLE" .indigoPlugin) + echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT + echo "Detected plugin name: $PLUGIN_NAME" + + - name: Extract version from Info.plist + id: get_version + run: | + PLUGIN_NAME="${{ steps.get_plugin_name.outputs.plugin_name }}" + VERSION=$(grep -A1 'PluginVersion' "${PLUGIN_NAME}.indigoPlugin/Contents/Info.plist" | grep '' | sed 's/.*\(.*\)<\/string>.*/\1/') + if [ -z "$VERSION" ]; then + echo "Error: Could not extract PluginVersion from Info.plist." >&2 + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Check if tag already exists + run: | + VERSION="${{ steps.get_version.outputs.version }}" + if git rev-parse "v$VERSION" >/dev/null 2>&1; then + echo "::error::Version v$VERSION already exists as a git tag. Please update the PluginVersion in Info.plist." + exit 1 + fi + if git rev-parse "$VERSION" >/dev/null 2>&1; then + echo "::error::Version $VERSION already exists as a git tag. Please update the PluginVersion in Info.plist." + exit 1 + fi + echo "✓ Version $VERSION is new and can be released." From 253b144d84eaad6d96ac213cc991161f8ff768ff Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Sat, 9 May 2026 20:38:28 +0100 Subject: [PATCH 2/3] fix(workflows): use -iname for case-insensitive bundle detection Heatmiser uses HeatmiserNeo.IndigoPlugin (capital I). Linux runners are case-sensitive so -name "*.indigoPlugin" misses it. Switch to -iname so the canonical workflows work for both legacy capitalized bundles and modern lowercase ones. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/plugin-dev/workflows/README.md | 2 +- docs/plugin-dev/workflows/create-release.yml | 2 +- docs/plugin-dev/workflows/version-check.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/plugin-dev/workflows/README.md b/docs/plugin-dev/workflows/README.md index c3d2ad3..7063c11 100644 --- a/docs/plugin-dev/workflows/README.md +++ b/docs/plugin-dev/workflows/README.md @@ -10,7 +10,7 @@ Drop-in GitHub Actions workflows for any Indigo plugin in the workspace. They au ## Conventions - **Tag prefix**: `v$VERSION` (e.g. `v2026.0.3`). The version-check is conservative and rejects either prefixed or bare tags clashing with the new version. -- **Plugin bundle detection**: `find . -maxdepth 1 -name "*.indigoPlugin" -type d`. The bundle must live at the repo root. +- **Plugin bundle detection**: `find . -maxdepth 1 -iname "*.indigoPlugin" -type d`. The bundle must live at the repo root. Case-insensitive (`-iname`) so legacy bundles like `HeatmiserNeo.IndigoPlugin` are matched. - **Action versions**: `actions/checkout@v4`, `softprops/action-gh-release@v2`. Both run on Node 20. ## Usage diff --git a/docs/plugin-dev/workflows/create-release.yml b/docs/plugin-dev/workflows/create-release.yml index da4ad97..7abae12 100644 --- a/docs/plugin-dev/workflows/create-release.yml +++ b/docs/plugin-dev/workflows/create-release.yml @@ -20,7 +20,7 @@ jobs: - name: Determine plugin name id: get_plugin_name run: | - PLUGIN_BUNDLE=$(find . -maxdepth 1 -name "*.indigoPlugin" -type d | head -1) + PLUGIN_BUNDLE=$(find . -maxdepth 1 -iname "*.indigoPlugin" -type d | head -1) if [ -z "$PLUGIN_BUNDLE" ]; then echo "Error: No .indigoPlugin directory found in repository root." >&2 exit 1 diff --git a/docs/plugin-dev/workflows/version-check.yml b/docs/plugin-dev/workflows/version-check.yml index ebfa43c..1b0ff42 100644 --- a/docs/plugin-dev/workflows/version-check.yml +++ b/docs/plugin-dev/workflows/version-check.yml @@ -18,7 +18,7 @@ jobs: - name: Determine plugin name id: get_plugin_name run: | - PLUGIN_BUNDLE=$(find . -maxdepth 1 -name "*.indigoPlugin" -type d | head -1) + PLUGIN_BUNDLE=$(find . -maxdepth 1 -iname "*.indigoPlugin" -type d | head -1) if [ -z "$PLUGIN_BUNDLE" ]; then echo "Error: No .indigoPlugin directory found in repository root." >&2 exit 1 From f7c8c94d0f733d4735b483c459eadedd2bff8831 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Sat, 9 May 2026 20:43:42 +0100 Subject: [PATCH 3/3] fix(workflows): use bundle name directly, drop suffix-strip basename "$PLUGIN_BUNDLE" .indigoPlugin is case-sensitive: for HeatmiserNeo.IndigoPlugin (capital I) the suffix wasn't stripped, so later steps tried to read HeatmiserNeo.IndigoPlugin.indigoPlugin/... and failed. Drop the basename suffix-strip entirely. The plugin_bundle output now contains the actual directory name (e.g. "HeatmiserNeo.IndigoPlugin" or "Domio.indigoPlugin") which is used verbatim for paths and the asset zip. Asset name preserves original case. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/plugin-dev/workflows/README.md | 2 +- docs/plugin-dev/workflows/create-release.yml | 26 ++++++++++---------- docs/plugin-dev/workflows/version-check.yml | 18 +++++++------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/plugin-dev/workflows/README.md b/docs/plugin-dev/workflows/README.md index 7063c11..db269d6 100644 --- a/docs/plugin-dev/workflows/README.md +++ b/docs/plugin-dev/workflows/README.md @@ -10,7 +10,7 @@ Drop-in GitHub Actions workflows for any Indigo plugin in the workspace. They au ## Conventions - **Tag prefix**: `v$VERSION` (e.g. `v2026.0.3`). The version-check is conservative and rejects either prefixed or bare tags clashing with the new version. -- **Plugin bundle detection**: `find . -maxdepth 1 -iname "*.indigoPlugin" -type d`. The bundle must live at the repo root. Case-insensitive (`-iname`) so legacy bundles like `HeatmiserNeo.IndigoPlugin` are matched. +- **Plugin bundle detection**: `find . -maxdepth 1 -iname "*.indigoPlugin" -type d`. The bundle must live at the repo root. Case-insensitive (`-iname`) so legacy bundles like `HeatmiserNeo.IndigoPlugin` are matched. The detected bundle name is used verbatim throughout (no suffix-stripping with `basename`), so the asset zip preserves the exact directory case (`HeatmiserNeo.IndigoPlugin.zip`, `Domio.indigoPlugin.zip`). - **Action versions**: `actions/checkout@v4`, `softprops/action-gh-release@v2`. Both run on Node 20. ## Usage diff --git a/docs/plugin-dev/workflows/create-release.yml b/docs/plugin-dev/workflows/create-release.yml index 7abae12..0633303 100644 --- a/docs/plugin-dev/workflows/create-release.yml +++ b/docs/plugin-dev/workflows/create-release.yml @@ -17,23 +17,23 @@ jobs: with: fetch-depth: 0 - - name: Determine plugin name - id: get_plugin_name + - name: Determine plugin bundle + id: get_plugin run: | - PLUGIN_BUNDLE=$(find . -maxdepth 1 -iname "*.indigoPlugin" -type d | head -1) - if [ -z "$PLUGIN_BUNDLE" ]; then + PLUGIN_BUNDLE_PATH=$(find . -maxdepth 1 -iname "*.indigoPlugin" -type d | head -1) + if [ -z "$PLUGIN_BUNDLE_PATH" ]; then echo "Error: No .indigoPlugin directory found in repository root." >&2 exit 1 fi - PLUGIN_NAME=$(basename "$PLUGIN_BUNDLE" .indigoPlugin) - echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT - echo "Detected plugin name: $PLUGIN_NAME" + PLUGIN_BUNDLE=$(basename "$PLUGIN_BUNDLE_PATH") + echo "plugin_bundle=$PLUGIN_BUNDLE" >> $GITHUB_OUTPUT + echo "Detected plugin bundle: $PLUGIN_BUNDLE" - name: Extract version from Info.plist id: get_version run: | - PLUGIN_NAME="${{ steps.get_plugin_name.outputs.plugin_name }}" - VERSION=$(grep -A1 'PluginVersion' "${PLUGIN_NAME}.indigoPlugin/Contents/Info.plist" | grep '' | sed 's/.*\(.*\)<\/string>.*/\1/') + PLUGIN_BUNDLE="${{ steps.get_plugin.outputs.plugin_bundle }}" + VERSION=$(grep -A1 'PluginVersion' "${PLUGIN_BUNDLE}/Contents/Info.plist" | grep '' | sed 's/.*\(.*\)<\/string>.*/\1/') if [ -z "$VERSION" ]; then echo "Error: Could not extract PluginVersion from Info.plist." >&2 exit 1 @@ -56,8 +56,8 @@ jobs: - name: Create plugin bundle zip if: steps.check_release.outputs.exists == 'false' run: | - PLUGIN_NAME="${{ steps.get_plugin_name.outputs.plugin_name }}" - zip -r "${PLUGIN_NAME}.indigoPlugin.zip" "${PLUGIN_NAME}.indigoPlugin" \ + PLUGIN_BUNDLE="${{ steps.get_plugin.outputs.plugin_bundle }}" + zip -r "${PLUGIN_BUNDLE}.zip" "${PLUGIN_BUNDLE}" \ -x "*.pyc" \ -x "*/__pycache__" \ -x "*/__pycache__/*" \ @@ -67,7 +67,7 @@ jobs: -x "*/.idea/*" \ -x "*.bbproject" \ -x "*.bbproject/*" - echo "Created ${PLUGIN_NAME}.indigoPlugin.zip" + echo "Created ${PLUGIN_BUNDLE}.zip" - name: Create GitHub Release if: steps.check_release.outputs.exists == 'false' @@ -78,4 +78,4 @@ jobs: generate_release_notes: true draft: false prerelease: false - files: ${{ steps.get_plugin_name.outputs.plugin_name }}.indigoPlugin.zip + files: ${{ steps.get_plugin.outputs.plugin_bundle }}.zip diff --git a/docs/plugin-dev/workflows/version-check.yml b/docs/plugin-dev/workflows/version-check.yml index 1b0ff42..7e8d7cd 100644 --- a/docs/plugin-dev/workflows/version-check.yml +++ b/docs/plugin-dev/workflows/version-check.yml @@ -15,23 +15,23 @@ jobs: with: fetch-depth: 0 - - name: Determine plugin name - id: get_plugin_name + - name: Determine plugin bundle + id: get_plugin run: | - PLUGIN_BUNDLE=$(find . -maxdepth 1 -iname "*.indigoPlugin" -type d | head -1) - if [ -z "$PLUGIN_BUNDLE" ]; then + PLUGIN_BUNDLE_PATH=$(find . -maxdepth 1 -iname "*.indigoPlugin" -type d | head -1) + if [ -z "$PLUGIN_BUNDLE_PATH" ]; then echo "Error: No .indigoPlugin directory found in repository root." >&2 exit 1 fi - PLUGIN_NAME=$(basename "$PLUGIN_BUNDLE" .indigoPlugin) - echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT - echo "Detected plugin name: $PLUGIN_NAME" + PLUGIN_BUNDLE=$(basename "$PLUGIN_BUNDLE_PATH") + echo "plugin_bundle=$PLUGIN_BUNDLE" >> $GITHUB_OUTPUT + echo "Detected plugin bundle: $PLUGIN_BUNDLE" - name: Extract version from Info.plist id: get_version run: | - PLUGIN_NAME="${{ steps.get_plugin_name.outputs.plugin_name }}" - VERSION=$(grep -A1 'PluginVersion' "${PLUGIN_NAME}.indigoPlugin/Contents/Info.plist" | grep '' | sed 's/.*\(.*\)<\/string>.*/\1/') + PLUGIN_BUNDLE="${{ steps.get_plugin.outputs.plugin_bundle }}" + VERSION=$(grep -A1 'PluginVersion' "${PLUGIN_BUNDLE}/Contents/Info.plist" | grep '' | sed 's/.*\(.*\)<\/string>.*/\1/') if [ -z "$VERSION" ]; then echo "Error: Could not extract PluginVersion from Info.plist." >&2 exit 1