|
1 | | -name: Release Obsidian Plugin |
| 1 | +name: Build & Release Obsidian Plugin |
2 | 2 |
|
3 | 3 | on: |
4 | 4 | push: |
5 | 5 | branches: |
6 | 6 | - main |
7 | | - tags: |
8 | | - - "*" |
| 7 | + workflow_dispatch: |
9 | 8 |
|
10 | | -env: |
11 | | - PLUGIN_NAME: linian |
| 9 | +permissions: |
| 10 | + contents: write |
| 11 | + |
| 12 | +concurrency: |
| 13 | + group: release-${{ github.ref }} |
| 14 | + cancel-in-progress: false |
12 | 15 |
|
13 | 16 | jobs: |
14 | | - # Deploy to release branch on every push to main |
15 | | - deploy-release-branch: |
16 | | - if: github.ref == 'refs/heads/main' |
| 17 | + release: |
17 | 18 | runs-on: ubuntu-latest |
18 | | - |
| 19 | + env: |
| 20 | + RELEASE_NOTES_PATH: "" |
| 21 | + RELEASE_FILES: "" |
| 22 | + RELEASE_FILES_DIR: "" |
| 23 | + ZIP_PATH: "" |
| 24 | + HAS_VERSIONS: "false" |
| 25 | + RELEASE_COMMIT: "" |
19 | 26 | steps: |
20 | | - - uses: actions/checkout@v4 |
| 27 | + - name: Checkout repository |
| 28 | + uses: actions/checkout@v4 |
21 | 29 | with: |
22 | 30 | fetch-depth: 0 |
23 | 31 |
|
24 | | - - name: Use Node.js |
| 32 | + - name: Set up Node.js |
25 | 33 | uses: actions/setup-node@v4 |
26 | 34 | with: |
27 | 35 | node-version: "18" |
| 36 | + cache: npm |
28 | 37 |
|
29 | | - - name: Install dependencies |
30 | | - run: npm ci |
31 | | - |
32 | | - - name: Build plugin |
33 | | - run: npm run build |
34 | | - |
35 | | - - name: Create release folder structure |
| 38 | + - name: Read plugin metadata |
| 39 | + id: meta |
| 40 | + shell: bash |
36 | 41 | run: | |
37 | | - mkdir -p release-temp/${{ env.PLUGIN_NAME }} |
38 | | - cp main.js manifest.json styles.css release-temp/${{ env.PLUGIN_NAME }}/ |
39 | | - if [ -f "versions.json" ]; then |
40 | | - cp versions.json release-temp/${{ env.PLUGIN_NAME }}/ |
| 42 | + VERSION=$(node -p "require('./manifest.json').version") |
| 43 | + ID=$(node -p "require('./manifest.json').id") |
| 44 | + echo "version=$VERSION" >> "$GITHUB_OUTPUT" |
| 45 | + echo "id=$ID" >> "$GITHUB_OUTPUT" |
| 46 | +
|
| 47 | + - name: Check if tag already exists |
| 48 | + id: check_tag |
| 49 | + shell: bash |
| 50 | + run: | |
| 51 | + git fetch --tags --force |
| 52 | + if git rev-parse "refs/tags/${{ steps.meta.outputs.version }}" >/dev/null 2>&1; then |
| 53 | + echo "exists=true" >> "$GITHUB_OUTPUT" |
| 54 | + else |
| 55 | + echo "exists=false" >> "$GITHUB_OUTPUT" |
41 | 56 | fi |
42 | 57 |
|
43 | | - - name: Deploy to release branch |
44 | | - uses: peaceiris/actions-gh-pages@v3 |
45 | | - with: |
46 | | - github_token: ${{ secrets.GITHUB_TOKEN }} |
47 | | - publish_branch: release |
48 | | - publish_dir: ./release-temp |
49 | | - force_orphan: true |
50 | | - commit_message: "Deploy built plugin files for ${{ github.sha }}" |
51 | | - |
52 | | - # Create GitHub Release on tag push |
53 | | - create-github-release: |
54 | | - if: startsWith(github.ref, 'refs/tags/') |
55 | | - runs-on: ubuntu-latest |
56 | | - |
57 | | - steps: |
58 | | - - uses: actions/checkout@v4 |
59 | | - |
60 | | - - name: Use Node.js |
61 | | - uses: actions/setup-node@v4 |
62 | | - with: |
63 | | - node-version: "18" |
| 58 | + - name: Skip release (tag already exists) |
| 59 | + if: steps.check_tag.outputs.exists == 'true' |
| 60 | + run: echo "Tag ${{ steps.meta.outputs.version }} already exists — skipping release pipeline." |
64 | 61 |
|
65 | 62 | - name: Install dependencies |
| 63 | + if: steps.check_tag.outputs.exists == 'false' |
66 | 64 | run: npm ci |
67 | 65 |
|
68 | | - - name: Build plugin |
| 66 | + - name: Build plugin bundle |
| 67 | + if: steps.check_tag.outputs.exists == 'false' |
69 | 68 | run: npm run build |
70 | 69 |
|
71 | | - - name: Create release assets |
| 70 | + - name: Assemble release folder (BRAT structure) |
| 71 | + if: steps.check_tag.outputs.exists == 'false' |
| 72 | + shell: bash |
72 | 73 | run: | |
73 | | - # Create zip with files directly in root (correct Obsidian plugin structure) |
74 | | - zip ${{ env.PLUGIN_NAME }}-${{ github.ref_name }}.zip main.js manifest.json styles.css versions.json |
| 74 | + set -euo pipefail |
| 75 | + mkdir -p dist/${{ steps.meta.outputs.id }} |
| 76 | + cp main.js dist/${{ steps.meta.outputs.id }}/ |
| 77 | + cp manifest.json dist/${{ steps.meta.outputs.id }}/ |
| 78 | + cp styles.css dist/${{ steps.meta.outputs.id }}/ |
| 79 | + if [ -f "versions.json" ]; then |
| 80 | + cp versions.json dist/${{ steps.meta.outputs.id }}/ |
| 81 | + fi |
| 82 | + cd dist |
| 83 | + zip -r "${{ steps.meta.outputs.id }}-${{ steps.meta.outputs.version }}.zip" "${{ steps.meta.outputs.id }}" |
| 84 | + echo "ZIP_PATH=$PWD/${{ steps.meta.outputs.id }}-${{ steps.meta.outputs.version }}.zip" >> $GITHUB_ENV |
| 85 | + cd .. |
| 86 | +
|
| 87 | + - name: Extract release notes from changelog |
| 88 | + if: steps.check_tag.outputs.exists == 'false' |
| 89 | + env: |
| 90 | + VERSION: ${{ steps.meta.outputs.version }} |
| 91 | + shell: bash |
| 92 | + run: | |
| 93 | + set -euo pipefail |
| 94 | + node <<'EOF' > dist/release-notes.md |
| 95 | + const fs = require('fs'); |
| 96 | + const version = process.env.VERSION; |
| 97 | + const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); |
| 98 | + const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| 99 | + const pattern = new RegExp(`## \\[${escape(version)}\\][\\s\\S]*?(?=^## \\[|$)`, 'm'); |
| 100 | + const match = changelog.match(pattern); |
| 101 | + if (!match) { |
| 102 | + console.error(`Could not find changelog entry for version ${version}`); |
| 103 | + process.exit(1); |
| 104 | + } |
| 105 | + process.stdout.write(match[0].trim() + '\n'); |
| 106 | + EOF |
| 107 | + echo "RELEASE_NOTES_PATH=$(pwd)/dist/release-notes.md" >> $GITHUB_ENV |
| 108 | +
|
| 109 | + - name: Stage artifacts for downstream steps |
| 110 | + if: steps.check_tag.outputs.exists == 'false' |
| 111 | + shell: bash |
| 112 | + run: | |
| 113 | + set -euo pipefail |
| 114 | + mkdir -p "$RUNNER_TEMP/plugin-files" |
| 115 | + cp dist/${{ steps.meta.outputs.id }}/main.js "$RUNNER_TEMP/plugin-files/" |
| 116 | + cp dist/${{ steps.meta.outputs.id }}/manifest.json "$RUNNER_TEMP/plugin-files/" |
| 117 | + cp dist/${{ steps.meta.outputs.id }}/styles.css "$RUNNER_TEMP/plugin-files/" |
| 118 | + if [ -f dist/${{ steps.meta.outputs.id }}/versions.json ]; then |
| 119 | + cp dist/${{ steps.meta.outputs.id }}/versions.json "$RUNNER_TEMP/plugin-files/" |
| 120 | + echo "HAS_VERSIONS=true" >> $GITHUB_ENV |
| 121 | + else |
| 122 | + echo "HAS_VERSIONS=false" >> $GITHUB_ENV |
| 123 | + fi |
| 124 | + cp "$ZIP_PATH" "$RUNNER_TEMP/" |
| 125 | + echo "ZIP_PATH=$RUNNER_TEMP/${{ steps.meta.outputs.id }}-${{ steps.meta.outputs.version }}.zip" >> $GITHUB_ENV |
| 126 | + echo "RELEASE_FILES_DIR=$RUNNER_TEMP/plugin-files" >> $GITHUB_ENV |
| 127 | + echo "RELEASE_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV |
| 128 | +
|
| 129 | + - name: Update release branch |
| 130 | + if: steps.check_tag.outputs.exists == 'false' |
| 131 | + shell: bash |
| 132 | + run: | |
| 133 | + set -euo pipefail |
| 134 | + git config user.name "github-actions[bot]" |
| 135 | + git config user.email "github-actions[bot]@users.noreply.github.com" |
| 136 | + ORIG_REF=$(git rev-parse --abbrev-ref HEAD) |
| 137 | + git fetch origin release || true |
| 138 | + if git show-ref --verify --quiet refs/remotes/origin/release; then |
| 139 | + git checkout -B release origin/release |
| 140 | + else |
| 141 | + git checkout -B release |
| 142 | + fi |
| 143 | + rm -rf * |
| 144 | + cp "$RELEASE_FILES_DIR"/main.js . |
| 145 | + cp "$RELEASE_FILES_DIR"/manifest.json . |
| 146 | + cp "$RELEASE_FILES_DIR"/styles.css . |
| 147 | + if [ "$HAS_VERSIONS" = "true" ]; then |
| 148 | + cp "$RELEASE_FILES_DIR"/versions.json . |
| 149 | + else |
| 150 | + rm -f versions.json || true |
| 151 | + fi |
| 152 | + git add main.js manifest.json styles.css |
| 153 | + if [ "$HAS_VERSIONS" = "true" ]; then |
| 154 | + git add versions.json |
| 155 | + fi |
| 156 | + if git diff --staged --quiet; then |
| 157 | + echo "No release branch changes to commit" |
| 158 | + else |
| 159 | + git commit -m "Deploy built plugin files for v${{ steps.meta.outputs.version }}\n\nGenerated from commit: $RELEASE_COMMIT" |
| 160 | + git push origin release --force |
| 161 | + fi |
| 162 | + git checkout "$ORIG_REF" |
| 163 | + git reset --hard "$RELEASE_COMMIT" |
75 | 164 |
|
76 | | - - name: Create Release |
| 165 | + - name: Prepare asset list |
| 166 | + if: steps.check_tag.outputs.exists == 'false' |
| 167 | + shell: bash |
| 168 | + run: | |
| 169 | + set -euo pipefail |
| 170 | + FILE_LIST="$ZIP_PATH"$'\n'$RELEASE_FILES_DIR/main.js$'\n'$RELEASE_FILES_DIR/manifest.json$'\n'$RELEASE_FILES_DIR/styles.css |
| 171 | + if [ "$HAS_VERSIONS" = "true" ]; then |
| 172 | + FILE_LIST+=$'\n'$RELEASE_FILES_DIR/versions.json |
| 173 | + fi |
| 174 | + { |
| 175 | + echo "RELEASE_FILES<<'EOF'" |
| 176 | + echo "$FILE_LIST" |
| 177 | + echo EOF |
| 178 | + } >> $GITHUB_ENV |
| 179 | +
|
| 180 | + - name: Create GitHub release |
| 181 | + if: steps.check_tag.outputs.exists == 'false' |
77 | 182 | uses: softprops/action-gh-release@v1 |
78 | 183 | with: |
79 | | - tag_name: ${{ github.ref_name }} |
80 | | - name: "Linian v${{ github.ref_name }}" |
81 | | - body: | |
82 | | - ## Linian Obsidian Plugin v${{ github.ref_name }} |
83 | | -
|
84 | | - ### Installation |
85 | | -
|
86 | | - **Option 1: Download Plugin Folder (Recommended)** |
87 | | - 1. Download `${{ env.PLUGIN_NAME }}-${{ github.ref_name }}.zip` |
88 | | - 2. Extract the zip file |
89 | | - 3. Copy the `${{ env.PLUGIN_NAME }}` folder to your vault's `.obsidian/plugins/` directory |
90 | | - 4. Enable the plugin in Obsidian Settings → Community Plugins |
91 | | -
|
92 | | - **Option 2: Manual Installation** |
93 | | - 1. Download `main.js`, `manifest.json`, and `styles.css` |
94 | | - 2. Create a folder named `${{ env.PLUGIN_NAME }}` in your vault's `.obsidian/plugins/` directory |
95 | | - 3. Place the downloaded files in that folder |
96 | | - 4. Enable the plugin in Obsidian Settings → Community Plugins |
97 | | -
|
| 184 | + tag_name: ${{ steps.meta.outputs.version }} |
| 185 | + name: Linian v${{ steps.meta.outputs.version }} |
| 186 | + body_path: ${{ env.RELEASE_NOTES_PATH }} |
| 187 | + files: ${{ env.RELEASE_FILES }} |
98 | 188 | draft: false |
99 | 189 | prerelease: false |
100 | | - files: | |
101 | | - ${{ env.PLUGIN_NAME }}-${{ github.ref_name }}.zip |
102 | | - main.js |
103 | | - manifest.json |
104 | | - styles.css |
105 | | - versions.json |
| 190 | + |
| 191 | + - name: Upload release bundle artifact |
| 192 | + if: steps.check_tag.outputs.exists == 'false' |
| 193 | + uses: actions/upload-artifact@v4 |
| 194 | + with: |
| 195 | + name: linian-${{ steps.meta.outputs.version }} |
| 196 | + path: ${{ env.RELEASE_FILES }} |
0 commit comments