climaybe 1.0.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.
@@ -0,0 +1,84 @@
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.
5
+
6
+ name: Root to Stores
7
+
8
+ on:
9
+ push:
10
+ branches:
11
+ - 'live-*'
12
+
13
+ # Prevent concurrent runs per branch
14
+ concurrency:
15
+ group: root-to-stores-${{ github.ref_name }}
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ sync:
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ contents: write
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ with:
26
+ token: ${{ secrets.GITHUB_TOKEN }}
27
+
28
+ - name: Skip workflow commits
29
+ id: gate
30
+ run: |
31
+ COMMIT_MSG="${{ github.event.head_commit.message }}"
32
+ if echo "$COMMIT_MSG" | grep -qE "\[root-to-stores\]|\[stores-to-root\]|\[hotfix-backport\]|chore\(release\)"; then
33
+ echo "skip=true" >> $GITHUB_OUTPUT
34
+ else
35
+ echo "skip=false" >> $GITHUB_OUTPUT
36
+ fi
37
+
38
+ - name: Extract store alias
39
+ if: steps.gate.outputs.skip != 'true'
40
+ id: alias
41
+ run: |
42
+ BRANCH="${{ github.ref_name }}"
43
+ ALIAS="${BRANCH#live-}"
44
+ echo "alias=$ALIAS" >> $GITHUB_OUTPUT
45
+
46
+ - name: Sync root to stores/<alias>/
47
+ if: steps.gate.outputs.skip != 'true'
48
+ run: |
49
+ ALIAS="${{ steps.alias.outputs.alias }}"
50
+ STORE_DIR="stores/${ALIAS}"
51
+
52
+ # Ensure store directory exists
53
+ mkdir -p "${STORE_DIR}/config" "${STORE_DIR}/templates" "${STORE_DIR}/sections"
54
+
55
+ # Sync directories: config, templates, sections
56
+ for DIR in config templates sections; do
57
+ if [ -d "$DIR" ]; then
58
+ find "$DIR" -name "*.json" | while read -r FILE; do
59
+ # Skip excluded files
60
+ if [ "$FILE" = "config/settings_schema.json" ]; then
61
+ continue
62
+ fi
63
+
64
+ DEST="${STORE_DIR}/${FILE}"
65
+ mkdir -p "$(dirname "$DEST")"
66
+ cp "$FILE" "$DEST"
67
+ echo " Synced: $FILE → $DEST"
68
+ done
69
+ fi
70
+ done
71
+
72
+ - name: Commit changes
73
+ if: steps.gate.outputs.skip != 'true'
74
+ run: |
75
+ git config user.name "github-actions[bot]"
76
+ git config user.email "github-actions[bot]@users.noreply.github.com"
77
+
78
+ git add -A
79
+ if ! git diff --cached --quiet; then
80
+ git commit -m "chore: sync root to stores/${{ steps.alias.outputs.alias }}/ [root-to-stores]"
81
+ git push
82
+ else
83
+ echo "No changes to commit."
84
+ fi
@@ -0,0 +1,89 @@
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.
4
+
5
+ name: Stores to Root
6
+
7
+ on:
8
+ push:
9
+ branches:
10
+ - 'staging-*'
11
+
12
+ # Prevent concurrent runs per branch
13
+ concurrency:
14
+ group: stores-to-root-${{ github.ref_name }}
15
+ cancel-in-progress: false
16
+
17
+ jobs:
18
+ sync:
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ contents: write
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ with:
25
+ token: ${{ secrets.GITHUB_TOKEN }}
26
+
27
+ - name: Skip if this is a sync commit
28
+ id: gate
29
+ run: |
30
+ COMMIT_MSG="${{ github.event.head_commit.message }}"
31
+ if echo "$COMMIT_MSG" | grep -q "\[stores-to-root\]"; then
32
+ echo "skip=true" >> $GITHUB_OUTPUT
33
+ else
34
+ echo "skip=false" >> $GITHUB_OUTPUT
35
+ fi
36
+
37
+ - name: Extract store alias from branch name
38
+ if: steps.gate.outputs.skip != 'true'
39
+ id: alias
40
+ run: |
41
+ BRANCH="${{ github.ref_name }}"
42
+ ALIAS="${BRANCH#staging-}"
43
+ echo "alias=$ALIAS" >> $GITHUB_OUTPUT
44
+ echo "Store alias: $ALIAS"
45
+
46
+ - name: Sync stores/<alias>/ to root
47
+ if: steps.gate.outputs.skip != 'true'
48
+ run: |
49
+ ALIAS="${{ steps.alias.outputs.alias }}"
50
+ STORE_DIR="stores/${ALIAS}"
51
+
52
+ if [ ! -d "$STORE_DIR" ]; then
53
+ echo "Store directory $STORE_DIR does not exist, skipping."
54
+ exit 0
55
+ fi
56
+
57
+ # Sync directories: config, templates, sections
58
+ for DIR in config templates sections; do
59
+ SRC="${STORE_DIR}/${DIR}"
60
+ if [ -d "$SRC" ]; then
61
+ # Copy JSON files only, preserving directory structure
62
+ find "$SRC" -name "*.json" | while read -r FILE; do
63
+ REL_PATH="${FILE#$STORE_DIR/}"
64
+
65
+ # Skip excluded files
66
+ if [ "$REL_PATH" = "config/settings_schema.json" ]; then
67
+ continue
68
+ fi
69
+
70
+ mkdir -p "$(dirname "$REL_PATH")"
71
+ cp "$FILE" "$REL_PATH"
72
+ echo " Copied: $REL_PATH"
73
+ done
74
+ fi
75
+ done
76
+
77
+ - name: Commit changes
78
+ if: steps.gate.outputs.skip != 'true'
79
+ run: |
80
+ git config user.name "github-actions[bot]"
81
+ git config user.email "github-actions[bot]@users.noreply.github.com"
82
+
83
+ git add -A
84
+ if ! git diff --cached --quiet; then
85
+ git commit -m "chore: sync stores/${{ steps.alias.outputs.alias }}/ to root [stores-to-root]"
86
+ git push
87
+ else
88
+ echo "No changes to commit."
89
+ fi
@@ -0,0 +1,108 @@
1
+ # climaybe — AI Changelog Generator (Reusable Workflow)
2
+ # Uses Google Gemini to classify commits and generate a changelog.
3
+
4
+ name: AI Changelog
5
+
6
+ on:
7
+ workflow_call:
8
+ inputs:
9
+ base_ref:
10
+ description: 'Base ref (tag or commit) to collect commits from'
11
+ required: true
12
+ type: string
13
+ head_ref:
14
+ description: 'Head ref to collect commits to (default: HEAD)'
15
+ required: false
16
+ type: string
17
+ default: 'HEAD'
18
+ outputs:
19
+ changelog:
20
+ description: 'Generated changelog in markdown format'
21
+ value: ${{ jobs.generate.outputs.changelog }}
22
+ secrets:
23
+ GEMINI_API_KEY:
24
+ required: true
25
+
26
+ jobs:
27
+ generate:
28
+ runs-on: ubuntu-latest
29
+ outputs:
30
+ changelog: ${{ steps.ai.outputs.changelog }}
31
+
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ with:
35
+ fetch-depth: 0
36
+
37
+ - name: Collect commits
38
+ id: commits
39
+ run: |
40
+ COMMITS=$(git log --pretty=format:"%h %s" ${{ inputs.base_ref }}..${{ inputs.head_ref }} 2>/dev/null || echo "")
41
+ if [ -z "$COMMITS" ]; then
42
+ echo "No commits found between ${{ inputs.base_ref }} and ${{ inputs.head_ref }}"
43
+ echo "commits=" >> $GITHUB_OUTPUT
44
+ exit 0
45
+ fi
46
+ # Escape for JSON
47
+ COMMITS_ESCAPED=$(echo "$COMMITS" | jq -Rs .)
48
+ echo "commits=$COMMITS_ESCAPED" >> $GITHUB_OUTPUT
49
+
50
+ - name: Generate changelog with Gemini
51
+ id: ai
52
+ if: steps.commits.outputs.commits != ''
53
+ env:
54
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
55
+ run: |
56
+ COMMITS=${{ steps.commits.outputs.commits }}
57
+
58
+ PROMPT=$(cat <<'PROMPT_EOF'
59
+ You are a changelog generator for a Shopify theme repository.
60
+ Given the following git commits, classify each into one of these categories:
61
+ - **Features**: New functionality
62
+ - **Fixes**: Bug fixes
63
+ - **Improvements**: Enhancements to existing features
64
+ - **Chore**: Maintenance, refactoring, docs
65
+
66
+ Format the output as a markdown changelog. Group by category.
67
+ Omit empty categories. Write concise, merchant-friendly descriptions.
68
+ Do NOT include commit hashes in the output.
69
+
70
+ Commits:
71
+ PROMPT_EOF
72
+ )
73
+
74
+ PAYLOAD=$(jq -n \
75
+ --arg prompt "$PROMPT" \
76
+ --argjson commits "$COMMITS" \
77
+ '{
78
+ "contents": [{
79
+ "parts": [{"text": ($prompt + "\n" + $commits)}]
80
+ }],
81
+ "generationConfig": {
82
+ "temperature": 0.3,
83
+ "maxOutputTokens": 2048
84
+ }
85
+ }')
86
+
87
+ RESPONSE=$(curl -s -X POST \
88
+ "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${GEMINI_API_KEY}" \
89
+ -H "Content-Type: application/json" \
90
+ -d "$PAYLOAD")
91
+
92
+ CHANGELOG=$(echo "$RESPONSE" | jq -r '.candidates[0].content.parts[0].text // "Changelog generation failed."')
93
+
94
+ # Write to output (multiline)
95
+ {
96
+ echo "changelog<<CHANGELOG_EOF"
97
+ echo "$CHANGELOG"
98
+ echo "CHANGELOG_EOF"
99
+ } >> $GITHUB_OUTPUT
100
+
101
+ - name: Fallback if no commits
102
+ if: steps.commits.outputs.commits == ''
103
+ run: |
104
+ {
105
+ echo "changelog<<CHANGELOG_EOF"
106
+ echo "No changes detected."
107
+ echo "CHANGELOG_EOF"
108
+ } >> $GITHUB_OUTPUT
@@ -0,0 +1,122 @@
1
+ # climaybe — Version Bump (Reusable Workflow)
2
+ # Bumps version in settings_schema.json, creates a git tag, and pushes.
3
+
4
+ name: Version Bump
5
+
6
+ on:
7
+ workflow_call:
8
+ inputs:
9
+ bump_type:
10
+ description: 'Type of version bump: minor or patch'
11
+ required: true
12
+ type: string
13
+ version:
14
+ description: 'Explicit version to set (e.g., v3.2.0). If empty, auto-increments based on bump_type.'
15
+ required: false
16
+ type: string
17
+ default: ''
18
+ changelog:
19
+ description: 'Changelog text for the tag annotation'
20
+ required: false
21
+ type: string
22
+ default: ''
23
+ outputs:
24
+ new_version:
25
+ description: 'The new version tag that was created'
26
+ value: ${{ jobs.bump.outputs.new_version }}
27
+
28
+ jobs:
29
+ bump:
30
+ runs-on: ubuntu-latest
31
+ outputs:
32
+ new_version: ${{ steps.bump.outputs.new_version }}
33
+
34
+ permissions:
35
+ contents: write
36
+
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ with:
40
+ fetch-depth: 0
41
+ token: ${{ secrets.GITHUB_TOKEN }}
42
+
43
+ - name: Configure git
44
+ run: |
45
+ git config user.name "github-actions[bot]"
46
+ git config user.email "github-actions[bot]@users.noreply.github.com"
47
+
48
+ - name: Determine new version
49
+ id: bump
50
+ run: |
51
+ BUMP_TYPE="${{ inputs.bump_type }}"
52
+ EXPLICIT_VERSION="${{ inputs.version }}"
53
+
54
+ if [ -n "$EXPLICIT_VERSION" ]; then
55
+ # Use explicit version, ensure it starts with v
56
+ NEW_VERSION="${EXPLICIT_VERSION#v}"
57
+ NEW_VERSION="v${NEW_VERSION}"
58
+ else
59
+ # Get latest tag
60
+ LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
61
+ LATEST_TAG="${LATEST_TAG#v}"
62
+
63
+ IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST_TAG"
64
+ MAJOR=${MAJOR:-0}
65
+ MINOR=${MINOR:-0}
66
+ PATCH=${PATCH:-0}
67
+
68
+ if [ "$BUMP_TYPE" = "minor" ]; then
69
+ MINOR=$((MINOR + 1))
70
+ PATCH=0
71
+ elif [ "$BUMP_TYPE" = "patch" ]; then
72
+ PATCH=$((PATCH + 1))
73
+ fi
74
+
75
+ NEW_VERSION="v${MAJOR}.${MINOR}.${PATCH}"
76
+ fi
77
+
78
+ echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
79
+ echo "New version: $NEW_VERSION"
80
+
81
+ - name: Update settings_schema.json
82
+ run: |
83
+ NEW_VERSION="${{ steps.bump.outputs.new_version }}"
84
+ SCHEMA_FILE="config/settings_schema.json"
85
+
86
+ if [ -f "$SCHEMA_FILE" ]; then
87
+ # Update the theme_info version in settings_schema.json
88
+ node -e "
89
+ const fs = require('fs');
90
+ const schema = JSON.parse(fs.readFileSync('$SCHEMA_FILE', 'utf-8'));
91
+ const themeInfo = schema.find(s => s.name === 'theme_info');
92
+ if (themeInfo) {
93
+ themeInfo.theme_version = '${NEW_VERSION#v}';
94
+ }
95
+ fs.writeFileSync('$SCHEMA_FILE', JSON.stringify(schema, null, 2) + '\n');
96
+ "
97
+ echo "Updated $SCHEMA_FILE to ${NEW_VERSION}"
98
+ else
99
+ echo "No $SCHEMA_FILE found, skipping schema update."
100
+ fi
101
+
102
+ - name: Commit and tag
103
+ run: |
104
+ NEW_VERSION="${{ steps.bump.outputs.new_version }}"
105
+
106
+ # Stage changes
107
+ git add -A
108
+
109
+ # Only commit if there are changes
110
+ if ! git diff --cached --quiet; then
111
+ git commit -m "chore(release): bump version to ${NEW_VERSION}"
112
+ fi
113
+
114
+ # Create annotated tag
115
+ CHANGELOG="${{ inputs.changelog }}"
116
+ if [ -n "$CHANGELOG" ]; then
117
+ git tag -a "$NEW_VERSION" -m "$CHANGELOG"
118
+ else
119
+ git tag -a "$NEW_VERSION" -m "Release ${NEW_VERSION}"
120
+ fi
121
+
122
+ git push origin HEAD --follow-tags
@@ -0,0 +1,73 @@
1
+ # climaybe — Nightly Hotfix Tagger (Single-store)
2
+ # Runs at 02:00 US Eastern (07:00 UTC) every night.
3
+ # If there are untagged commits on main (not from staging merges),
4
+ # generates a changelog and creates a patch version tag.
5
+
6
+ name: Nightly Hotfix Tag
7
+
8
+ on:
9
+ schedule:
10
+ # 02:00 US Eastern = 07:00 UTC
11
+ - cron: '0 7 * * *'
12
+ # Allow manual trigger for testing
13
+ workflow_dispatch:
14
+
15
+ # Prevent concurrent runs
16
+ concurrency:
17
+ group: nightly-hotfix
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ check:
22
+ runs-on: ubuntu-latest
23
+ outputs:
24
+ has_changes: ${{ steps.check.outputs.has_changes }}
25
+ last_tag: ${{ steps.check.outputs.last_tag }}
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ with:
29
+ fetch-depth: 0
30
+
31
+ - name: Check for untagged commits
32
+ id: check
33
+ run: |
34
+ LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
35
+
36
+ if [ -z "$LATEST_TAG" ]; then
37
+ echo "No tags found, skipping."
38
+ echo "has_changes=false" >> $GITHUB_OUTPUT
39
+ exit 0
40
+ fi
41
+
42
+ echo "last_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
43
+
44
+ # Count commits since last tag, excluding version bump commits
45
+ COMMITS=$(git log ${LATEST_TAG}..HEAD --pretty=format:"%s" | grep -v "chore(release): bump version" | grep -v "\[hotfix-backport\]" || true)
46
+
47
+ if [ -n "$COMMITS" ]; then
48
+ COMMIT_COUNT=$(echo "$COMMITS" | wc -l | tr -d ' ')
49
+ echo "Found $COMMIT_COUNT untagged commit(s) since $LATEST_TAG"
50
+ echo "has_changes=true" >> $GITHUB_OUTPUT
51
+ else
52
+ echo "No untagged hotfix commits since $LATEST_TAG"
53
+ echo "has_changes=false" >> $GITHUB_OUTPUT
54
+ fi
55
+
56
+ changelog:
57
+ needs: check
58
+ if: needs.check.outputs.has_changes == 'true'
59
+ uses: ./.github/workflows/ai-changelog.yml
60
+ with:
61
+ base_ref: ${{ needs.check.outputs.last_tag }}
62
+ head_ref: HEAD
63
+ secrets:
64
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
65
+
66
+ bump:
67
+ needs: [check, changelog]
68
+ if: needs.check.outputs.has_changes == 'true'
69
+ uses: ./.github/workflows/version-bump.yml
70
+ with:
71
+ bump_type: patch
72
+ changelog: ${{ needs.changelog.outputs.changelog }}
73
+ secrets: inherit
@@ -0,0 +1,96 @@
1
+ # climaybe — Post-Merge Tag (Single-store)
2
+ # After a staging → main PR is merged, detects the target version from the PR title
3
+ # and bumps the minor version accordingly.
4
+ # PR title convention: "Release v3.2" or "Release v3.2.0"
5
+
6
+ name: Post-Merge Tag
7
+
8
+ on:
9
+ push:
10
+ branches: [main]
11
+
12
+ # Prevent concurrent version bumps
13
+ concurrency:
14
+ group: post-merge-tag
15
+ cancel-in-progress: false
16
+
17
+ jobs:
18
+ detect:
19
+ runs-on: ubuntu-latest
20
+ outputs:
21
+ is_release: ${{ steps.check.outputs.is_release }}
22
+ version: ${{ steps.check.outputs.version }}
23
+ steps:
24
+ - name: Check if this push is from a merged staging PR
25
+ id: check
26
+ env:
27
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28
+ run: |
29
+ # Check the commit message for merge indicator
30
+ COMMIT_MSG=$(echo "${{ github.event.head_commit.message }}" | head -1)
31
+
32
+ # Skip hotfix backports
33
+ if echo "$COMMIT_MSG" | grep -q "\[hotfix-backport\]"; then
34
+ echo "is_release=false" >> $GITHUB_OUTPUT
35
+ exit 0
36
+ fi
37
+
38
+ # Skip version bump commits from this workflow
39
+ if echo "$COMMIT_MSG" | grep -q "chore(release): bump version"; then
40
+ echo "is_release=false" >> $GITHUB_OUTPUT
41
+ exit 0
42
+ fi
43
+
44
+ # Look for merged PR that targeted main from staging
45
+ PR_NUMBER=$(echo "$COMMIT_MSG" | grep -oP '#\K\d+' | head -1 || true)
46
+
47
+ if [ -n "$PR_NUMBER" ]; then
48
+ PR_DATA=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER 2>/dev/null || echo "")
49
+
50
+ if [ -n "$PR_DATA" ]; then
51
+ HEAD_REF=$(echo "$PR_DATA" | jq -r '.head.ref')
52
+ TITLE=$(echo "$PR_DATA" | jq -r '.title')
53
+
54
+ if [ "$HEAD_REF" = "staging" ]; then
55
+ # Extract version from PR title: "Release v3.2" or "Release v3.2.0"
56
+ VERSION=$(echo "$TITLE" | grep -oP 'v\d+\.\d+(\.\d+)?' || true)
57
+
58
+ if [ -n "$VERSION" ]; then
59
+ # Ensure it has 3 parts
60
+ PARTS=$(echo "$VERSION" | tr '.' '\n' | wc -l)
61
+ if [ "$PARTS" -eq "2" ]; then
62
+ VERSION="${VERSION}.0"
63
+ fi
64
+
65
+ echo "is_release=true" >> $GITHUB_OUTPUT
66
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
67
+ echo "Detected release version: $VERSION"
68
+ exit 0
69
+ fi
70
+ fi
71
+ fi
72
+ fi
73
+
74
+ echo "is_release=false" >> $GITHUB_OUTPUT
75
+
76
+ # Generate changelog for the release
77
+ changelog:
78
+ needs: detect
79
+ if: needs.detect.outputs.is_release == 'true'
80
+ uses: ./.github/workflows/ai-changelog.yml
81
+ with:
82
+ base_ref: ${{ needs.detect.outputs.version }}
83
+ head_ref: HEAD
84
+ secrets:
85
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
86
+
87
+ # Apply version bump
88
+ bump:
89
+ needs: [detect, changelog]
90
+ if: needs.detect.outputs.is_release == 'true'
91
+ uses: ./.github/workflows/version-bump.yml
92
+ with:
93
+ bump_type: minor
94
+ version: ${{ needs.detect.outputs.version }}
95
+ changelog: ${{ needs.changelog.outputs.changelog }}
96
+ secrets: inherit