---
name: obsidian-ci-integration
description: |
  Set up GitHub Actions CI/CD for Obsidian plugin development.
  Use when automating builds, tests, and releases for your plugin,
  or setting up continuous integration for Obsidian projects.
  Trigger with phrases like "obsidian CI", "obsidian github actions",
  "obsidian automated build", "obsidian CI/CD".
allowed-tools: Read, Write, Edit, Bash(npm:*)
version: 1.0.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
---

# Obsidian CI Integration

## Overview
Set up GitHub Actions for automated building, testing, and releasing Obsidian plugins.

## Prerequisites
- GitHub repository for your plugin
- Working local build (npm run build)
- Basic understanding of GitHub Actions

## Instructions

### Step 1: Create Build Workflow
```yaml
# .github/workflows/build.yml
name: Build Obsidian Plugin

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint
        continue-on-error: true

      - name: Type check
        run: npm run typecheck
        if: always()

      - name: Build
        run: npm run build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: plugin-build
          path: |
            main.js
            manifest.json
            styles.css
          if-no-files-found: error
```

### Step 2: Create Test Workflow
```yaml
# .github/workflows/test.yml
name: Test

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test -- --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage/lcov.info
          fail_ci_if_error: false
```

### Step 3: Create Release Workflow
```yaml
# .github/workflows/release.yml
name: Release Obsidian Plugin

on:
  push:
    tags:
      - '*'

permissions:
  contents: write

jobs:
  release:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Get version
        id: version
        run: echo "version=$(jq -r '.version' manifest.json)" >> $GITHUB_OUTPUT

      - name: Verify tag matches version
        run: |
          TAG_VERSION="${GITHUB_REF#refs/tags/}"
          MANIFEST_VERSION="${{ steps.version.outputs.version }}"
          if [ "$TAG_VERSION" != "$MANIFEST_VERSION" ]; then
            echo "Tag ($TAG_VERSION) does not match manifest version ($MANIFEST_VERSION)"
            exit 1
          fi

      - name: Create release archive
        run: |
          mkdir release
          cp main.js manifest.json release/
          [ -f styles.css ] && cp styles.css release/
          cd release && zip -r ../my-plugin-${{ steps.version.outputs.version }}.zip *

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          files: |
            main.js
            manifest.json
            styles.css
            my-plugin-${{ steps.version.outputs.version }}.zip
          generate_release_notes: true
          draft: false
          prerelease: false
```

### Step 4: Add Version Bump Script
```javascript
// version-bump.mjs
import { readFileSync, writeFileSync } from "fs";

const targetVersion = process.argv[2];
if (!targetVersion) {
  console.error("Usage: node version-bump.mjs <version>");
  process.exit(1);
}

// Validate semver
if (!/^\d+\.\d+\.\d+$/.test(targetVersion)) {
  console.error("Invalid version format. Use semver: x.y.z");
  process.exit(1);
}

// Update manifest.json
const manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));

// Update versions.json
let versions = {};
try {
  versions = JSON.parse(readFileSync("versions.json", "utf8"));
} catch {
  // File doesn't exist, create new
}
versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));

// Update package.json
const pkg = JSON.parse(readFileSync("package.json", "utf8"));
pkg.version = targetVersion;
writeFileSync("package.json", JSON.stringify(pkg, null, "\t"));

console.log(`Version bumped to ${targetVersion}`);
```

### Step 5: Configure package.json Scripts
```json
{
  "scripts": {
    "dev": "node esbuild.config.mjs",
    "build": "node esbuild.config.mjs production",
    "test": "vitest run",
    "test:watch": "vitest",
    "lint": "eslint src/ --ext .ts",
    "typecheck": "tsc --noEmit",
    "version": "node version-bump.mjs && git add manifest.json versions.json package.json",
    "release": "npm run version && git commit -m 'Bump version' && git push && git push --tags"
  }
}
```

### Step 6: Create Validation Workflow
```yaml
# .github/workflows/validate.yml
name: Validate Plugin

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  validate:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Validate manifest.json
        run: |
          # Check required fields
          for field in id name version minAppVersion description author; do
            value=$(jq -r ".$field" manifest.json)
            if [ "$value" == "null" ] || [ -z "$value" ]; then
              echo "ERROR: Missing required field: $field"
              exit 1
            fi
          done

          # Validate version format
          version=$(jq -r '.version' manifest.json)
          if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            echo "ERROR: Invalid version format: $version"
            exit 1
          fi

          echo "manifest.json is valid"

      - name: Validate versions.json
        run: |
          if [ ! -f versions.json ]; then
            echo "ERROR: versions.json not found"
            exit 1
          fi

          # Validate JSON syntax
          if ! jq empty versions.json; then
            echo "ERROR: Invalid JSON in versions.json"
            exit 1
          fi

          # Check current version is in versions.json
          current=$(jq -r '.version' manifest.json)
          if [ "$(jq -r ".[\"$current\"]" versions.json)" == "null" ]; then
            echo "WARNING: Current version $current not in versions.json"
          fi

          echo "versions.json is valid"

      - name: Check for console.log
        run: |
          count=$(grep -r "console.log" src/ --include="*.ts" | wc -l || true)
          if [ "$count" -gt 5 ]; then
            echo "WARNING: Found $count console.log statements (consider removing before release)"
          fi
```

## Output
- Build workflow for CI validation
- Test workflow with coverage reporting
- Release workflow for automated GitHub releases
- Version bump script for consistent versioning
- Validation workflow for plugin standards

## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| Build fails | Missing dependencies | Ensure package-lock.json is committed |
| Release fails | Version mismatch | Run version bump before tagging |
| Upload fails | File not found | Check build output paths |
| Permission denied | Token scope | Check workflow permissions |

## Examples

### Manual Release Process
```bash
# 1. Bump version
npm run version 1.0.1

# 2. Commit and tag
git add -A
git commit -m "Release v1.0.1"
git tag 1.0.1

# 3. Push (triggers release workflow)
git push && git push --tags
```

### Beta Release Workflow
```yaml
# .github/workflows/beta.yml
name: Beta Release

on:
  push:
    branches: [beta]

jobs:
  beta:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build

      - name: Create beta release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: beta
          prerelease: true
          files: |
            main.js
            manifest.json
          body: |
            Beta release from ${{ github.sha }}
            Install via BRAT: `username/repo-name` with beta tag
```

## Resources
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
- [Obsidian Plugin Releasing Guide](https://docs.obsidian.md/Plugins/Releasing/Release+your+plugin+with+GitHub+Actions)

## Next Steps
For publishing to community plugins, see `obsidian-deploy-integration`.
