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.
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # climaybe
2
+
3
+ Shopify CI/CD CLI — scaffolds GitHub Actions workflows, branch strategy, and store config for single-store and multi-store theme repositories.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g climaybe
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ cd your-shopify-theme-repo
15
+ climaybe init
16
+ ```
17
+
18
+ The interactive setup will ask for your store URL(s) and configure everything automatically.
19
+
20
+ ## Commands
21
+
22
+ ### `climaybe init`
23
+
24
+ Interactive setup that configures your repo for CI/CD.
25
+
26
+ 1. Prompts for your store URL (e.g., `voldt-staging.myshopify.com`)
27
+ 2. Extracts subdomain as alias, lets you override
28
+ 3. Asks if you want to add more stores
29
+ 4. Based on store count, sets up **single-store** or **multi-store** mode
30
+ 5. Writes `package.json` config
31
+ 6. Scaffolds GitHub Actions workflows
32
+ 7. Creates git branches and store directories (multi-store)
33
+
34
+ ### `climaybe add-store`
35
+
36
+ Add a new store to an existing setup.
37
+
38
+ ```bash
39
+ climaybe add-store
40
+ ```
41
+
42
+ - Prompts for new store URL + alias
43
+ - Creates `staging-<alias>` and `live-<alias>` branches
44
+ - Creates `stores/<alias>/` directory structure
45
+ - If store count goes from 1 to 2+, automatically migrates from single to multi-store mode
46
+
47
+ ### `climaybe switch <alias>`
48
+
49
+ Switch your local dev environment to a specific store (multi-store only).
50
+
51
+ ```bash
52
+ climaybe switch voldt-norway
53
+ ```
54
+
55
+ Copies `stores/<alias>/` JSON files to the repo root so you can preview that store locally.
56
+
57
+ ### `climaybe sync [alias]`
58
+
59
+ Sync root JSON files back to a store directory (multi-store only).
60
+
61
+ ```bash
62
+ climaybe sync voldt-norway
63
+ ```
64
+
65
+ If no alias is given, syncs to the default store.
66
+
67
+ ### `climaybe update-workflows`
68
+
69
+ Refresh GitHub Actions workflows from the latest bundled templates.
70
+
71
+ ```bash
72
+ climaybe update-workflows
73
+ ```
74
+
75
+ Useful after updating the CLI to get the latest workflow improvements.
76
+
77
+ ## Configuration
78
+
79
+ The CLI writes config into the `config` field of your `package.json`:
80
+
81
+ ```json
82
+ {
83
+ "config": {
84
+ "port": 9295,
85
+ "default_store": "voldt-staging.myshopify.com",
86
+ "stores": {
87
+ "voldt-staging": "voldt-staging.myshopify.com",
88
+ "voldt-norway": "voldt-norway.myshopify.com"
89
+ }
90
+ }
91
+ }
92
+ ```
93
+
94
+ Workflows read this config at runtime — no hardcoded values in YAML files.
95
+
96
+ ## Branch Strategy
97
+
98
+ ### Single-store
99
+
100
+ ```
101
+ staging → main
102
+ ```
103
+
104
+ - `staging` — development branch
105
+ - `main` — production branch
106
+
107
+ ### Multi-store
108
+
109
+ ```
110
+ staging → main → staging-<store> → live-<store>
111
+ ```
112
+
113
+ - `staging` — development branch
114
+ - `main` — shared codebase (not live)
115
+ - `staging-<store>` — per-store staging with store-specific JSON data
116
+ - `live-<store>` — per-store production
117
+
118
+ ## Workflows
119
+
120
+ ### Shared (both modes)
121
+
122
+ | Workflow | Purpose |
123
+ |----------|---------|
124
+ | `ai-changelog.yml` | Reusable workflow. Sends commits to Gemini API, returns classified changelog. |
125
+ | `version-bump.yml` | Reusable workflow. Bumps version in `settings_schema.json`, creates git tag. |
126
+
127
+ ### Single-store
128
+
129
+ | Workflow | Trigger | What it does |
130
+ |----------|---------|-------------|
131
+ | `release-pr-check.yml` | PR from `staging` to `main` | Generates changelog, creates pre-release patch tag, posts PR comment |
132
+ | `post-merge-tag.yml` | Push to `main` (merged PR) | Detects release version from PR title, bumps minor version |
133
+ | `nightly-hotfix.yml` | Cron 02:00 US Eastern | Tags untagged hotfix commits with patch version |
134
+
135
+ ### Multi-store (additional)
136
+
137
+ | Workflow | Trigger | What it does |
138
+ |----------|---------|-------------|
139
+ | `main-to-staging-stores.yml` | Push to `main` | Opens PRs to each `staging-<alias>` branch |
140
+ | `stores-to-root.yml` | Push to `staging-*` | Copies `stores/<alias>/` JSONs to repo root |
141
+ | `pr-to-live.yml` | After stores-to-root | Opens PR from `staging-<alias>` to `live-<alias>` |
142
+ | `root-to-stores.yml` | Push to `live-*` (hotfix) | Syncs root JSONs back to `stores/<alias>/` |
143
+ | `hotfix-backport.yml` | After root-to-stores | Creates backport PR to `main` |
144
+
145
+ ## Versioning
146
+
147
+ - **Release merge** (`staging` → `main`): Minor bump (e.g., `v3.1.x` → `v3.2.0`)
148
+ - **Hotfix** (direct commit to `main` or `live-*`): Patch bump (e.g., `v3.2.0` → `v3.2.1`)
149
+ - **PR title convention**: `Release v3.2` — the workflow extracts the version from this
150
+
151
+ All version bumps update `config/settings_schema.json` automatically.
152
+
153
+ ## File Sync Rules (Multi-store)
154
+
155
+ **Synced between root and `stores/<alias>/`:**
156
+ - `config/settings_data.json`
157
+ - `templates/*.json`
158
+ - `sections/*.json`
159
+
160
+ **NOT synced (travels via branch history):**
161
+ - `config/settings_schema.json`
162
+ - `locales/*.json`
163
+
164
+ ## Recursive Trigger Prevention
165
+
166
+ - Hotfix backport commits contain `[hotfix-backport]` in the message
167
+ - Store sync commits contain `[stores-to-root]` or `[root-to-stores]`
168
+ - Version bump commits contain `chore(release): bump version`
169
+ - All workflows check for these flags and skip accordingly
170
+
171
+ ## GitHub Secrets
172
+
173
+ Add the following secret to your GitHub repository:
174
+
175
+ | Secret | Required | Description |
176
+ |--------|----------|-------------|
177
+ | `GEMINI_API_KEY` | Yes | Google Gemini API key for changelog generation |
178
+
179
+ ## Directory Structure (Multi-store)
180
+
181
+ ```
182
+ ├── assets/
183
+ ├── config/
184
+ ├── layout/
185
+ ├── locales/
186
+ ├── sections/
187
+ ├── snippets/
188
+ ├── templates/
189
+ ├── stores/
190
+ │ ├── voldt-staging/
191
+ │ │ ├── config/settings_data.json
192
+ │ │ ├── templates/*.json
193
+ │ │ └── sections/*.json
194
+ │ └── voldt-norway/
195
+ │ ├── config/settings_data.json
196
+ │ ├── templates/*.json
197
+ │ └── sections/*.json
198
+ ├── package.json
199
+ └── .github/workflows/
200
+ ```
201
+
202
+ ## License
203
+
204
+ MIT — Electric Maybe
package/bin/cli.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from '../src/index.js';
4
+
5
+ run(process.argv);
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "climaybe",
3
+ "version": "1.0.0",
4
+ "description": "Shopify CI/CD CLI — scaffolds workflows, branch strategy, and store config for single-store and multi-store theme repos",
5
+ "type": "module",
6
+ "bin": {
7
+ "climaybe": "bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20.0.0"
15
+ },
16
+ "keywords": [
17
+ "shopify",
18
+ "ci",
19
+ "cd",
20
+ "theme",
21
+ "workflow",
22
+ "github-actions"
23
+ ],
24
+ "author": "Electric Maybe",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "commander": "^13.1.0",
28
+ "picocolors": "^1.1.1",
29
+ "prompts": "^2.4.2"
30
+ }
31
+ }
@@ -0,0 +1,55 @@
1
+ import pc from 'picocolors';
2
+ import { promptNewStore } from '../lib/prompts.js';
3
+ import { readConfig, addStoreToConfig, getStoreAliases, getMode } from '../lib/config.js';
4
+ import { createStoreBranches } from '../lib/git.js';
5
+ import { scaffoldWorkflows } from '../lib/workflows.js';
6
+ import { createStoreDirectories } from '../lib/store-sync.js';
7
+
8
+ export async function addStoreCommand() {
9
+ console.log(pc.bold('\n climaybe — Add Store\n'));
10
+
11
+ // Guard: config must exist
12
+ const config = readConfig();
13
+ if (!config?.stores) {
14
+ console.log(pc.red(' No climaybe config found. Run "climaybe init" first.\n'));
15
+ return;
16
+ }
17
+
18
+ const existingAliases = getStoreAliases();
19
+ const previousMode = getMode();
20
+
21
+ // Prompt for new store
22
+ const store = await promptNewStore(existingAliases);
23
+ if (!store) return;
24
+
25
+ // Add to config
26
+ addStoreToConfig(store.alias, store.domain);
27
+ console.log(pc.green(` Added store: ${store.alias} → ${store.domain}`));
28
+
29
+ const newMode = getMode();
30
+
31
+ // Create store branches + directories
32
+ createStoreBranches(store.alias);
33
+ createStoreDirectories(store.alias);
34
+
35
+ // Handle single → multi migration
36
+ if (previousMode === 'single' && newMode === 'multi') {
37
+ console.log(pc.cyan('\n Migrating from single-store to multi-store mode...'));
38
+
39
+ // Create branches for the original store too
40
+ const originalAlias = existingAliases[0];
41
+ createStoreBranches(originalAlias);
42
+ createStoreDirectories(originalAlias);
43
+
44
+ // Re-scaffold workflows for multi mode
45
+ scaffoldWorkflows('multi');
46
+ console.log(pc.green(' Migration complete — workflows updated to multi-store mode.'));
47
+ } else if (newMode === 'multi') {
48
+ // Already multi, just make sure workflows are current
49
+ scaffoldWorkflows('multi');
50
+ }
51
+
52
+ console.log(pc.bold(pc.green('\n Store added successfully!\n')));
53
+ console.log(pc.dim(` New branches: staging-${store.alias}, live-${store.alias}`));
54
+ console.log(pc.dim(` Store dir: stores/${store.alias}/\n`));
55
+ }
@@ -0,0 +1,78 @@
1
+ import pc from 'picocolors';
2
+ import { promptStoreLoop } from '../lib/prompts.js';
3
+ import { readConfig, writeConfig } from '../lib/config.js';
4
+ import { ensureGitRepo, ensureInitialCommit, ensureStagingBranch, createStoreBranches } from '../lib/git.js';
5
+ import { scaffoldWorkflows } from '../lib/workflows.js';
6
+ import { createStoreDirectories } from '../lib/store-sync.js';
7
+
8
+ export async function initCommand() {
9
+ console.log(pc.bold('\n climaybe — Shopify CI/CD Setup\n'));
10
+
11
+ // Guard: check if already initialized
12
+ const existing = readConfig();
13
+ if (existing?.stores && Object.keys(existing.stores).length > 0) {
14
+ console.log(pc.yellow(' This repo already has a climaybe config.'));
15
+ console.log(pc.dim(' Use "climaybe add-store" to add more stores.'));
16
+ console.log(pc.dim(' Use "climaybe update-workflows" to refresh workflows.\n'));
17
+ return;
18
+ }
19
+
20
+ // 1. Collect stores from user
21
+ const stores = await promptStoreLoop();
22
+ const mode = stores.length > 1 ? 'multi' : 'single';
23
+
24
+ console.log(pc.dim(`\n Mode: ${mode}-store (${stores.length} store(s))`));
25
+
26
+ // 2. Build config
27
+ const config = {
28
+ port: 9295,
29
+ default_store: stores[0].domain,
30
+ stores: {},
31
+ };
32
+
33
+ for (const s of stores) {
34
+ config.stores[s.alias] = s.domain;
35
+ }
36
+
37
+ // 3. Write package.json config
38
+ writeConfig(config);
39
+ console.log(pc.green(' Updated package.json config.'));
40
+
41
+ // 4. Ensure git repo
42
+ ensureGitRepo();
43
+ ensureInitialCommit();
44
+
45
+ // 5. Create branches
46
+ if (mode === 'single') {
47
+ ensureStagingBranch();
48
+ } else {
49
+ // Multi-store: staging branch + per-store branches
50
+ ensureStagingBranch();
51
+ for (const s of stores) {
52
+ createStoreBranches(s.alias);
53
+ createStoreDirectories(s.alias);
54
+ }
55
+ }
56
+
57
+ // 6. Scaffold workflows
58
+ scaffoldWorkflows(mode);
59
+
60
+ // Done
61
+ console.log(pc.bold(pc.green('\n Setup complete!\n')));
62
+
63
+ if (mode === 'single') {
64
+ console.log(pc.dim(' Branches: main, staging'));
65
+ console.log(pc.dim(' Workflow: staging → main with versioning + nightly hotfix tagging'));
66
+ } else {
67
+ console.log(pc.dim(' Branches: main, staging'));
68
+ for (const s of stores) {
69
+ console.log(pc.dim(` staging-${s.alias}, live-${s.alias}`));
70
+ }
71
+ console.log(pc.dim(' Workflow: staging → main → staging-<store> → live-<store>'));
72
+ }
73
+
74
+ console.log(pc.dim('\n Next steps:'));
75
+ console.log(pc.dim(' 1. Add GEMINI_API_KEY to your GitHub repo secrets'));
76
+ console.log(pc.dim(' 2. Push to GitHub and start using the branching workflow'));
77
+ console.log(pc.dim(' 3. Tag your first release: git tag v1.0.0\n'));
78
+ }
@@ -0,0 +1,27 @@
1
+ import pc from 'picocolors';
2
+ import { getStoreAliases, getMode } from '../lib/config.js';
3
+ import { storesToRoot } from '../lib/store-sync.js';
4
+
5
+ export async function switchCommand(alias) {
6
+ console.log(pc.bold('\n climaybe — Switch Store\n'));
7
+
8
+ const mode = getMode();
9
+ if (mode !== 'multi') {
10
+ console.log(pc.yellow(' Switch is only available in multi-store mode.\n'));
11
+ return;
12
+ }
13
+
14
+ const aliases = getStoreAliases();
15
+ if (!aliases.includes(alias)) {
16
+ console.log(pc.red(` Unknown store alias: "${alias}"`));
17
+ console.log(pc.dim(` Available: ${aliases.join(', ')}\n`));
18
+ return;
19
+ }
20
+
21
+ const ok = storesToRoot(alias);
22
+ if (ok) {
23
+ console.log(pc.bold(pc.green(`\n Switched to store: ${alias}\n`)));
24
+ console.log(pc.dim(' Root JSON files now reflect this store\'s data.'));
25
+ console.log(pc.dim(' Use "climaybe sync" to write changes back.\n'));
26
+ }
27
+ }
@@ -0,0 +1,42 @@
1
+ import pc from 'picocolors';
2
+ import { getStoreAliases, getMode, readConfig } from '../lib/config.js';
3
+ import { rootToStores } from '../lib/store-sync.js';
4
+
5
+ export async function syncCommand(alias) {
6
+ console.log(pc.bold('\n climaybe — Sync to Store\n'));
7
+
8
+ const mode = getMode();
9
+ if (mode !== 'multi') {
10
+ console.log(pc.yellow(' Sync is only available in multi-store mode.\n'));
11
+ return;
12
+ }
13
+
14
+ const aliases = getStoreAliases();
15
+
16
+ // If no alias provided, use default store
17
+ if (!alias) {
18
+ const config = readConfig();
19
+ const defaultDomain = config?.default_store;
20
+ // Find alias by domain
21
+ alias = aliases.find((a) => config.stores[a] === defaultDomain);
22
+
23
+ if (!alias) {
24
+ console.log(pc.red(' No alias provided and no default store found.'));
25
+ console.log(pc.dim(` Usage: climaybe sync <alias>`));
26
+ console.log(pc.dim(` Available: ${aliases.join(', ')}\n`));
27
+ return;
28
+ }
29
+ console.log(pc.dim(` Using default store: ${alias}`));
30
+ }
31
+
32
+ if (!aliases.includes(alias)) {
33
+ console.log(pc.red(` Unknown store alias: "${alias}"`));
34
+ console.log(pc.dim(` Available: ${aliases.join(', ')}\n`));
35
+ return;
36
+ }
37
+
38
+ const ok = rootToStores(alias);
39
+ if (ok) {
40
+ console.log(pc.bold(pc.green(`\n Synced root → stores/${alias}/\n`)));
41
+ }
42
+ }
@@ -0,0 +1,18 @@
1
+ import pc from 'picocolors';
2
+ import { getMode, readConfig } from '../lib/config.js';
3
+ import { scaffoldWorkflows } from '../lib/workflows.js';
4
+
5
+ export async function updateWorkflowsCommand() {
6
+ console.log(pc.bold('\n climaybe — Update Workflows\n'));
7
+
8
+ const config = readConfig();
9
+ if (!config?.stores) {
10
+ console.log(pc.red(' No climaybe config found. Run "climaybe init" first.\n'));
11
+ return;
12
+ }
13
+
14
+ const mode = getMode();
15
+ scaffoldWorkflows(mode);
16
+
17
+ console.log(pc.bold(pc.green('\n Workflows updated!\n')));
18
+ }
package/src/index.js ADDED
@@ -0,0 +1,44 @@
1
+ import { Command } from 'commander';
2
+ import { initCommand } from './commands/init.js';
3
+ import { addStoreCommand } from './commands/add-store.js';
4
+ import { switchCommand } from './commands/switch.js';
5
+ import { syncCommand } from './commands/sync.js';
6
+ import { updateWorkflowsCommand } from './commands/update-workflows.js';
7
+
8
+ export function run(argv) {
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('climaybe')
13
+ .description('Shopify CI/CD CLI — scaffolds workflows, branch strategy, and store config')
14
+ .version('1.0.0');
15
+
16
+ program
17
+ .command('init')
18
+ .description('Initialize CI/CD setup for a Shopify theme repo')
19
+ .action(initCommand);
20
+
21
+ program
22
+ .command('add-store')
23
+ .description('Add a new store to an existing multi-store config')
24
+ .action(addStoreCommand);
25
+
26
+ program
27
+ .command('switch')
28
+ .argument('<alias>', 'Store alias to switch to')
29
+ .description('Switch local dev environment to a specific store')
30
+ .action(switchCommand);
31
+
32
+ program
33
+ .command('sync')
34
+ .argument('[alias]', 'Store alias to sync to (defaults to active store)')
35
+ .description('Sync root JSON files back to a store directory')
36
+ .action(syncCommand);
37
+
38
+ program
39
+ .command('update-workflows')
40
+ .description('Refresh GitHub Actions workflows from latest bundled templates')
41
+ .action(updateWorkflowsCommand);
42
+
43
+ program.parse(argv);
44
+ }
@@ -0,0 +1,92 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ const PKG = 'package.json';
5
+
6
+ /**
7
+ * Resolve absolute path to the target repo's package.json.
8
+ * Defaults to cwd.
9
+ */
10
+ function pkgPath(cwd = process.cwd()) {
11
+ return join(cwd, PKG);
12
+ }
13
+
14
+ /**
15
+ * Read the full package.json from a target repo.
16
+ * Returns null if it doesn't exist.
17
+ */
18
+ export function readPkg(cwd = process.cwd()) {
19
+ const p = pkgPath(cwd);
20
+ if (!existsSync(p)) return null;
21
+ return JSON.parse(readFileSync(p, 'utf-8'));
22
+ }
23
+
24
+ /**
25
+ * Write the full package.json object back to disk.
26
+ */
27
+ export function writePkg(pkg, cwd = process.cwd()) {
28
+ writeFileSync(pkgPath(cwd), JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
29
+ }
30
+
31
+ /**
32
+ * Read the climaybe config section from package.json.
33
+ * Returns null if package.json or config section doesn't exist.
34
+ */
35
+ export function readConfig(cwd = process.cwd()) {
36
+ const pkg = readPkg(cwd);
37
+ return pkg?.config ?? null;
38
+ }
39
+
40
+ /**
41
+ * Write/merge the climaybe config section into package.json.
42
+ * Creates package.json if it doesn't exist.
43
+ */
44
+ export function writeConfig(config, cwd = process.cwd()) {
45
+ let pkg = readPkg(cwd);
46
+ if (!pkg) {
47
+ pkg = {
48
+ name: 'shopify-theme',
49
+ version: '1.0.0',
50
+ private: true,
51
+ config: {},
52
+ };
53
+ }
54
+ pkg.config = { ...pkg.config, ...config };
55
+ writePkg(pkg, cwd);
56
+ }
57
+
58
+ /**
59
+ * Get the list of store aliases from config.
60
+ */
61
+ export function getStoreAliases(cwd = process.cwd()) {
62
+ const config = readConfig(cwd);
63
+ if (!config?.stores) return [];
64
+ return Object.keys(config.stores);
65
+ }
66
+
67
+ /**
68
+ * Determine the current mode: 'single' or 'multi'.
69
+ */
70
+ export function getMode(cwd = process.cwd()) {
71
+ const aliases = getStoreAliases(cwd);
72
+ return aliases.length > 1 ? 'multi' : 'single';
73
+ }
74
+
75
+ /**
76
+ * Add a store entry to the config.
77
+ * Returns the updated config.
78
+ */
79
+ export function addStoreToConfig(alias, domain, cwd = process.cwd()) {
80
+ const config = readConfig(cwd) || { port: 9295, stores: {} };
81
+ if (!config.stores) config.stores = {};
82
+
83
+ config.stores[alias] = domain;
84
+
85
+ // Set as default if it's the first store
86
+ if (!config.default_store) {
87
+ config.default_store = domain;
88
+ }
89
+
90
+ writeConfig(config, cwd);
91
+ return readConfig(cwd);
92
+ }
package/src/lib/git.js ADDED
@@ -0,0 +1,90 @@
1
+ import { execSync } from 'node:child_process';
2
+ import pc from 'picocolors';
3
+
4
+ const exec = (cmd, cwd = process.cwd()) =>
5
+ execSync(cmd, { cwd, encoding: 'utf-8', stdio: 'pipe' }).trim();
6
+
7
+ /**
8
+ * Check if current directory is a git repo.
9
+ */
10
+ export function isGitRepo(cwd = process.cwd()) {
11
+ try {
12
+ exec('git rev-parse --is-inside-work-tree', cwd);
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Get the current branch name.
21
+ */
22
+ export function currentBranch(cwd = process.cwd()) {
23
+ return exec('git rev-parse --abbrev-ref HEAD', cwd);
24
+ }
25
+
26
+ /**
27
+ * Check if a local branch exists.
28
+ */
29
+ export function branchExists(name, cwd = process.cwd()) {
30
+ try {
31
+ exec(`git rev-parse --verify ${name}`, cwd);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Create a new branch from current HEAD (does not checkout).
40
+ */
41
+ export function createBranch(name, cwd = process.cwd()) {
42
+ if (branchExists(name, cwd)) {
43
+ console.log(pc.yellow(` Branch "${name}" already exists, skipping.`));
44
+ return false;
45
+ }
46
+ exec(`git branch ${name}`, cwd);
47
+ console.log(pc.green(` Created branch: ${name}`));
48
+ return true;
49
+ }
50
+
51
+ /**
52
+ * Create staging and live branches for a store alias.
53
+ */
54
+ export function createStoreBranches(alias, cwd = process.cwd()) {
55
+ const staging = `staging-${alias}`;
56
+ const live = `live-${alias}`;
57
+ createBranch(staging, cwd);
58
+ createBranch(live, cwd);
59
+ }
60
+
61
+ /**
62
+ * Ensure the staging branch exists (for single-store mode).
63
+ */
64
+ export function ensureStagingBranch(cwd = process.cwd()) {
65
+ createBranch('staging', cwd);
66
+ }
67
+
68
+ /**
69
+ * Create an initial commit if the repo has no commits.
70
+ */
71
+ export function ensureInitialCommit(cwd = process.cwd()) {
72
+ try {
73
+ exec('git rev-parse HEAD', cwd);
74
+ } catch {
75
+ // No commits yet — create an initial one
76
+ exec('git add -A', cwd);
77
+ exec('git commit -m "chore: initial commit" --allow-empty', cwd);
78
+ console.log(pc.green(' Created initial commit.'));
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Initialize a git repo if not already one.
84
+ */
85
+ export function ensureGitRepo(cwd = process.cwd()) {
86
+ if (!isGitRepo(cwd)) {
87
+ exec('git init', cwd);
88
+ console.log(pc.green(' Initialized git repository.'));
89
+ }
90
+ }