climaybe 3.1.3 → 3.2.0

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
@@ -243,14 +243,14 @@ When enabled, builds are **resilient**:
243
243
  - If `_scripts/*.js` or `_styles/main.css` are missing, the build workflow **skips** those steps and continues.
244
244
  - `init` may offer to create entrypoints; **default answer is No**.
245
245
  - Script bundling preserves comments/spacing and emits bundles only for root entry files (files imported by other top-level `_scripts/*.js` are inlined, not emitted separately).
246
- - On `live-<alias>` branches only, script bundles are minified and overwrite `assets/*.js`; `main` and `staging-*` keep readable built JS.
246
+ - Script bundles are written to `assets/*.js` (readable by default; use `climaybe build-scripts --minify` if you want minified output).
247
247
  - Live minified `assets/*` changes are intentionally excluded from hotfix backports to `main` (no branch-specific `.gitignore` split required).
248
248
 
249
249
  Build workflows install deps with `npm ci` and run `npx --no-install climaybe build-scripts` plus `npx --no-install climaybe build`, so CI uses lockfile-pinned versions (no `@latest` drift).
250
250
 
251
251
  | Workflow | Trigger | What it does |
252
252
  |----------|---------|-------------|
253
- | `build-pipeline.yml` | Push to any branch (ignores docs-only/tooling-only paths; see CI/CD reference) | Runs reusable build; Lighthouse only on branch **`staging`**, when a build ran, and secrets allow |
253
+ | `build-pipeline.yml` | Push to any branch (ignores docs-only/tooling-only paths; see CI/CD reference) | Runs reusable build; skips entirely on **`live-*`** when the pusher is a GitHub **`[bot]`** user; Lighthouse only on branch **`staging`**, when a build ran, and secrets allow |
254
254
  | `reusable-build.yml` | workflow_call | Path-filtered `build-scripts` / Tailwind (`climaybe build`), then commits compiled assets when changed |
255
255
  | `create-release.yml` | Push tag `v*`, or **workflow_run** after Post-Merge Tag / Nightly Hotfix Tag succeed on `main` | Builds release archive and creates GitHub Release notes from commits since the previous tag. It filters repetitive automation subjects (main→staging syncs, store/root sync chores, bot merge noise) before generating notes. If remaining subjects are low-signal and `GEMINI_API_KEY` exists, it uses Gemini to generate cleaner merchant-facing notes. Also covers tags created by workflows with `GITHUB_TOKEN`, which may not trigger tag-push workflows. |
256
256
 
package/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 3.1.3
1
+ 3.2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "3.1.3",
3
+ "version": "3.2.0",
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": {
@@ -232,8 +232,14 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = false } =
232
232
  writeTaggedLine('tailwind', pc.blue, 'watching _styles/main.css -> assets/style.css');
233
233
  }
234
234
 
235
- // Optional dev MCP (non-blocking if missing)
236
- const devMcp = spawnLogged('npx', ['-y', '@shopify/dev-mcp@latest'], { name: 'dev-mcp', cwd });
235
+ // Optional dev MCP (non-blocking if missing). @shopify/dev-mcp pulls hydrogen (peers react-router 7.12)
236
+ // alongside react-router 7.13 npm warns with ERESOLVE unless legacy peer resolution is enabled.
237
+ const devMcpEnv = { ...process.env, npm_config_legacy_peer_deps: 'true' };
238
+ const devMcp = spawnLogged('npx', ['-y', '@shopify/dev-mcp@latest'], {
239
+ name: 'dev-mcp',
240
+ cwd,
241
+ env: devMcpEnv,
242
+ });
237
243
 
238
244
  const scriptsDir = join(cwd, '_scripts');
239
245
  if (existsSync(scriptsDir)) {
@@ -22,11 +22,14 @@ jobs:
22
22
  # Skip pure store-sync + hotfix-backport commits; those are infrastructure-only
23
23
  # syncs from staging/live stores back to main and should not trigger heavy theme
24
24
  # review / build pipelines.
25
+ # On live-<alias>, skip when the pusher is a GitHub bot (actor name contains "[bot]")
26
+ # so automated commits do not run script/Tailwind compilation.
25
27
  if: >
26
28
  !contains(github.event.head_commit.message, '[hotfix-backport]')
27
29
  && !contains(github.event.head_commit.message, '[stores-to-root]')
28
30
  && !contains(github.event.head_commit.message, '[root-to-stores]')
29
31
  && !(github.actor == 'github-actions[bot]' && contains(github.event.head_commit.message, 'chore(assets): update compiled javascript and css'))
32
+ && !(startsWith(github.ref_name, 'live-') && contains(github.actor, '[bot]'))
30
33
  uses: ./.github/workflows/reusable-build.yml
31
34
 
32
35
  lighthouse-gate:
@@ -15,6 +15,7 @@ jobs:
15
15
  resolve-store:
16
16
  runs-on: ubuntu-latest
17
17
  outputs:
18
+ store_alias: ${{ steps.resolve.outputs.alias }}
18
19
  store_alias_secret: ${{ steps.resolve.outputs.alias_secret }}
19
20
  steps:
20
21
  - name: Checkout code
@@ -80,7 +81,7 @@ jobs:
80
81
  secrets: inherit
81
82
 
82
83
  comment-on-pr:
83
- needs: cleanup-theme
84
+ needs: [cleanup-theme, resolve-store]
84
85
  runs-on: ubuntu-latest
85
86
  steps:
86
87
  - name: Comment on PR about cleanup
@@ -88,6 +89,7 @@ jobs:
88
89
  with:
89
90
  script: |
90
91
  const prNumber = context.payload.pull_request.number;
92
+ const storeAlias = '${{ needs.resolve-store.outputs.store_alias }}' || '';
91
93
  const deletedCount = parseInt('${{ needs.cleanup-theme.outputs.deleted_count }}') || 0;
92
94
  const deletedThemesRaw = `${{ needs.cleanup-theme.outputs.deleted_themes }}`.trim();
93
95
 
@@ -98,8 +100,9 @@ jobs:
98
100
  '',
99
101
  `**Branch:** ${context.payload.pull_request.head.ref}`,
100
102
  `**PR Status:** ${context.payload.pull_request.state}`,
103
+ storeAlias ? `**Store:** ${storeAlias}` : null,
101
104
  `**Deleted Themes:** ${deletedCount}`
102
- ];
105
+ ].filter(Boolean);
103
106
 
104
107
  if (deletedCount > 0 && deletedThemesRaw) {
105
108
  const deletedThemeLines = deletedThemesRaw
@@ -18,6 +18,18 @@ on:
18
18
  deleted_themes:
19
19
  description: "Deleted theme names, one per line"
20
20
  value: ${{ jobs.cleanup.outputs.deleted_themes }}
21
+ matched_count:
22
+ description: "Number of themes matching this PR (before delete)"
23
+ value: ${{ jobs.cleanup.outputs.matched_count }}
24
+ matched_themes:
25
+ description: "Matched theme names, one per line (before delete)"
26
+ value: ${{ jobs.cleanup.outputs.matched_themes }}
27
+ skipped_reason:
28
+ description: "Why cleanup was skipped (empty if not skipped)"
29
+ value: ${{ jobs.cleanup.outputs.skipped_reason }}
30
+ store_hint:
31
+ description: "Redacted store host (best-effort; may be masked)"
32
+ value: ${{ jobs.cleanup.outputs.store_hint }}
21
33
 
22
34
  jobs:
23
35
  cleanup:
@@ -25,6 +37,10 @@ jobs:
25
37
  outputs:
26
38
  deleted_count: ${{ steps.cleanup.outputs.deleted_count }}
27
39
  deleted_themes: ${{ steps.cleanup.outputs.deleted_themes }}
40
+ matched_count: ${{ steps.cleanup.outputs.matched_count }}
41
+ matched_themes: ${{ steps.cleanup.outputs.matched_themes }}
42
+ skipped_reason: ${{ steps.cleanup.outputs.skipped_reason }}
43
+ store_hint: ${{ steps.cleanup.outputs.store_hint }}
28
44
  steps:
29
45
  - name: Install Shopify CLI
30
46
  run: npm install -g @shopify/cli @shopify/theme
@@ -35,20 +51,86 @@ jobs:
35
51
  SHOPIFY_STORE_URL: ${{ inputs.store_alias_secret && secrets[format('SHOPIFY_STORE_URL_{0}', inputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
36
52
  SHOPIFY_THEME_ACCESS_TOKEN: ${{ inputs.store_alias_secret && secrets[format('SHOPIFY_THEME_ACCESS_TOKEN_{0}', inputs.store_alias_secret)] || secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
37
53
  PR_NUMBER: ${{ inputs.pr_number }}
54
+ STORE_ALIAS_SECRET: ${{ inputs.store_alias_secret }}
38
55
  run: |
39
- if [ -z "$SHOPIFY_STORE_URL" ] || [ -z "$SHOPIFY_THEME_ACCESS_TOKEN" ]; then
56
+ set -euo pipefail
57
+
58
+ STORE_HOST="${SHOPIFY_STORE_URL#https://}"
59
+ STORE_HOST="${STORE_HOST#http://}"
60
+ STORE_HOST="${STORE_HOST%%/*}"
61
+
62
+ {
63
+ echo "### 🧹 Cleanup preview themes"
64
+ echo ""
65
+ echo "- **PR**: \`${PR_NUMBER}\`"
66
+ echo "- **Store alias secret**: \`${STORE_ALIAS_SECRET:-<default>}\`"
67
+ echo "- **Store (host)**: \`${STORE_HOST:-<missing>}\`"
68
+ } >> "$GITHUB_STEP_SUMMARY"
69
+
70
+ echo "PR=${PR_NUMBER}"
71
+ echo "Store alias secret=${STORE_ALIAS_SECRET:-<default>}"
72
+ echo "Store host=${STORE_HOST:-<missing>}"
73
+
74
+ if [ -z "${SHOPIFY_STORE_URL:-}" ] || [ -z "${SHOPIFY_THEME_ACCESS_TOKEN:-}" ]; then
40
75
  echo "Missing Shopify secrets; skipping theme cleanup (no themes deleted)."
41
- echo "deleted_count=0" >> $GITHUB_OUTPUT
42
- echo "deleted_themes<<DELETED_EOF" >> $GITHUB_OUTPUT
43
- echo "DELETED_EOF" >> $GITHUB_OUTPUT
76
+
77
+ {
78
+ echo ""
79
+ echo "#### Result"
80
+ echo "- **Skipped**: yes (missing Shopify secrets)"
81
+ echo "- **Matched themes**: 0"
82
+ echo "- **Deleted themes**: 0"
83
+ } >> "$GITHUB_STEP_SUMMARY"
84
+
85
+ echo "skipped_reason=missing_secrets" >> "$GITHUB_OUTPUT"
86
+ echo "store_hint=${STORE_HOST:-}" >> "$GITHUB_OUTPUT"
87
+ echo "matched_count=0" >> "$GITHUB_OUTPUT"
88
+ echo "matched_themes<<MATCHED_EOF" >> "$GITHUB_OUTPUT"
89
+ echo "MATCHED_EOF" >> "$GITHUB_OUTPUT"
90
+ echo "deleted_count=0" >> "$GITHUB_OUTPUT"
91
+ echo "deleted_themes<<DELETED_EOF" >> "$GITHUB_OUTPUT"
92
+ echo "DELETED_EOF" >> "$GITHUB_OUTPUT"
44
93
  exit 0
45
94
  fi
46
95
 
47
- THEME_LIST=$(shopify theme list \
96
+ echo "store_hint=${STORE_HOST:-}" >> "$GITHUB_OUTPUT"
97
+ echo "skipped_reason=" >> "$GITHUB_OUTPUT"
98
+
99
+ THEME_LIST_ERR="$(mktemp)"
100
+ THEME_LIST_JSON="$(mktemp)"
101
+
102
+ if ! shopify theme list \
48
103
  --store "$SHOPIFY_STORE_URL" \
49
104
  --password "$SHOPIFY_THEME_ACCESS_TOKEN" \
50
- --json 2>/dev/null || echo "[]")
105
+ --json > "$THEME_LIST_JSON" 2> "$THEME_LIST_ERR"; then
106
+ echo "shopify theme list failed."
107
+ echo "Error output (first 120 lines):"
108
+ sed -n '1,120p' "$THEME_LIST_ERR" || true
109
+ {
110
+ echo ""
111
+ echo "#### Result"
112
+ echo "- **Error**: \`shopify theme list\` failed"
113
+ echo ""
114
+ echo "<details><summary>CLI error output</summary>"
115
+ echo ""
116
+ echo "\`\`\`"
117
+ sed -n '1,240p' "$THEME_LIST_ERR" || true
118
+ echo "\`\`\`"
119
+ echo "</details>"
120
+ } >> "$GITHUB_STEP_SUMMARY"
121
+ exit 1
122
+ fi
123
+
124
+ # Defensive: if CLI returned empty, treat as an empty list (instead of jq failure).
125
+ if [ ! -s "$THEME_LIST_JSON" ]; then
126
+ echo "[]" > "$THEME_LIST_JSON"
127
+ fi
128
+
129
+ ALL_COUNT="$(jq -r 'length' "$THEME_LIST_JSON" 2>/dev/null || echo "0")"
130
+ echo "Found ${ALL_COUNT} total theme(s) on store."
51
131
 
132
+ MATCHED_COUNT=0
133
+ MATCHED_THEMES=""
52
134
  DELETED_COUNT=0
53
135
  DELETED_THEMES=""
54
136
 
@@ -57,22 +139,61 @@ jobs:
57
139
  [ -z "$THEME_NAME" ] && continue
58
140
 
59
141
  if printf "%s" "$THEME_NAME" | grep -q "PR${PR_NUMBER}"; then
142
+ MATCHED_COUNT=$((MATCHED_COUNT + 1))
143
+ MATCHED_THEMES="${MATCHED_THEMES}${THEME_NAME}\n"
144
+ echo "Matched: $THEME_NAME ($THEME_ID)"
145
+
146
+ DELETE_ERR="$(mktemp)"
60
147
  if shopify theme delete \
61
148
  --store "$SHOPIFY_STORE_URL" \
62
149
  --password "$SHOPIFY_THEME_ACCESS_TOKEN" \
63
150
  --force \
64
- --theme "$THEME_ID" 2>/dev/null; then
151
+ --theme "$THEME_ID" 1>/dev/null 2>"$DELETE_ERR"; then
65
152
  DELETED_COUNT=$((DELETED_COUNT + 1))
66
153
  DELETED_THEMES="${DELETED_THEMES}${THEME_NAME}\n"
154
+ echo "Deleted: $THEME_NAME ($THEME_ID)"
67
155
  else
68
- echo "Failed to delete theme: $THEME_NAME ($THEME_ID)"
156
+ echo "Failed to delete: $THEME_NAME ($THEME_ID)"
157
+ echo "Delete error output (first 80 lines):"
158
+ sed -n '1,80p' "$DELETE_ERR" || true
69
159
  fi
70
160
  fi
71
- done < <(echo "$THEME_LIST" | jq -r '.[] | [.id, .name] | @tsv')
161
+ done < <(jq -r '.[] | [.id, .name] | @tsv' "$THEME_LIST_JSON")
162
+
163
+ {
164
+ echo ""
165
+ echo "#### Result"
166
+ echo "- **Total themes on store**: ${ALL_COUNT}"
167
+ echo "- **Matched (PR${PR_NUMBER})**: ${MATCHED_COUNT}"
168
+ echo "- **Deleted**: ${DELETED_COUNT}"
169
+ } >> "$GITHUB_STEP_SUMMARY"
170
+
171
+ if [ "$MATCHED_COUNT" -gt 0 ]; then
172
+ {
173
+ echo ""
174
+ echo "#### Matched theme names"
175
+ printf "%b" "$MATCHED_THEMES" | sed -e 's/^/- /'
176
+ } >> "$GITHUB_STEP_SUMMARY"
177
+ fi
178
+
179
+ if [ "$DELETED_COUNT" -gt 0 ]; then
180
+ {
181
+ echo ""
182
+ echo "#### Deleted theme names"
183
+ printf "%b" "$DELETED_THEMES" | sed -e 's/^/- /'
184
+ } >> "$GITHUB_STEP_SUMMARY"
185
+ fi
186
+
187
+ echo "matched_count=$MATCHED_COUNT" >> "$GITHUB_OUTPUT"
188
+ {
189
+ echo "matched_themes<<MATCHED_EOF"
190
+ printf "%b" "$MATCHED_THEMES"
191
+ echo "MATCHED_EOF"
192
+ } >> "$GITHUB_OUTPUT"
72
193
 
73
- echo "deleted_count=$DELETED_COUNT" >> $GITHUB_OUTPUT
194
+ echo "deleted_count=$DELETED_COUNT" >> "$GITHUB_OUTPUT"
74
195
  {
75
196
  echo "deleted_themes<<DELETED_EOF"
76
197
  printf "%b" "$DELETED_THEMES"
77
198
  echo "DELETED_EOF"
78
- } >> $GITHUB_OUTPUT
199
+ } >> "$GITHUB_OUTPUT"