climaybe 3.4.3 → 3.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -233,11 +233,11 @@ Enabled via `climaybe init` prompt (`Enable preview + cleanup workflows?`; defau
233
233
  |----------|---------|-------------|
234
234
  | `pr-update.yml` | PR opened/synchronize/reopened (base: main, staging, develop, staging-*, live-*) | Shares draft theme, renames with `-PR<number>`, comments preview + customize URLs. **Multi-store + source branch (`pull_request.head.ref`) not** `staging-<alias>` **or** `live-<alias>`**:** publishes to **every** configured store (matrix) and posts **all** links in one PR comment. For `staging-<alias>` / `live-<alias>` source branches, only that store is used. **Path filter:** theme paths only (`assets/`, `blocks/`, `config/`, `layout/`, `locales/`, `sections/`, `snippets/`, `templates/`, `_scripts/`, `_styles/`, `shopify.theme.toml`, `stores/**`). |
235
235
  | `pr-close.yml` | PR closed (same branch set) | Deletes this PR’s preview themes using the **same store matrix rule** as `pr-update`; PR comment shows **total** deleted count across stores. |
236
- | `cleanup-orphan-preview-themes.yml` | Weekly (Mon 06:00 UTC) + `workflow_dispatch` | Per store: deletes themes ending with `-PR<n>` when PR `#n` is **not** open (merged/closed without cleanup). Uses `gh pr list` (limit 1000 open PRs). |
236
+ | `cleanup-orphan-preview-themes.yml` | PR closed (same branch set) + weekly (Mon 06:00 UTC) + `workflow_dispatch` | Per store: deletes themes ending with `-PR<n>` when PR `#n` is **not** open (merged/closed without cleanup). Uses `gh pr list` (limit 1000 open PRs). |
237
237
  | `reusable-publish-pr-preview-store.yml` | workflow_call | Share + rename + upload comment fragment for **one** store (matrix leg in `pr-update`). |
238
238
  | `reusable-share-theme.yml` | workflow_call | Shares Shopify draft theme and returns `theme_id` (still available if you call it elsewhere) |
239
239
  | `reusable-rename-theme.yml` | workflow_call | Renames shared theme to include `PR<number>` (still available for custom flows) |
240
- | `reusable-comment-on-pr.yml` | workflow_call | Posts preview comment; aggregates `preview-fragment-*` artifacts when `use_preview_fragments` is true |
240
+ | `reusable-comment-on-pr.yml` | workflow_call | Upserts one bot preview comment; aggregates `preview-fragment-*` artifacts when `use_preview_fragments` is true and removes older bot preview comments |
241
241
  | `reusable-cleanup-themes.yml` | workflow_call | `cleanup_mode: by_pr` deletes names ending with `-PR{padded}`; `orphan_pr` deletes `-PR<n>` when PR `n` is not open. Optional `result_artifact_prefix` for matrix fan-in on `pr-close` |
242
242
  | `reusable-extract-pr-number.yml` | workflow_call | Extracts padded/unpadded PR number outputs for naming and API-safe usage |
243
243
 
package/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 3.4.3
1
+ 3.4.5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "3.4.3",
3
+ "version": "3.4.5",
4
4
  "description": "Shopify CLI by Electric Maybe for theme CI/CD workflows, branch orchestration, app setup, and dev tooling",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,6 +5,9 @@
5
5
  name: Cleanup orphan preview themes
6
6
 
7
7
  on:
8
+ pull_request:
9
+ types: [closed]
10
+ branches: [main, staging, develop, 'staging-*', 'live-*']
8
11
  schedule:
9
12
  - cron: '0 6 * * 1'
10
13
  workflow_dispatch:
@@ -12,6 +15,7 @@ on:
12
15
  permissions:
13
16
  contents: read
14
17
  pull-requests: read
18
+ actions: write
15
19
 
16
20
  jobs:
17
21
  configure:
@@ -56,6 +56,7 @@ jobs:
56
56
  const path = require('path');
57
57
  const issueNumber = parseInt(process.env.PR_NUMBER);
58
58
  const useFragments = process.env.USE_PREVIEW_FRAGMENTS === 'true';
59
+ const marker = '<!-- climaybe-preview-comment -->';
59
60
 
60
61
  const walkJsonFiles = (dir) => {
61
62
  const out = [];
@@ -84,6 +85,7 @@ jobs:
84
85
  }
85
86
 
86
87
  const parts = [
88
+ marker,
87
89
  '## 🎨 Theme Preview Generated',
88
90
  '',
89
91
  `**Branch:** ${context.payload.pull_request?.head?.ref || context.ref.replace('refs/heads/', '')}`,
@@ -92,7 +94,12 @@ jobs:
92
94
 
93
95
  if (fragments.length > 0) {
94
96
  parts.push('', '### Preview links (per store)');
95
- const byAlias = [...fragments].sort((a, b) => String(a.alias).localeCompare(String(b.alias)));
97
+ const uniqueByAlias = new Map();
98
+ for (const f of fragments) {
99
+ const alias = String(f.alias || '').trim() || 'store';
100
+ uniqueByAlias.set(alias, f);
101
+ }
102
+ const byAlias = [...uniqueByAlias.values()].sort((a, b) => String(a.alias).localeCompare(String(b.alias)));
96
103
  for (const f of byAlias) {
97
104
  const host = (f.store_host || '').replace(/\/$/, '');
98
105
  const tid = f.theme_id || '';
@@ -129,10 +136,43 @@ jobs:
129
136
  }
130
137
 
131
138
  parts.push('', '---', '*This preview will be available for 7 days.*');
139
+ const body = parts.join('\n');
132
140
 
133
- await github.rest.issues.createComment({
134
- issue_number: issueNumber,
141
+ const comments = await github.paginate(github.rest.issues.listComments, {
135
142
  owner: context.repo.owner,
136
143
  repo: context.repo.repo,
137
- body: parts.join('\n')
144
+ issue_number: issueNumber,
145
+ per_page: 100
138
146
  });
147
+ const managed = comments
148
+ .filter((c) => {
149
+ const text = String(c.body || '');
150
+ const botLike = c.user?.type === 'Bot' || String(c.user?.login || '').endsWith('[bot]');
151
+ const previewLike = text.includes(marker) || text.includes('## 🎨 Theme Preview Generated');
152
+ return botLike && previewLike;
153
+ })
154
+ .sort((a, b) => a.id - b.id);
155
+
156
+ if (managed.length > 0) {
157
+ const keep = managed[managed.length - 1];
158
+ await github.rest.issues.updateComment({
159
+ owner: context.repo.owner,
160
+ repo: context.repo.repo,
161
+ comment_id: keep.id,
162
+ body
163
+ });
164
+ for (const c of managed.slice(0, -1)) {
165
+ await github.rest.issues.deleteComment({
166
+ owner: context.repo.owner,
167
+ repo: context.repo.repo,
168
+ comment_id: c.id
169
+ });
170
+ }
171
+ } else {
172
+ await github.rest.issues.createComment({
173
+ issue_number: issueNumber,
174
+ owner: context.repo.owner,
175
+ repo: context.repo.repo,
176
+ body
177
+ });
178
+ }
@@ -143,20 +143,23 @@ jobs:
143
143
  run: |
144
144
  node <<'NODE'
145
145
  const fs = require('fs');
146
+ const alias = process.env.STORE_ALIAS || 'store';
147
+ const safeAlias = alias.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
148
+ const fileName = `fragment-${safeAlias || 'store'}.json`;
146
149
  const url = (process.env.SHOPIFY_STORE_URL || '')
147
150
  .replace(/^https?:\/\//, '')
148
151
  .replace(/\/.*$/, '');
149
152
  const payload = {
150
- alias: process.env.STORE_ALIAS || '',
153
+ alias,
151
154
  theme_id: process.env.THEME_ID || '',
152
155
  store_host: url,
153
156
  };
154
- fs.writeFileSync('fragment.json', JSON.stringify(payload, null, 0));
157
+ fs.writeFileSync(fileName, JSON.stringify(payload, null, 0));
155
158
  NODE
156
159
 
157
160
  - name: Upload preview fragment
158
161
  uses: actions/upload-artifact@v4
159
162
  with:
160
163
  name: preview-fragment-${{ inputs.store_alias }}
161
- path: fragment.json
164
+ path: fragment-*.json
162
165
  retention-days: 2