climaybe 1.5.0 → 1.5.2

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
@@ -169,8 +169,8 @@ Enabled via `climaybe init` prompt (`Enable preview + cleanup workflows?`).
169
169
 
170
170
  | Workflow | Trigger | What it does |
171
171
  |----------|---------|-------------|
172
- | `pr-update.yml` | PR opened/synchronize/reopened | Shares draft theme, renames with `-PR<number>`, comments preview + customize URLs |
173
- | `pr-close.yml` | PR closed | Deletes matching preview themes and comments deleted count + names |
172
+ | `pr-update.yml` | PR opened/synchronize/reopened (base: main, staging, develop, staging-*, live-*) | Shares draft theme, renames with `-PR<number>`, comments preview + customize URLs; uses default store for main/staging/develop, or the store for staging-&lt;alias&gt;/live-&lt;alias&gt; |
173
+ | `pr-close.yml` | PR closed (same branch set) | Deletes matching preview themes and comments deleted count + names |
174
174
  | `reusable-share-theme.yml` | workflow_call | Shares Shopify draft theme and returns `theme_id` |
175
175
  | `reusable-rename-theme.yml` | workflow_call | Renames shared theme to include `PR<number>` (fails job on rename failure) |
176
176
  | `reusable-comment-on-pr.yml` | workflow_call | Posts preview comment including Customize URL |
@@ -232,7 +232,7 @@ Add the following secrets to your GitHub repository (or use **GitLab CI/CD varia
232
232
 
233
233
  **Store URL:** During `climaybe init` (or `add-store`), store URL secret(s) are set from your configured store domain(s); you are only prompted for the theme token.
234
234
 
235
- **Multi-store:** Per-store secrets `SHOPIFY_STORE_URL_<ALIAS>` and `SHOPIFY_THEME_ACCESS_TOKEN_<ALIAS>` — the URL is set from config; you must provide the theme token per store. `<ALIAS>` is uppercase with hyphens as underscores (e.g. `voldt-norway` → `SHOPIFY_STORE_URL_VOLDT_NORWAY`).
235
+ **Multi-store:** Per-store secrets `SHOPIFY_STORE_URL_<ALIAS>` and `SHOPIFY_THEME_ACCESS_TOKEN_<ALIAS>` — the URL is set from config; you must provide the theme token per store. `<ALIAS>` is uppercase with hyphens as underscores (e.g. `voldt-norway` → `SHOPIFY_STORE_URL_VOLDT_NORWAY`). Preview and cleanup: for PRs targeting **main**, **staging**, or **develop** the workflows use the **default store** (from `config.default_store` or first in `config.stores`); for PRs targeting **staging-&lt;alias&gt;** or **live-&lt;alias&gt;** they use that store’s credentials. Set either the plain `SHOPIFY_*` secrets or the `_<ALIAS>` pair for each store you use in preview.
236
236
 
237
237
  ## Directory Structure (Multi-store)
238
238
 
package/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 1.5.0
1
+ 1.5.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Shopify CI/CD CLI — scaffolds workflows, branch strategy, and store config for single-store and multi-store theme repos",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,6 +24,7 @@ jobs:
24
24
  - uses: actions/checkout@v4
25
25
  with:
26
26
  token: ${{ secrets.GITHUB_TOKEN }}
27
+ fetch-depth: 2
27
28
 
28
29
  - name: Skip if this is a sync commit
29
30
  id: gate
@@ -41,6 +42,29 @@ jobs:
41
42
  echo "skip=false" >> $GITHUB_OUTPUT
42
43
  fi
43
44
 
45
+ - name: Keep store-specific paths (e.g. locales) after main sync
46
+ if: steps.gate.outputs.skip != 'true'
47
+ run: |
48
+ # After a main→staging merge, restore store-specific paths from the pre-merge commit
49
+ # so they are not overwritten by main. Only run when paths exist (no pathspec error).
50
+ if [ ! -d "locales" ]; then
51
+ echo "No locales/ directory, skipping."
52
+ exit 0
53
+ fi
54
+ PARENT=$(git rev-parse HEAD^1 2>/dev/null || echo "")
55
+ if [ -z "$PARENT" ]; then
56
+ echo "Not a merge commit or shallow clone, skipping."
57
+ exit 0
58
+ fi
59
+ find locales -name "*.json" 2>/dev/null | while read -r f; do
60
+ git checkout "$PARENT" -- "$f" 2>/dev/null || true
61
+ done
62
+ if git diff --quiet; then
63
+ echo "No store-specific locale changes to restore."
64
+ else
65
+ echo "Restored store-specific locale files from pre-merge commit."
66
+ fi
67
+
44
68
  - name: Extract store alias from branch name
45
69
  if: steps.gate.outputs.skip != 'true'
46
70
  id: alias
@@ -6,17 +6,77 @@ name: PR Close
6
6
  on:
7
7
  pull_request:
8
8
  types: [closed]
9
- branches: [main, staging, develop]
9
+ branches: [main, staging, develop, 'staging-*', 'live-*']
10
10
 
11
11
  jobs:
12
12
  extract-pr-number:
13
13
  uses: ./.github/workflows/reusable-extract-pr-number.yml
14
14
 
15
+ resolve-store:
16
+ runs-on: ubuntu-latest
17
+ outputs:
18
+ store_alias_secret: ${{ steps.resolve.outputs.alias_secret }}
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Resolve store alias from base branch
24
+ id: resolve
25
+ env:
26
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
27
+ run: |
28
+ if [[ -n "$BASE_REF" && ("$BASE_REF" == staging-* || "$BASE_REF" == live-*) ]]; then
29
+ ALIAS="${BASE_REF#staging-}"
30
+ ALIAS="${ALIAS#live-}"
31
+ else
32
+ ALIAS=$(node -e "
33
+ const fs = require('fs');
34
+ const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
35
+ const stores = pkg?.config?.stores || {};
36
+ const defaultStoreRaw = pkg?.config?.default_store;
37
+ const normalize = (v) => String(v || '')
38
+ .toLowerCase()
39
+ .replace(/^https?:\\/\\//, '')
40
+ .replace(/\\/.*$/, '');
41
+ const defaultStore = normalize(defaultStoreRaw);
42
+ let alias = '';
43
+ if (defaultStore) {
44
+ for (const [k, d] of Object.entries(stores)) {
45
+ if (normalize(d) === defaultStore) {
46
+ alias = k;
47
+ break;
48
+ }
49
+ }
50
+ }
51
+ if (!alias) {
52
+ alias = Object.keys(stores)[0] || '';
53
+ }
54
+ if (!alias && defaultStore) {
55
+ const subdomain = defaultStore.split('.')[0] || 'default';
56
+ alias = subdomain.replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-') || 'default';
57
+ }
58
+ if (!alias) {
59
+ alias = 'default';
60
+ }
61
+ process.stdout.write(alias);
62
+ ")
63
+ fi
64
+
65
+ if [ -z "$ALIAS" ]; then
66
+ echo "Could not resolve store alias from package.json config or base branch."
67
+ exit 1
68
+ fi
69
+
70
+ ALIAS_SECRET=$(echo "$ALIAS" | tr '[:lower:]-' '[:upper:]_')
71
+ echo "alias=$ALIAS" >> $GITHUB_OUTPUT
72
+ echo "alias_secret=$ALIAS_SECRET" >> $GITHUB_OUTPUT
73
+
15
74
  cleanup-theme:
16
- needs: extract-pr-number
75
+ needs: [extract-pr-number, resolve-store]
17
76
  uses: ./.github/workflows/reusable-cleanup-themes.yml
18
77
  with:
19
78
  pr_number: ${{ needs.extract-pr-number.outputs.pr_number }}
79
+ store_alias_secret: ${{ needs.resolve-store.outputs.store_alias_secret }}
20
80
  secrets: inherit
21
81
 
22
82
  comment-on-pr:
@@ -7,7 +7,7 @@ name: PR Update
7
7
  on:
8
8
  pull_request:
9
9
  types: [opened, synchronize, reopened]
10
- branches: [main, staging, develop]
10
+ branches: [main, staging, develop, 'staging-*', 'live-*']
11
11
 
12
12
  jobs:
13
13
  extract-pr-number:
@@ -22,40 +22,48 @@ jobs:
22
22
  - name: Checkout code
23
23
  uses: actions/checkout@v4
24
24
 
25
- - name: Resolve default store alias
25
+ - name: Resolve store alias from base branch
26
26
  id: resolve
27
+ env:
28
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
27
29
  run: |
28
- ALIAS=$(node -e "
29
- const fs = require('fs');
30
- const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
31
- const stores = pkg?.config?.stores || {};
32
- const defaultStoreRaw = pkg?.config?.default_store;
33
- const normalize = (v) => String(v || '')
34
- .toLowerCase()
35
- .replace(/^https?:\\/\\//, '')
36
- .replace(/\\/.*$/, '');
37
- const defaultStore = normalize(defaultStoreRaw);
38
- let alias = '';
39
- if (defaultStore) {
40
- for (const [k, d] of Object.entries(stores)) {
41
- if (normalize(d) === defaultStore) {
42
- alias = k;
43
- break;
30
+ # On staging-<alias> or live-<alias> PRs, use that store; otherwise default store
31
+ if [[ -n "$BASE_REF" && ("$BASE_REF" == staging-* || "$BASE_REF" == live-*) ]]; then
32
+ ALIAS="${BASE_REF#staging-}"
33
+ ALIAS="${ALIAS#live-}"
34
+ else
35
+ ALIAS=$(node -e "
36
+ const fs = require('fs');
37
+ const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
38
+ const stores = pkg?.config?.stores || {};
39
+ const defaultStoreRaw = pkg?.config?.default_store;
40
+ const normalize = (v) => String(v || '')
41
+ .toLowerCase()
42
+ .replace(/^https?:\\/\\//, '')
43
+ .replace(/\\/.*$/, '');
44
+ const defaultStore = normalize(defaultStoreRaw);
45
+ let alias = '';
46
+ if (defaultStore) {
47
+ for (const [k, d] of Object.entries(stores)) {
48
+ if (normalize(d) === defaultStore) {
49
+ alias = k;
50
+ break;
51
+ }
44
52
  }
45
53
  }
46
- }
47
- if (!alias) {
48
- alias = Object.keys(stores)[0] || '';
49
- }
50
- if (!alias && defaultStore) {
51
- const subdomain = defaultStore.split('.')[0] || 'default';
52
- alias = subdomain.replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-') || 'default';
53
- }
54
- if (!alias) {
55
- alias = 'default';
56
- }
57
- process.stdout.write(alias);
58
- ")
54
+ if (!alias) {
55
+ alias = Object.keys(stores)[0] || '';
56
+ }
57
+ if (!alias && defaultStore) {
58
+ const subdomain = defaultStore.split('.')[0] || 'default';
59
+ alias = subdomain.replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-') || 'default';
60
+ }
61
+ if (!alias) {
62
+ alias = 'default';
63
+ }
64
+ process.stdout.write(alias);
65
+ ")
66
+ fi
59
67
 
60
68
  if [ -z "$ALIAS" ]; then
61
69
  echo "Could not resolve default store alias from package.json config."
@@ -87,10 +95,11 @@ jobs:
87
95
  echo "Shopify credentials are configured for alias: ${{ steps.resolve.outputs.alias }}"
88
96
 
89
97
  cleanup-themes:
90
- needs: extract-pr-number
98
+ needs: [extract-pr-number, validate-environment]
91
99
  uses: ./.github/workflows/reusable-cleanup-themes.yml
92
100
  with:
93
101
  pr_number: ${{ needs.extract-pr-number.outputs.pr_number }}
102
+ store_alias_secret: ${{ needs.validate-environment.outputs.store_alias_secret }}
94
103
  secrets: inherit
95
104
 
96
105
  share-theme:
@@ -99,9 +108,8 @@ jobs:
99
108
  with:
100
109
  pr_number: ${{ needs.extract-pr-number.outputs.pr_number }}
101
110
  store_alias: ${{ needs.validate-environment.outputs.store_alias }}
102
- secrets:
103
- SHOPIFY_STORE_URL: ${{ secrets[format('SHOPIFY_STORE_URL_{0}', needs.validate-environment.outputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
104
- SHOPIFY_THEME_ACCESS_TOKEN: ${{ secrets[format('SHOPIFY_THEME_ACCESS_TOKEN_{0}', needs.validate-environment.outputs.store_alias_secret)] || secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
111
+ store_alias_secret: ${{ needs.validate-environment.outputs.store_alias_secret }}
112
+ secrets: inherit
105
113
 
106
114
  rename-theme:
107
115
  needs: [share-theme, extract-pr-number, validate-environment]
@@ -111,9 +119,8 @@ jobs:
111
119
  theme_name: ${{ needs.share-theme.outputs.theme_name }}
112
120
  pr_number: ${{ needs.extract-pr-number.outputs.pr_number }}
113
121
  store_alias: ${{ needs.validate-environment.outputs.store_alias }}
114
- secrets:
115
- SHOPIFY_STORE_URL: ${{ secrets[format('SHOPIFY_STORE_URL_{0}', needs.validate-environment.outputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
116
- SHOPIFY_THEME_ACCESS_TOKEN: ${{ secrets[format('SHOPIFY_THEME_ACCESS_TOKEN_{0}', needs.validate-environment.outputs.store_alias_secret)] || secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
122
+ store_alias_secret: ${{ needs.validate-environment.outputs.store_alias_secret }}
123
+ secrets: inherit
117
124
 
118
125
  comment-on-pr:
119
126
  needs: [share-theme, rename-theme, extract-pr-number, validate-environment]
@@ -123,5 +130,5 @@ jobs:
123
130
  share_output: ${{ needs.share-theme.outputs.share_output }}
124
131
  pr_number: ${{ needs.extract-pr-number.outputs.pr_number_unpadded }}
125
132
  store_alias: ${{ needs.validate-environment.outputs.store_alias }}
126
- secrets:
127
- SHOPIFY_STORE_URL: ${{ secrets[format('SHOPIFY_STORE_URL_{0}', needs.validate-environment.outputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
133
+ store_alias_secret: ${{ needs.validate-environment.outputs.store_alias_secret }}
134
+ secrets: inherit
@@ -7,6 +7,10 @@ on:
7
7
  required: true
8
8
  type: string
9
9
  description: "PR number to clean up themes for"
10
+ store_alias_secret:
11
+ required: false
12
+ type: string
13
+ description: "Upper snake-case alias for scoped secret (e.g. VOLDT_STAGING). If set, uses SHOPIFY_*_<this>; else uses SHOPIFY_STORE_URL / SHOPIFY_THEME_ACCESS_TOKEN."
10
14
  outputs:
11
15
  deleted_count:
12
16
  description: "Number of deleted themes"
@@ -28,13 +32,16 @@ jobs:
28
32
  - name: Cleanup PR preview themes
29
33
  id: cleanup
30
34
  env:
31
- SHOPIFY_STORE_URL: ${{ secrets.SHOPIFY_STORE_URL }}
32
- SHOPIFY_THEME_ACCESS_TOKEN: ${{ secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
35
+ SHOPIFY_STORE_URL: ${{ inputs.store_alias_secret && secrets[format('SHOPIFY_STORE_URL_{0}', inputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
36
+ SHOPIFY_THEME_ACCESS_TOKEN: ${{ inputs.store_alias_secret && secrets[format('SHOPIFY_THEME_ACCESS_TOKEN_{0}', inputs.store_alias_secret)] || secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
33
37
  PR_NUMBER: ${{ inputs.pr_number }}
34
38
  run: |
35
39
  if [ -z "$SHOPIFY_STORE_URL" ] || [ -z "$SHOPIFY_THEME_ACCESS_TOKEN" ]; then
36
- echo "Missing Shopify secrets."
37
- exit 1
40
+ 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
44
+ exit 0
38
45
  fi
39
46
 
40
47
  THEME_LIST=$(shopify theme list \
@@ -18,9 +18,7 @@ on:
18
18
  store_alias_secret:
19
19
  required: false
20
20
  type: string
21
- secrets:
22
- SHOPIFY_STORE_URL:
23
- required: false
21
+ description: "Upper snake-case alias for scoped secret (e.g. VOLDT_STAGING). If set, uses SHOPIFY_STORE_URL_<this>; else uses SHOPIFY_STORE_URL."
24
22
 
25
23
  jobs:
26
24
  comment:
@@ -29,7 +27,7 @@ jobs:
29
27
  - name: Post preview comment
30
28
  uses: actions/github-script@v7
31
29
  env:
32
- SHOPIFY_STORE_URL: ${{ secrets.SHOPIFY_STORE_URL }}
30
+ SHOPIFY_STORE_URL: ${{ inputs.store_alias_secret && secrets[format('SHOPIFY_STORE_URL_{0}', inputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
33
31
  THEME_ID: ${{ inputs.theme_id }}
34
32
  SHARE_OUTPUT: ${{ inputs.share_output }}
35
33
  PR_NUMBER: ${{ inputs.pr_number }}
@@ -22,12 +22,7 @@ on:
22
22
  store_alias_secret:
23
23
  required: false
24
24
  type: string
25
- description: "Upper snake-case alias for scoped secret lookup"
26
- secrets:
27
- SHOPIFY_STORE_URL:
28
- required: false
29
- SHOPIFY_THEME_ACCESS_TOKEN:
30
- required: false
25
+ description: "Upper snake-case alias for scoped secret (e.g. VOLDT_STAGING). If set, uses SHOPIFY_*_<this>; else uses SHOPIFY_STORE_URL / SHOPIFY_THEME_ACCESS_TOKEN."
31
26
 
32
27
  jobs:
33
28
  rename:
@@ -38,8 +33,8 @@ jobs:
38
33
 
39
34
  - name: Rename theme
40
35
  env:
41
- SHOPIFY_STORE_URL: ${{ secrets.SHOPIFY_STORE_URL }}
42
- SHOPIFY_THEME_ACCESS_TOKEN: ${{ secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
36
+ SHOPIFY_STORE_URL: ${{ inputs.store_alias_secret && secrets[format('SHOPIFY_STORE_URL_{0}', inputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
37
+ SHOPIFY_THEME_ACCESS_TOKEN: ${{ inputs.store_alias_secret && secrets[format('SHOPIFY_THEME_ACCESS_TOKEN_{0}', inputs.store_alias_secret)] || secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
43
38
  THEME_ID: ${{ inputs.theme_id }}
44
39
  THEME_NAME: ${{ inputs.theme_name }}
45
40
  PR_NUMBER: ${{ inputs.pr_number }}
@@ -14,7 +14,7 @@ on:
14
14
  store_alias_secret:
15
15
  required: false
16
16
  type: string
17
- description: "Upper snake-case alias for scoped secret lookup"
17
+ description: "Upper snake-case alias for scoped secret (e.g. VOLDT_STAGING). If set, uses SHOPIFY_*_<this>; else uses SHOPIFY_STORE_URL / SHOPIFY_THEME_ACCESS_TOKEN."
18
18
  outputs:
19
19
  theme_id:
20
20
  description: "Shared theme ID"
@@ -25,11 +25,6 @@ on:
25
25
  share_output:
26
26
  description: "Raw share command output"
27
27
  value: ${{ jobs.share.outputs.share_output }}
28
- secrets:
29
- SHOPIFY_STORE_URL:
30
- required: false
31
- SHOPIFY_THEME_ACCESS_TOKEN:
32
- required: false
33
28
 
34
29
  jobs:
35
30
  share:
@@ -55,8 +50,8 @@ jobs:
55
50
  - name: Share theme
56
51
  id: share
57
52
  env:
58
- SHOPIFY_STORE_URL: ${{ secrets.SHOPIFY_STORE_URL }}
59
- SHOPIFY_THEME_ACCESS_TOKEN: ${{ secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
53
+ SHOPIFY_STORE_URL: ${{ inputs.store_alias_secret && secrets[format('SHOPIFY_STORE_URL_{0}', inputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
54
+ SHOPIFY_THEME_ACCESS_TOKEN: ${{ inputs.store_alias_secret && secrets[format('SHOPIFY_THEME_ACCESS_TOKEN_{0}', inputs.store_alias_secret)] || secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
60
55
  run: |
61
56
  if [ -z "$SHOPIFY_STORE_URL" ] || [ -z "$SHOPIFY_THEME_ACCESS_TOKEN" ]; then
62
57
  echo "Missing Shopify secrets."