climaybe 1.4.3 → 1.5.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.
package/README.md CHANGED
@@ -4,15 +4,21 @@ Shopify CI/CD CLI — scaffolds GitHub Actions workflows, branch strategy, and s
4
4
 
5
5
  ## Install
6
6
 
7
+ Install in your theme repo (project-only, no global install):
8
+
7
9
  ```bash
8
- npm install -g climaybe
10
+ cd your-shopify-theme-repo
11
+ npm install -D climaybe
9
12
  ```
10
13
 
14
+ Run commands with `npx climaybe` (or add scripts to your `package.json`).
15
+
11
16
  ## Quick Start
12
17
 
13
18
  ```bash
14
19
  cd your-shopify-theme-repo
15
- climaybe init
20
+ npm install -D climaybe
21
+ npx climaybe init
16
22
  ```
17
23
 
18
24
  The interactive setup will ask for your store URL(s) and configure everything automatically.
@@ -38,7 +44,7 @@ Interactive setup that configures your repo for CI/CD.
38
44
  Add a new store to an existing setup.
39
45
 
40
46
  ```bash
41
- climaybe add-store
47
+ npx climaybe add-store
42
48
  ```
43
49
 
44
50
  - Prompts for new store URL + alias
@@ -51,7 +57,7 @@ climaybe add-store
51
57
  Switch your local dev environment to a specific store (multi-store only).
52
58
 
53
59
  ```bash
54
- climaybe switch voldt-norway
60
+ npx climaybe switch voldt-norway
55
61
  ```
56
62
 
57
63
  Copies `stores/<alias>/` JSON files to the repo root so you can preview that store locally.
@@ -61,17 +67,26 @@ Copies `stores/<alias>/` JSON files to the repo root so you can preview that sto
61
67
  Sync root JSON files back to a store directory (multi-store only).
62
68
 
63
69
  ```bash
64
- climaybe sync voldt-norway
70
+ npx climaybe sync voldt-norway
65
71
  ```
66
72
 
67
73
  If no alias is given, syncs to the default store.
68
74
 
75
+ ### `climaybe ensure-branches`
76
+
77
+ Create missing `staging` and per-store branches (`staging-<alias>`, `live-<alias>`) from your current branch (usually `main`). Use when the repo only has `main` (e.g. after a fresh clone) so the main → staging-&lt;store&gt; sync can run.
78
+
79
+ ```bash
80
+ npx climaybe ensure-branches
81
+ git push origin --all
82
+ ```
83
+
69
84
  ### `climaybe update-workflows`
70
85
 
71
86
  Refresh GitHub Actions workflows from the latest bundled templates.
72
87
 
73
88
  ```bash
74
- climaybe update-workflows
89
+ npx climaybe update-workflows
75
90
  ```
76
91
 
77
92
  Useful after updating the CLI to get the latest workflow improvements.
@@ -217,7 +232,7 @@ Add the following secrets to your GitHub repository (or use **GitLab CI/CD varia
217
232
 
218
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.
219
234
 
220
- **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 workflows use the **default store** (from `config.default_store` or first store in `config.stores`), so set either the plain `SHOPIFY_*` secrets or the `_<ALIAS>` pair for that default store.
221
236
 
222
237
  ## Directory Structure (Multi-store)
223
238
 
package/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 1.4.3
1
+ 1.5.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "1.4.3",
3
+ "version": "1.5.1",
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": {
@@ -0,0 +1,52 @@
1
+ import pc from 'picocolors';
2
+ import { readConfig } from '../lib/config.js';
3
+ import {
4
+ isGitRepo,
5
+ currentBranch,
6
+ ensureStagingBranch,
7
+ createStoreBranches,
8
+ } from '../lib/git.js';
9
+
10
+ /**
11
+ * Create missing staging and per-store branches from current HEAD.
12
+ * Use when the repo only has main (e.g. after clone) so the main → staging-<store> sync can run.
13
+ */
14
+ export async function ensureBranchesCommand() {
15
+ console.log(pc.bold('\n climaybe — Ensure Branches\n'));
16
+
17
+ const config = readConfig();
18
+ if (!config?.stores) {
19
+ console.log(pc.red(' No climaybe config found. Run "climaybe init" first.\n'));
20
+ return;
21
+ }
22
+
23
+ if (!isGitRepo()) {
24
+ console.log(pc.red(' Not a git repository. Run "git init" or clone the repo first.\n'));
25
+ return;
26
+ }
27
+
28
+ const branch = currentBranch();
29
+ const aliases = Object.keys(config.stores);
30
+ const mode = aliases.length > 1 ? 'multi' : 'single';
31
+
32
+ console.log(pc.dim(` Current branch: ${branch}`));
33
+ console.log(pc.dim(` Mode: ${mode}-store (${aliases.length} store(s))\n`));
34
+
35
+ ensureStagingBranch();
36
+
37
+ if (mode === 'multi') {
38
+ for (const alias of aliases) {
39
+ createStoreBranches(alias);
40
+ }
41
+ }
42
+
43
+ console.log(pc.bold(pc.green('\n Branches ensured.\n')));
44
+ console.log(pc.dim(' Push them so CI can run:'));
45
+ console.log(pc.dim(' git push origin staging'));
46
+ if (mode === 'multi') {
47
+ for (const alias of aliases) {
48
+ console.log(pc.dim(` git push origin staging-${alias} live-${alias}`));
49
+ }
50
+ }
51
+ console.log(pc.dim(' Or push all at once: git push origin --all\n'));
52
+ }
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ import { addStoreCommand } from './commands/add-store.js';
4
4
  import { switchCommand } from './commands/switch.js';
5
5
  import { syncCommand } from './commands/sync.js';
6
6
  import { updateWorkflowsCommand } from './commands/update-workflows.js';
7
+ import { ensureBranchesCommand } from './commands/ensure-branches.js';
7
8
 
8
9
  /**
9
10
  * Create the CLI program (for testing and for run).
@@ -51,6 +52,11 @@ export function createProgram(version = '0.0.0', packageDir = '') {
51
52
  .description('Refresh GitHub Actions workflows from latest bundled templates')
52
53
  .action(updateWorkflowsCommand);
53
54
 
55
+ program
56
+ .command('ensure-branches')
57
+ .description('Create missing staging and per-store branches from current HEAD (then push)')
58
+ .action(ensureBranchesCommand);
59
+
54
60
  return program;
55
61
  }
56
62
 
@@ -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
@@ -12,12 +12,66 @@ 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 default store alias
24
+ id: resolve
25
+ run: |
26
+ ALIAS=$(node -e "
27
+ const fs = require('fs');
28
+ const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
29
+ const stores = pkg?.config?.stores || {};
30
+ const defaultStoreRaw = pkg?.config?.default_store;
31
+ const normalize = (v) => String(v || '')
32
+ .toLowerCase()
33
+ .replace(/^https?:\\/\\//, '')
34
+ .replace(/\\/.*$/, '');
35
+ const defaultStore = normalize(defaultStoreRaw);
36
+ let alias = '';
37
+ if (defaultStore) {
38
+ for (const [k, d] of Object.entries(stores)) {
39
+ if (normalize(d) === defaultStore) {
40
+ alias = k;
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ if (!alias) {
46
+ alias = Object.keys(stores)[0] || '';
47
+ }
48
+ if (!alias && defaultStore) {
49
+ const subdomain = defaultStore.split('.')[0] || 'default';
50
+ alias = subdomain.replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-') || 'default';
51
+ }
52
+ if (!alias) {
53
+ alias = 'default';
54
+ }
55
+ process.stdout.write(alias);
56
+ ")
57
+
58
+ if [ -z "$ALIAS" ]; then
59
+ echo "Could not resolve default store alias from package.json config."
60
+ exit 1
61
+ fi
62
+
63
+ ALIAS_SECRET=$(echo "$ALIAS" | tr '[:lower:]-' '[:upper:]_')
64
+ echo "alias=$ALIAS" >> $GITHUB_OUTPUT
65
+ echo "alias_secret=$ALIAS_SECRET" >> $GITHUB_OUTPUT
66
+
15
67
  cleanup-theme:
16
- needs: extract-pr-number
68
+ needs: [extract-pr-number, resolve-store]
17
69
  uses: ./.github/workflows/reusable-cleanup-themes.yml
18
70
  with:
19
71
  pr_number: ${{ needs.extract-pr-number.outputs.pr_number }}
20
- secrets: inherit
72
+ secrets:
73
+ SHOPIFY_STORE_URL: ${{ secrets[format('SHOPIFY_STORE_URL_{0}', needs.resolve-store.outputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
74
+ SHOPIFY_THEME_ACCESS_TOKEN: ${{ secrets[format('SHOPIFY_THEME_ACCESS_TOKEN_{0}', needs.resolve-store.outputs.store_alias_secret)] || secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
21
75
 
22
76
  comment-on-pr:
23
77
  needs: cleanup-theme
@@ -87,11 +87,13 @@ jobs:
87
87
  echo "Shopify credentials are configured for alias: ${{ steps.resolve.outputs.alias }}"
88
88
 
89
89
  cleanup-themes:
90
- needs: extract-pr-number
90
+ needs: [extract-pr-number, validate-environment]
91
91
  uses: ./.github/workflows/reusable-cleanup-themes.yml
92
92
  with:
93
93
  pr_number: ${{ needs.extract-pr-number.outputs.pr_number }}
94
- secrets: inherit
94
+ secrets:
95
+ SHOPIFY_STORE_URL: ${{ secrets[format('SHOPIFY_STORE_URL_{0}', needs.validate-environment.outputs.store_alias_secret)] || secrets.SHOPIFY_STORE_URL }}
96
+ SHOPIFY_THEME_ACCESS_TOKEN: ${{ secrets[format('SHOPIFY_THEME_ACCESS_TOKEN_{0}', needs.validate-environment.outputs.store_alias_secret)] || secrets.SHOPIFY_THEME_ACCESS_TOKEN }}
95
97
 
96
98
  share-theme:
97
99
  needs: [validate-environment, extract-pr-number, cleanup-themes]
@@ -33,8 +33,11 @@ jobs:
33
33
  PR_NUMBER: ${{ inputs.pr_number }}
34
34
  run: |
35
35
  if [ -z "$SHOPIFY_STORE_URL" ] || [ -z "$SHOPIFY_THEME_ACCESS_TOKEN" ]; then
36
- echo "Missing Shopify secrets."
37
- exit 1
36
+ echo "Missing Shopify secrets; skipping theme cleanup (no themes deleted)."
37
+ echo "deleted_count=0" >> $GITHUB_OUTPUT
38
+ echo "deleted_themes<<DELETED_EOF" >> $GITHUB_OUTPUT
39
+ echo "DELETED_EOF" >> $GITHUB_OUTPUT
40
+ exit 0
38
41
  fi
39
42
 
40
43
  THEME_LIST=$(shopify theme list \