climaybe 1.5.2 → 1.6.1

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.
@@ -1,7 +1,10 @@
1
1
  # climaybe — Main to Staging <store> (Multi-store)
2
- # When a PR is merged into main (from staging), this workflow
3
- # opens and auto-merges PRs from main to each staging-<alias> branch.
4
- # Skips [hotfix-backport] and version-bump commits so multistore-hotfix-to-main syncs are not re-pushed to stores.
2
+ # When main is pushed (e.g. after staging→main merge), syncs main to each staging-<alias>.
3
+ # Root JSON files (config/settings_data.json, templates/*.json, sections/*.json) are ignored:
4
+ # merge brings main in but we immediately restore root from stores/<alias>/ so store-specific data is kept.
5
+ # Runs on every push to main except pure store-sync commits ([stores-to-root], [root-to-stores]), so that:
6
+ # - Version-bump commits: staging-<alias> get the new version.
7
+ # - Hotfix backports (live or staging-<alias> → main): other staging-<alias> get those changes; the store that sent the hotfix is skipped (no need to merge back to the same branch).
5
8
 
6
9
  name: Main to Staging Stores
7
10
 
@@ -15,30 +18,38 @@ concurrency:
15
18
  cancel-in-progress: false
16
19
 
17
20
  jobs:
18
- # Gate: skip hotfix backports and version bump commits
21
+ # Gate: skip only pure store-sync commits. When hotfix-backport, extract source branch alias so we skip merging back into that same store.
19
22
  gate:
20
23
  runs-on: ubuntu-latest
21
24
  outputs:
22
25
  should_run: ${{ steps.check.outputs.should_run }}
26
+ hotfix_source_alias: ${{ steps.check.outputs.hotfix_source_alias }}
23
27
  steps:
24
- - name: Check commit message
28
+ - name: Check commit message and hotfix source
25
29
  id: check
26
30
  run: |
27
31
  COMMIT_MSG="${{ github.event.head_commit.message }}"
28
32
 
29
- if echo "$COMMIT_MSG" | grep -q "\[hotfix-backport\]"; then
30
- echo "Skipping — hotfix backport commit"
31
- echo "should_run=false" >> $GITHUB_OUTPUT
32
- elif echo "$COMMIT_MSG" | grep -q "chore(release): bump version"; then
33
- echo "Skipping — version bump commit"
33
+ if echo "$COMMIT_MSG" | grep -qE "\[skip-store-sync\]|\[stores-to-root\]|\[root-to-stores\]"; then
34
+ echo "Skipping — store sync commit (avoid loop)"
34
35
  echo "should_run=false" >> $GITHUB_OUTPUT
35
- elif echo "$COMMIT_MSG" | grep -q "\[skip-store-sync\]"; then
36
- echo "Skipping — store sync commit"
37
- echo "should_run=false" >> $GITHUB_OUTPUT
38
- else
39
- echo "should_run=true" >> $GITHUB_OUTPUT
36
+ echo "hotfix_source_alias=" >> $GITHUB_OUTPUT
37
+ exit 0
40
38
  fi
41
39
 
40
+ echo "should_run=true" >> $GITHUB_OUTPUT
41
+
42
+ # If hotfix-backport, extract source branch alias (e.g. staging-norway or live-norway → norway) so we skip that store in sync
43
+ HOTFIX_SOURCE_ALIAS=""
44
+ if echo "$COMMIT_MSG" | grep -q "\[hotfix-backport\]"; then
45
+ SOURCE_BRANCH=$(echo "$COMMIT_MSG" | sed -n 's/.*Merge \([^ ]*\) into main.*/\1/p' | head -1)
46
+ if [ -n "$SOURCE_BRANCH" ]; then
47
+ HOTFIX_SOURCE_ALIAS="${SOURCE_BRANCH#staging-}"
48
+ HOTFIX_SOURCE_ALIAS="${HOTFIX_SOURCE_ALIAS#live-}"
49
+ fi
50
+ fi
51
+ echo "hotfix_source_alias=$HOTFIX_SOURCE_ALIAS" >> $GITHUB_OUTPUT
52
+
42
53
  # Read store list from package.json
43
54
  config:
44
55
  needs: gate
@@ -60,7 +71,7 @@ jobs:
60
71
  echo "stores=$STORES" >> $GITHUB_OUTPUT
61
72
  echo "Store aliases: $STORES"
62
73
 
63
- # Create PRs to each staging-<alias> branch
74
+ # Merge main into each staging-<alias>; root JSONs ignored (restored from stores/<alias>/). Skip the store that sent the hotfix (no need to merge back).
64
75
  sync:
65
76
  needs: [gate, config]
66
77
  if: needs.gate.outputs.should_run == 'true'
@@ -75,85 +86,61 @@ jobs:
75
86
  actions: write
76
87
  env:
77
88
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89
+ HOTFIX_SOURCE_ALIAS: ${{ needs.gate.outputs.hotfix_source_alias }}
78
90
  steps:
79
91
  - uses: actions/checkout@v4
80
92
  with:
81
93
  fetch-depth: 0
82
94
 
83
- - name: Sync main to staging-${{ matrix.store }}
95
+ - name: Sync main to staging-${{ matrix.store }} (root JSONs ignored)
84
96
  run: |
85
97
  BRANCH="staging-${{ matrix.store }}"
98
+ ALIAS="${{ matrix.store }}"
99
+ STORE_DIR="stores/${ALIAS}"
100
+
101
+ if [ -n "$HOTFIX_SOURCE_ALIAS" ] && [ "$HOTFIX_SOURCE_ALIAS" = "$ALIAS" ]; then
102
+ echo "Hotfix came from staging-$ALIAS or live-$ALIAS, skipping (no need to merge back to same store)."
103
+ exit 0
104
+ fi
86
105
 
87
- # Check if branch exists
88
106
  if ! git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
89
107
  echo "Branch $BRANCH does not exist on remote, skipping."
90
108
  exit 0
91
109
  fi
92
110
 
93
- # Check for existing open PR
94
- EXISTING_PR=$(gh pr list --base "$BRANCH" --head main --state open --json number --jq '.[0].number' 2>/dev/null || echo "")
111
+ git config user.name "github-actions[bot]"
112
+ git config user.email "github-actions[bot]@users.noreply.github.com"
95
113
 
96
- if [ -n "$EXISTING_PR" ]; then
97
- echo "PR #$EXISTING_PR already exists for main → $BRANCH"
98
- # Update the existing PR by force
99
- gh pr close "$EXISTING_PR" --delete-branch=false 2>/dev/null || true
114
+ git fetch origin "$BRANCH"
115
+ git checkout "$BRANCH"
116
+ git merge origin/main --no-ff -m "Sync main → $BRANCH" || true
117
+ if [ $? -ne 0 ]; then
118
+ echo "Merge had conflicts; aborting. Manual resolution may be needed."
119
+ git merge --abort 2>/dev/null || true
120
+ exit 1
100
121
  fi
101
122
 
102
- # Create PR
103
- PR_URL=$(gh pr create \
104
- --base "$BRANCH" \
105
- --head main \
106
- --title "Sync main $BRANCH" \
107
- --body "Automated sync from main to $BRANCH after staging merge.
108
-
109
- *Generated by climaybe*" 2>/dev/null || echo "")
110
-
111
- if [ -n "$PR_URL" ]; then
112
- echo "Created PR: $PR_URL"
113
-
114
- # Auto-merge
115
- PR_NUM=$(echo "$PR_URL" | grep -oP '\d+$')
116
- if gh pr merge "$PR_NUM" --merge --admin 2>/dev/null; then
117
- echo "Auto-merge succeeded for $BRANCH"
118
-
119
- # Explicitly trigger Stores to Root to avoid missing downstream runs
120
- # when merges are performed by GITHUB_TOKEN-based automation.
121
- gh workflow run "Stores to Root" --ref "$BRANCH" 2>/dev/null || \
122
- echo "Failed to trigger Stores to Root for $BRANCH"
123
-
124
- # Wait for the latest Stores to Root run on this branch to complete,
125
- # then trigger PR to Live to keep ordering deterministic.
126
- for i in $(seq 1 30); do
127
- RUN_STATUS=$(gh run list \
128
- --workflow "Stores to Root" \
129
- --branch "$BRANCH" \
130
- --limit 1 \
131
- --json status,conclusion \
132
- --jq '.[0].status + "|" + (.[0].conclusion // "")' 2>/dev/null || echo "")
133
-
134
- if [ -z "$RUN_STATUS" ]; then
135
- sleep 5
136
- continue
137
- fi
138
-
139
- STATUS="${RUN_STATUS%%|*}"
140
- CONCLUSION="${RUN_STATUS##*|}"
141
-
142
- if [ "$STATUS" = "completed" ]; then
143
- if [ "$CONCLUSION" = "success" ]; then
144
- gh workflow run "PR to Live" --ref "$BRANCH" 2>/dev/null || \
145
- echo "Failed to trigger PR to Live for $BRANCH"
146
- else
147
- echo "Stores to Root did not succeed for $BRANCH (conclusion: $CONCLUSION)"
123
+ # Ignore root JSONs: restore from stores/<alias>/ so main's versions are not kept
124
+ if [ -d "$STORE_DIR" ]; then
125
+ for DIR in config templates sections; do
126
+ SRC="${STORE_DIR}/${DIR}"
127
+ if [ -d "$SRC" ]; then
128
+ find "$SRC" -name "*.json" | while read -r FILE; do
129
+ REL_PATH="${FILE#$STORE_DIR/}"
130
+ if [ "$REL_PATH" = "config/settings_schema.json" ]; then
131
+ continue
148
132
  fi
149
- break
150
- fi
151
-
152
- sleep 5
153
- done
154
- else
155
- echo "Auto-merge failed manual review may be needed."
133
+ mkdir -p "$(dirname "$REL_PATH")"
134
+ cp "$FILE" "$REL_PATH"
135
+ done
136
+ fi
137
+ done
138
+ git add config templates sections 2>/dev/null || true
139
+ if ! git diff --cached --quiet; then
140
+ git commit -m "chore: keep store root JSONs from stores/$ALIAS/ [stores-to-root]"
156
141
  fi
157
- else
158
- echo "No changes to sync for $BRANCH"
159
142
  fi
143
+
144
+ git push origin "$BRANCH" && echo "Pushed $BRANCH" || { echo "Push failed."; exit 1; }
145
+
146
+ gh workflow run "PR to Live" --ref "$BRANCH" 2>/dev/null || echo "Failed to trigger PR to Live for $BRANCH"
@@ -1,8 +1,7 @@
1
1
  # climaybe — Multistore Hotfix to Main
2
- # Automatically syncs direct hotfixes from staging-<store> or live-<store> back to main.
2
+ # Syncs hotfixes from staging-<store> or live-<store> back to main.
3
+ # Only when the push is NOT from main (main→staging merge would create a loop).
3
4
  # Triggers: push to staging-* or live-*; also after Root to Stores completes (live-*).
4
- # Merges the store branch into main with [hotfix-backport] so post-merge-tag runs patch bump.
5
- # main-to-staging-stores skips [hotfix-backport] commits so hotfixes are not re-pushed to stores.
6
5
 
7
6
  name: Multistore Hotfix to Main
8
7
 
@@ -49,8 +48,20 @@ jobs:
49
48
  run: |
50
49
  SOURCE="${{ steps.ref.outputs.branch }}"
51
50
 
52
- MERGE_BASE=$(git merge-base origin/main origin/$SOURCE 2>/dev/null || echo "")
51
+ # Do not backport when this push was a merge FROM main (staging-* or live-*; would loop)
52
+ if [[ "$SOURCE" == staging-* || "$SOURCE" == live-* ]]; then
53
+ MAIN_SHA=$(git rev-parse origin/main 2>/dev/null || echo "")
54
+ if [ -n "$MAIN_SHA" ]; then
55
+ SECOND_PARENT=$(git rev-parse origin/$SOURCE^2 2>/dev/null || echo "")
56
+ if [ "$SECOND_PARENT" = "$MAIN_SHA" ]; then
57
+ echo "needs_backport=false" >> $GITHUB_OUTPUT
58
+ echo "Push is merge from main into $SOURCE; skipping to avoid loop."
59
+ exit 0
60
+ fi
61
+ fi
62
+ fi
53
63
 
64
+ MERGE_BASE=$(git merge-base origin/main origin/$SOURCE 2>/dev/null || echo "")
54
65
  if [ -z "$MERGE_BASE" ]; then
55
66
  echo "needs_backport=false" >> $GITHUB_OUTPUT
56
67
  exit 0
@@ -1,7 +1,7 @@
1
- # climaybe — Root to Stores (Multi-store)
2
- # When direct commits (hotfixes) land on live-* branches,
3
- # syncs root JSON files back to stores/<alias>/ directory.
4
- # This prepares the data for backporting to main.
1
+ # climaybe — Root to Stores / Stores to Root (Multi-store, live-*)
2
+ # Same logic as stores-to-root on staging-*: direction depends on source.
3
+ # Push to live-* from MAIN (merge): copy stores/<alias>/ → root.
4
+ # Push to live-* from ELSEWHERE (direct, Shopify, etc.): copy root → stores/<alias>/.
5
5
 
6
6
  name: Root to Stores
7
7
 
@@ -24,16 +24,30 @@ jobs:
24
24
  - uses: actions/checkout@v4
25
25
  with:
26
26
  token: ${{ secrets.GITHUB_TOKEN }}
27
+ fetch-depth: 0
27
28
 
28
- - name: Skip workflow commits
29
+ - name: Skip if our own sync commit; detect if push from main
29
30
  id: gate
30
31
  run: |
31
32
  COMMIT_MSG="${{ github.event.head_commit.message }}"
32
33
  if echo "$COMMIT_MSG" | grep -qE "\[root-to-stores\]|\[stores-to-root\]|\[hotfix-backport\]|chore\(release\)"; then
33
34
  echo "skip=true" >> $GITHUB_OUTPUT
35
+ echo "from_main=false" >> $GITHUB_OUTPUT
36
+ exit 0
37
+ fi
38
+ # Detect: is this push a merge FROM main? (second parent = main)
39
+ MAIN_SHA=$(git rev-parse origin/main 2>/dev/null || echo "")
40
+ if [ -z "$MAIN_SHA" ]; then
41
+ echo "from_main=false" >> $GITHUB_OUTPUT
34
42
  else
35
- echo "skip=false" >> $GITHUB_OUTPUT
43
+ SECOND_PARENT=$(git rev-parse HEAD^2 2>/dev/null || echo "")
44
+ if [ "$SECOND_PARENT" = "$MAIN_SHA" ]; then
45
+ echo "from_main=true" >> $GITHUB_OUTPUT
46
+ else
47
+ echo "from_main=false" >> $GITHUB_OUTPUT
48
+ fi
36
49
  fi
50
+ echo "skip=false" >> $GITHUB_OUTPUT
37
51
 
38
52
  - name: Extract store alias
39
53
  if: steps.gate.outputs.skip != 'true'
@@ -42,25 +56,59 @@ jobs:
42
56
  BRANCH="${{ github.ref_name }}"
43
57
  ALIAS="${BRANCH#live-}"
44
58
  echo "alias=$ALIAS" >> $GITHUB_OUTPUT
59
+ echo "Store alias: $ALIAS (from_main=${{ steps.gate.outputs.from_main }})"
45
60
 
46
- - name: Sync root to stores/<alias>/
47
- if: steps.gate.outputs.skip != 'true'
61
+ # --- When push is FROM MAIN: stores root ---
62
+ - name: Sync stores/<alias>/ to root (main merged in)
63
+ if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.from_main == 'true'
48
64
  run: |
49
65
  ALIAS="${{ steps.alias.outputs.alias }}"
50
66
  STORE_DIR="stores/${ALIAS}"
67
+ if [ ! -d "$STORE_DIR" ]; then
68
+ echo "Store directory $STORE_DIR does not exist, skipping."
69
+ exit 0
70
+ fi
71
+ for DIR in config templates sections; do
72
+ SRC="${STORE_DIR}/${DIR}"
73
+ if [ -d "$SRC" ]; then
74
+ find "$SRC" -name "*.json" | while read -r FILE; do
75
+ REL_PATH="${FILE#$STORE_DIR/}"
76
+ if [ "$REL_PATH" = "config/settings_schema.json" ]; then
77
+ continue
78
+ fi
79
+ mkdir -p "$(dirname "$REL_PATH")"
80
+ cp "$FILE" "$REL_PATH"
81
+ echo " Copied: $REL_PATH"
82
+ done
83
+ fi
84
+ done
51
85
 
52
- # Ensure store directory exists
53
- mkdir -p "${STORE_DIR}/config" "${STORE_DIR}/templates" "${STORE_DIR}/sections"
86
+ - name: Commit stores→root
87
+ if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.from_main == 'true'
88
+ run: |
89
+ git config user.name "github-actions[bot]"
90
+ git config user.email "github-actions[bot]@users.noreply.github.com"
91
+ git add -A
92
+ if ! git diff --cached --quiet; then
93
+ git commit -m "chore: sync stores/${{ steps.alias.outputs.alias }}/ to root [stores-to-root]"
94
+ git push
95
+ else
96
+ echo "No changes to commit."
97
+ fi
54
98
 
55
- # Sync directories: config, templates, sections
99
+ # --- When push is NOT from main: root stores/<alias>/ ---
100
+ - name: Sync root to stores/<alias>/ (external push)
101
+ if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.from_main == 'false'
102
+ run: |
103
+ ALIAS="${{ steps.alias.outputs.alias }}"
104
+ STORE_DIR="stores/${ALIAS}"
105
+ mkdir -p "${STORE_DIR}/config" "${STORE_DIR}/templates" "${STORE_DIR}/sections"
56
106
  for DIR in config templates sections; do
57
107
  if [ -d "$DIR" ]; then
58
108
  find "$DIR" -name "*.json" | while read -r FILE; do
59
- # Skip excluded files
60
109
  if [ "$FILE" = "config/settings_schema.json" ]; then
61
110
  continue
62
111
  fi
63
-
64
112
  DEST="${STORE_DIR}/${FILE}"
65
113
  mkdir -p "$(dirname "$DEST")"
66
114
  cp "$FILE" "$DEST"
@@ -69,12 +117,11 @@ jobs:
69
117
  fi
70
118
  done
71
119
 
72
- - name: Commit changes
73
- if: steps.gate.outputs.skip != 'true'
120
+ - name: Commit root→stores
121
+ if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.from_main == 'false'
74
122
  run: |
75
123
  git config user.name "github-actions[bot]"
76
124
  git config user.email "github-actions[bot]@users.noreply.github.com"
77
-
78
125
  git add -A
79
126
  if ! git diff --cached --quiet; then
80
127
  git commit -m "chore: sync root to stores/${{ steps.alias.outputs.alias }}/ [root-to-stores]"
@@ -1,6 +1,7 @@
1
- # climaybe — Stores to Root (Multi-store)
2
- # When a push happens on staging-* branches (e.g., after main sync),
3
- # copies JSON files from stores/<alias>/ to the repo root.
1
+ # climaybe — Stores to Root / Root to Stores (Multi-store, staging-*)
2
+ # Push to staging-* from MAIN (merge): copy stores/<alias>/ → root (store-specific on top of main).
3
+ # Push to staging-* from ELSEWHERE (Shopify, direct, feature branch): copy root → stores/<alias>/
4
+ # so store-specific JSON changes are persisted, then hotfix-to-main can backport.
4
5
 
5
6
  name: Stores to Root
6
7
 
@@ -24,80 +25,80 @@ jobs:
24
25
  - uses: actions/checkout@v4
25
26
  with:
26
27
  token: ${{ secrets.GITHUB_TOKEN }}
27
- fetch-depth: 2
28
+ fetch-depth: 0
28
29
 
29
- - name: Skip if this is a sync commit
30
+ - name: Skip if this is our own sync commit
30
31
  id: gate
31
32
  run: |
32
- if [ "${{ github.event_name }}" = "push" ]; then
33
- COMMIT_MSG="${{ github.event.head_commit.message }}"
34
- if echo "$COMMIT_MSG" | grep -q "\[stores-to-root\]"; then
35
- echo "skip=true" >> $GITHUB_OUTPUT
33
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
34
+ echo "skip=false" >> $GITHUB_OUTPUT
35
+ echo "from_main=false" >> $GITHUB_OUTPUT
36
+ exit 0
37
+ fi
38
+ COMMIT_MSG="${{ github.event.head_commit.message }}"
39
+ if echo "$COMMIT_MSG" | grep -qE "\[stores-to-root\]|\[root-to-stores\]"; then
40
+ echo "skip=true" >> $GITHUB_OUTPUT
41
+ echo "from_main=false" >> $GITHUB_OUTPUT
42
+ exit 0
43
+ fi
44
+ # Detect: is this push a merge FROM main? (merge commit with second parent = main)
45
+ MAIN_SHA=$(git rev-parse origin/main 2>/dev/null || echo "")
46
+ if [ -z "$MAIN_SHA" ]; then
47
+ echo "from_main=false" >> $GITHUB_OUTPUT
48
+ else
49
+ SECOND_PARENT=$(git rev-parse HEAD^2 2>/dev/null || echo "")
50
+ if [ "$SECOND_PARENT" = "$MAIN_SHA" ]; then
51
+ echo "from_main=true" >> $GITHUB_OUTPUT
36
52
  else
37
- echo "skip=false" >> $GITHUB_OUTPUT
53
+ echo "from_main=false" >> $GITHUB_OUTPUT
38
54
  fi
39
- else
40
- # Manual/API dispatch should always run for the selected branch ref.
41
- echo "workflow_dispatch detected, bypassing sync-commit gate."
42
- echo "skip=false" >> $GITHUB_OUTPUT
43
55
  fi
56
+ echo "skip=false" >> $GITHUB_OUTPUT
44
57
 
45
- - name: Keep store-specific paths (e.g. locales) after main sync
58
+ - name: Extract store alias from branch name
46
59
  if: steps.gate.outputs.skip != 'true'
60
+ id: alias
61
+ run: |
62
+ BRANCH="${{ github.ref_name }}"
63
+ ALIAS="${BRANCH#staging-}"
64
+ echo "alias=$ALIAS" >> $GITHUB_OUTPUT
65
+ echo "Store alias: $ALIAS (from_main=${{ steps.gate.outputs.from_main }})"
66
+
67
+ # --- When push is FROM MAIN: stores → root, and restore store-specific locales ---
68
+ - name: Keep store-specific paths (e.g. locales) after main sync
69
+ if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.from_main == 'true'
47
70
  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
71
  if [ ! -d "locales" ]; then
51
- echo "No locales/ directory, skipping."
52
72
  exit 0
53
73
  fi
54
74
  PARENT=$(git rev-parse HEAD^1 2>/dev/null || echo "")
55
75
  if [ -z "$PARENT" ]; then
56
- echo "Not a merge commit or shallow clone, skipping."
57
76
  exit 0
58
77
  fi
59
78
  find locales -name "*.json" 2>/dev/null | while read -r f; do
60
79
  git checkout "$PARENT" -- "$f" 2>/dev/null || true
61
80
  done
62
- if git diff --quiet; then
63
- echo "No store-specific locale changes to restore."
64
- else
81
+ if ! git diff --quiet; then
65
82
  echo "Restored store-specific locale files from pre-merge commit."
66
83
  fi
67
84
 
68
- - name: Extract store alias from branch name
69
- if: steps.gate.outputs.skip != 'true'
70
- id: alias
71
- run: |
72
- BRANCH="${{ github.ref_name }}"
73
- ALIAS="${BRANCH#staging-}"
74
- echo "alias=$ALIAS" >> $GITHUB_OUTPUT
75
- echo "Store alias: $ALIAS"
76
-
77
- - name: Sync stores/<alias>/ to root
78
- if: steps.gate.outputs.skip != 'true'
85
+ - name: Sync stores/<alias>/ to root (main merged in)
86
+ if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.from_main == 'true'
79
87
  run: |
80
88
  ALIAS="${{ steps.alias.outputs.alias }}"
81
89
  STORE_DIR="stores/${ALIAS}"
82
-
83
90
  if [ ! -d "$STORE_DIR" ]; then
84
91
  echo "Store directory $STORE_DIR does not exist, skipping."
85
92
  exit 0
86
93
  fi
87
-
88
- # Sync directories: config, templates, sections
89
94
  for DIR in config templates sections; do
90
95
  SRC="${STORE_DIR}/${DIR}"
91
96
  if [ -d "$SRC" ]; then
92
- # Copy JSON files only, preserving directory structure
93
97
  find "$SRC" -name "*.json" | while read -r FILE; do
94
98
  REL_PATH="${FILE#$STORE_DIR/}"
95
-
96
- # Skip excluded files
97
99
  if [ "$REL_PATH" = "config/settings_schema.json" ]; then
98
100
  continue
99
101
  fi
100
-
101
102
  mkdir -p "$(dirname "$REL_PATH")"
102
103
  cp "$FILE" "$REL_PATH"
103
104
  echo " Copied: $REL_PATH"
@@ -105,12 +106,11 @@ jobs:
105
106
  fi
106
107
  done
107
108
 
108
- - name: Commit changes
109
- if: steps.gate.outputs.skip != 'true'
109
+ - name: Commit stores→root
110
+ if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.from_main == 'true'
110
111
  run: |
111
112
  git config user.name "github-actions[bot]"
112
113
  git config user.email "github-actions[bot]@users.noreply.github.com"
113
-
114
114
  git add -A
115
115
  if ! git diff --cached --quiet; then
116
116
  git commit -m "chore: sync stores/${{ steps.alias.outputs.alias }}/ to root [stores-to-root]"
@@ -118,3 +118,37 @@ jobs:
118
118
  else
119
119
  echo "No changes to commit."
120
120
  fi
121
+
122
+ # --- When push is NOT from main: root → stores/<alias>/ (persist store JSONs) ---
123
+ - name: Sync root to stores/<alias>/ (external push, e.g. Shopify)
124
+ if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.from_main == 'false'
125
+ run: |
126
+ ALIAS="${{ steps.alias.outputs.alias }}"
127
+ STORE_DIR="stores/${ALIAS}"
128
+ mkdir -p "${STORE_DIR}/config" "${STORE_DIR}/templates" "${STORE_DIR}/sections"
129
+ for DIR in config templates sections; do
130
+ if [ -d "$DIR" ]; then
131
+ find "$DIR" -name "*.json" | while read -r FILE; do
132
+ if [ "$FILE" = "config/settings_schema.json" ]; then
133
+ continue
134
+ fi
135
+ DEST="${STORE_DIR}/${FILE}"
136
+ mkdir -p "$(dirname "$DEST")"
137
+ cp "$FILE" "$DEST"
138
+ echo " Synced: $FILE → $DEST"
139
+ done
140
+ fi
141
+ done
142
+
143
+ - name: Commit root→stores
144
+ if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.from_main == 'false'
145
+ run: |
146
+ git config user.name "github-actions[bot]"
147
+ git config user.email "github-actions[bot]@users.noreply.github.com"
148
+ git add -A
149
+ if ! git diff --cached --quiet; then
150
+ git commit -m "chore: sync root to stores/${{ steps.alias.outputs.alias }}/ [root-to-stores]"
151
+ git push
152
+ else
153
+ echo "No changes to commit."
154
+ fi
@@ -1,5 +1,6 @@
1
1
  # climaybe — Version Bump (Reusable Workflow)
2
- # Bumps version in settings_schema.json, creates a git tag, and pushes.
2
+ # Bumps version in settings_schema.json and package.json (if present), creates a git tag, and pushes.
3
+ # Only runs on main (called from post-merge-tag and nightly-hotfix). staging / staging-* / live-* do not run version bump.
3
4
 
4
5
  name: Version Bump
5
6
 
@@ -60,8 +61,25 @@ jobs:
60
61
  fi
61
62
  NEW_VERSION="v${NEW_VERSION}"
62
63
  else
63
- # Get latest tag
64
- LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
64
+ # Get latest tag (or theme_version from settings_schema.json when no tags)
65
+ LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true)
66
+ if [ -z "$LATEST_TAG" ]; then
67
+ SCHEMA="config/settings_schema.json"
68
+ if [ -f "$SCHEMA" ]; then
69
+ LATEST_TAG=$(node -e "
70
+ const fs = require('fs');
71
+ const arr = JSON.parse(fs.readFileSync('$SCHEMA', 'utf-8'));
72
+ const info = arr.find(x => x.name === 'theme_info');
73
+ const v = (info && info.theme_version) ? String(info.theme_version).trim() : '';
74
+ if (!v) process.exit(1);
75
+ let parts = v.replace(/^v/i, '').split('.');
76
+ if (parts.length === 2) parts.push('0');
77
+ if (parts.length < 3) parts = parts.concat(Array(3 - parts.length).fill('0')).slice(0, 3);
78
+ console.log('v' + parts.slice(0, 3).join('.'));
79
+ " 2>/dev/null || true)
80
+ fi
81
+ [ -z "$LATEST_TAG" ] && LATEST_TAG="v0.0.0"
82
+ fi
65
83
  LATEST_TAG="${LATEST_TAG#v}"
66
84
 
67
85
  IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST_TAG"
@@ -82,20 +100,20 @@ jobs:
82
100
  echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
83
101
  echo "New version: $NEW_VERSION"
84
102
 
85
- - name: Update settings_schema.json
103
+ - name: Update settings_schema.json and package.json
104
+ env:
105
+ NEW_VERSION: ${{ steps.bump.outputs.new_version }}
86
106
  run: |
87
- NEW_VERSION="${{ steps.bump.outputs.new_version }}"
107
+ export VERSION_PLAIN="${NEW_VERSION#v}"
88
108
  SCHEMA_FILE="config/settings_schema.json"
89
109
 
90
110
  if [ -f "$SCHEMA_FILE" ]; then
91
- # Update the theme_info version in settings_schema.json
92
111
  node -e "
93
112
  const fs = require('fs');
113
+ const v = process.env.VERSION_PLAIN;
94
114
  const schema = JSON.parse(fs.readFileSync('$SCHEMA_FILE', 'utf-8'));
95
115
  const themeInfo = schema.find(s => s.name === 'theme_info');
96
- if (themeInfo) {
97
- themeInfo.theme_version = '${NEW_VERSION#v}';
98
- }
116
+ if (themeInfo) themeInfo.theme_version = v;
99
117
  fs.writeFileSync('$SCHEMA_FILE', JSON.stringify(schema, null, 2) + '\n');
100
118
  "
101
119
  echo "Updated $SCHEMA_FILE to ${NEW_VERSION}"
@@ -103,6 +121,18 @@ jobs:
103
121
  echo "No $SCHEMA_FILE found, skipping schema update."
104
122
  fi
105
123
 
124
+ if [ -f "package.json" ]; then
125
+ node -e "
126
+ const fs = require('fs');
127
+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
128
+ pkg.version = process.env.VERSION_PLAIN;
129
+ fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
130
+ "
131
+ echo "Updated package.json version to ${VERSION_PLAIN}"
132
+ else
133
+ echo "No package.json found, skipping."
134
+ fi
135
+
106
136
  - name: Commit and tag
107
137
  run: |
108
138
  NEW_VERSION="${{ steps.bump.outputs.new_version }}"