climaybe 1.8.1 → 1.9.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 +36 -15
- package/bin/version.txt +1 -1
- package/package.json +2 -2
- package/src/commands/add-store.js +12 -2
- package/src/commands/app-init.js +89 -0
- package/src/commands/ensure-branches.js +4 -1
- package/src/commands/init.js +21 -6
- package/src/commands/switch.js +4 -1
- package/src/commands/sync.js +4 -1
- package/src/commands/update-workflows.js +4 -1
- package/src/cursor/skills/changelog-release/SKILL.md +1 -1
- package/src/index.js +42 -20
- package/src/lib/config.js +39 -2
- package/src/lib/theme-guard.js +13 -0
- package/src/workflows/shared/version-bump.yml +28 -2
- package/src/workflows/single/nightly-hotfix.yml +4 -1
- package/src/workflows/single/post-merge-tag.yml +4 -1
- package/src/workflows/single/release-pr-check.yml +4 -1
package/README.md
CHANGED
|
@@ -1,38 +1,54 @@
|
|
|
1
1
|
# climaybe
|
|
2
2
|
|
|
3
|
-
Shopify CI/CD
|
|
3
|
+
Shopify CLI for **theme CI/CD** (GitHub Actions, branches, multi-store config) and light **app repo** setup. Same install works in theme or app repositories.
|
|
4
4
|
|
|
5
|
-
**Commit linting and
|
|
5
|
+
**Commit linting and Cursor bundle (optional in both flows):**
|
|
6
6
|
|
|
7
|
-
- **Conventional commit linting:** During `climaybe init`, you can
|
|
8
|
-
- **Cursor rules + skills:**
|
|
7
|
+
- **Conventional commit linting:** During `climaybe theme init` or `climaybe app init`, you can install [commitlint](https://commitlint.js.org/) and [Husky](https://typicode.github.io/husky) for [Conventional Commits](https://www.conventionalcommits.org/).
|
|
8
|
+
- **Cursor rules + skills:** Opt in to Electric Maybe’s bundled [Cursor](https://cursor.com/) rules and skills under `.cursor/rules/` and `.cursor/skills/` (themes, JS, a11y, commits, changelog, Linear, etc.).
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Command layout (Shopify CLI–style)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
- **`climaybe theme <command>`** — canonical commands for theme repos (workflows, stores, branches).
|
|
13
|
+
- **Same commands at the top level** — `climaybe init` is the same as `climaybe theme init` (backward compatible).
|
|
14
|
+
- **`climaybe app init`** — app repos only: writes `project_type: "app"` in `package.json` config, optional commitlint + Cursor bundle. Does **not** install theme GitHub Actions or store/branch setup.
|
|
15
|
+
- **`climaybe setup-commitlint`** and **`climaybe add-cursor`** — always at the top level (stack-agnostic).
|
|
16
|
+
|
|
17
|
+
Theme-only commands refuse to run when `package.json` → `config.project_type` is **`app`**.
|
|
13
18
|
|
|
14
|
-
Install
|
|
19
|
+
## Install
|
|
15
20
|
|
|
16
21
|
```bash
|
|
17
|
-
cd your-shopify-theme-repo
|
|
22
|
+
cd your-shopify-theme-repo # or app repo
|
|
18
23
|
npm install -D climaybe
|
|
19
24
|
```
|
|
20
25
|
|
|
21
26
|
Run commands with `npx climaybe` (or add scripts to your `package.json`).
|
|
22
27
|
|
|
23
|
-
## Quick Start
|
|
28
|
+
## Quick Start (theme)
|
|
24
29
|
|
|
25
30
|
```bash
|
|
26
31
|
cd your-shopify-theme-repo
|
|
27
32
|
npm install -D climaybe
|
|
28
33
|
npx climaybe init
|
|
34
|
+
# equivalent: npx climaybe theme init
|
|
29
35
|
```
|
|
30
36
|
|
|
31
37
|
The interactive setup will ask for your store URL(s) and configure everything automatically.
|
|
32
38
|
|
|
39
|
+
## Quick Start (app)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd your-shopify-app-repo
|
|
43
|
+
npm install -D climaybe
|
|
44
|
+
npx climaybe app init
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Installs optional commitlint/Husky and Cursor rules/skills only. Use [Shopify CLI](https://shopify.dev/docs/api/shopify-cli) for app development and deployment.
|
|
48
|
+
|
|
33
49
|
## Commands
|
|
34
50
|
|
|
35
|
-
### `climaybe init`
|
|
51
|
+
### `climaybe init` / `climaybe theme init`
|
|
36
52
|
|
|
37
53
|
Interactive setup that configures your repo for CI/CD.
|
|
38
54
|
|
|
@@ -49,7 +65,11 @@ Interactive setup that configures your repo for CI/CD.
|
|
|
49
65
|
11. Creates git branches and store directories (multi-store)
|
|
50
66
|
12. Optionally installs commitlint, Husky, and the Cursor bundle (rules + skills)
|
|
51
67
|
|
|
52
|
-
### `climaybe
|
|
68
|
+
### `climaybe app init`
|
|
69
|
+
|
|
70
|
+
Interactive setup for a **Shopify app** repository: optional commitlint + Husky, optional Cursor bundle, and `project_type: "app"` in `package.json` `config`. No theme workflows, stores, or staging/live branches.
|
|
71
|
+
|
|
72
|
+
### `climaybe add-store` / `climaybe theme add-store`
|
|
53
73
|
|
|
54
74
|
Add a new store to an existing setup.
|
|
55
75
|
|
|
@@ -62,7 +82,7 @@ npx climaybe add-store
|
|
|
62
82
|
- Creates `stores/<alias>/` directory structure
|
|
63
83
|
- If store count goes from 1 to 2+, automatically migrates from single to multi-store mode
|
|
64
84
|
|
|
65
|
-
### `climaybe switch <alias>`
|
|
85
|
+
### `climaybe switch <alias>` / `climaybe theme switch`
|
|
66
86
|
|
|
67
87
|
Switch your local dev environment to a specific store (multi-store only).
|
|
68
88
|
|
|
@@ -72,7 +92,7 @@ npx climaybe switch voldt-norway
|
|
|
72
92
|
|
|
73
93
|
Copies `stores/<alias>/` JSON files to the repo root so you can preview that store locally.
|
|
74
94
|
|
|
75
|
-
### `climaybe sync [alias]`
|
|
95
|
+
### `climaybe sync [alias]` / `climaybe theme sync`
|
|
76
96
|
|
|
77
97
|
Sync root JSON files back to a store directory (multi-store only).
|
|
78
98
|
|
|
@@ -82,7 +102,7 @@ npx climaybe sync voldt-norway
|
|
|
82
102
|
|
|
83
103
|
If no alias is given, syncs to the default store.
|
|
84
104
|
|
|
85
|
-
### `climaybe ensure-branches`
|
|
105
|
+
### `climaybe ensure-branches` / `climaybe theme ensure-branches`
|
|
86
106
|
|
|
87
107
|
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-<store> sync can run.
|
|
88
108
|
|
|
@@ -91,7 +111,7 @@ npx climaybe ensure-branches
|
|
|
91
111
|
git push origin --all
|
|
92
112
|
```
|
|
93
113
|
|
|
94
|
-
### `climaybe update-workflows`
|
|
114
|
+
### `climaybe update-workflows` / `climaybe theme update-workflows`
|
|
95
115
|
|
|
96
116
|
Refresh GitHub Actions workflows from the latest bundled templates.
|
|
97
117
|
|
|
@@ -225,6 +245,7 @@ Enabled via `climaybe init` prompt (`Enable build + Lighthouse workflows?`; defa
|
|
|
225
245
|
- **Non-staging to main** (hotfix backports, direct commits): **Patch** bump only, via **nightly workflow** at 02:00 US Eastern (not at commit time).
|
|
226
246
|
- **Version bump runs only on main** (post-merge-tag and nightly-hotfix). Main-to-staging-stores merges main into each `staging-<alias>` on every push (version bumps and hotfixes). When the push is a hotfix from one store (e.g. live-norway), that store’s staging branch is skipped; other stores’ staging branches still get the merge.
|
|
227
247
|
- Version bumps update `config/settings_schema.json` and, when present, `package.json` `version`.
|
|
248
|
+
- **Safety**: The version-bump workflow fails if the new tag would not be **strictly higher** than the latest merged release tag (semver), so the release line cannot step backward.
|
|
228
249
|
|
|
229
250
|
**Full specification:** For detailed versioning rules, local dev flow, hotfix behavior, and alignment with the external CI/CD doc, see **[CI/CD Reference](docs/CI_CD_REFERENCE.md)**.
|
|
230
251
|
|
package/bin/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.9.0
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "climaybe",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Shopify
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "Shopify CLI — theme CI/CD (workflows, branches, stores) and app repo helpers; also: commitlint and Cursor rules/skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"climaybe": "bin/cli.js"
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { promptNewStore, promptConfigureCISecrets, promptUpdateExistingSecrets, promptSecretValue, promptTestThemeToken } from '../lib/prompts.js';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
readConfig,
|
|
5
|
+
addStoreToConfig,
|
|
6
|
+
getStoreAliases,
|
|
7
|
+
getMode,
|
|
8
|
+
isPreviewWorkflowsEnabled,
|
|
9
|
+
isBuildWorkflowsEnabled,
|
|
10
|
+
} from '../lib/config.js';
|
|
11
|
+
import { requireThemeProject } from '../lib/theme-guard.js';
|
|
4
12
|
import { createStoreBranches } from '../lib/git.js';
|
|
5
13
|
import { scaffoldWorkflows } from '../lib/workflows.js';
|
|
6
14
|
import { createStoreDirectories } from '../lib/store-sync.js';
|
|
@@ -21,10 +29,12 @@ import {
|
|
|
21
29
|
export async function addStoreCommand() {
|
|
22
30
|
console.log(pc.bold('\n climaybe — Add Store\n'));
|
|
23
31
|
|
|
32
|
+
if (!requireThemeProject()) return;
|
|
33
|
+
|
|
24
34
|
// Guard: config must exist
|
|
25
35
|
const config = readConfig();
|
|
26
36
|
if (!config?.stores) {
|
|
27
|
-
console.log(pc.red(' No climaybe config found. Run "climaybe init" first.\n'));
|
|
37
|
+
console.log(pc.red(' No climaybe config found. Run "climaybe theme init" (or "climaybe init") first.\n'));
|
|
28
38
|
return;
|
|
29
39
|
}
|
|
30
40
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { promptCommitlint, promptCursorSkills } from '../lib/prompts.js';
|
|
4
|
+
import {
|
|
5
|
+
readConfig,
|
|
6
|
+
writeConfig,
|
|
7
|
+
getProjectType,
|
|
8
|
+
isThemeProjectForAppInit,
|
|
9
|
+
} from '../lib/config.js';
|
|
10
|
+
import { ensureGitRepo, ensureInitialCommit } from '../lib/git.js';
|
|
11
|
+
import { scaffoldCommitlint } from '../lib/commit-tooling.js';
|
|
12
|
+
import { scaffoldCursorBundle } from '../lib/cursor-bundle.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Minimal Shopify app repo setup: commitlint, Cursor bundle, project_type in config.
|
|
16
|
+
* No theme stores, branches, or GitHub Actions theme workflows.
|
|
17
|
+
*/
|
|
18
|
+
async function runAppInitFlow() {
|
|
19
|
+
const enableCommitlint = await promptCommitlint();
|
|
20
|
+
const enableCursorSkills = await promptCursorSkills();
|
|
21
|
+
|
|
22
|
+
const config = {
|
|
23
|
+
project_type: 'app',
|
|
24
|
+
commitlint: enableCommitlint,
|
|
25
|
+
cursor_skills: enableCursorSkills,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
writeConfig(config, process.cwd(), { defaultPackageName: 'shopify-app' });
|
|
29
|
+
console.log(pc.green(' Updated package.json config.'));
|
|
30
|
+
|
|
31
|
+
ensureGitRepo();
|
|
32
|
+
ensureInitialCommit();
|
|
33
|
+
|
|
34
|
+
if (enableCommitlint) {
|
|
35
|
+
console.log(pc.dim(' Setting up commitlint + Husky...'));
|
|
36
|
+
if (scaffoldCommitlint()) {
|
|
37
|
+
console.log(pc.green(' commitlint + Husky installed (conventional commits enforced on git commit).'));
|
|
38
|
+
} else {
|
|
39
|
+
console.log(pc.yellow(' commitlint setup failed or skipped (run npm install manually).'));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (enableCursorSkills) {
|
|
43
|
+
const cursorOk = scaffoldCursorBundle();
|
|
44
|
+
if (cursorOk) {
|
|
45
|
+
console.log(pc.green(' Electric Maybe Cursor rules + skills → .cursor/rules, .cursor/skills'));
|
|
46
|
+
} else {
|
|
47
|
+
console.log(pc.yellow(' Cursor bundle not found in package (skipped).'));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(pc.bold(pc.green('\n App setup complete!\n')));
|
|
52
|
+
console.log(pc.dim(' commitlint + Husky: ' + (enableCommitlint ? 'enabled' : 'disabled')));
|
|
53
|
+
console.log(pc.dim(' Cursor rules + skills: ' + (enableCursorSkills ? 'installed' : 'skipped')));
|
|
54
|
+
console.log(pc.dim('\n Next steps:'));
|
|
55
|
+
console.log(pc.dim(' Use Shopify CLI (`shopify app dev`, etc.) for app development.'));
|
|
56
|
+
console.log(pc.dim(' Theme CI/CD workflows are not installed; add your own deployment as needed.\n'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function appInitCommand() {
|
|
60
|
+
console.log(pc.bold('\n climaybe — Shopify app setup\n'));
|
|
61
|
+
|
|
62
|
+
if (isThemeProjectForAppInit()) {
|
|
63
|
+
console.log(
|
|
64
|
+
pc.red(
|
|
65
|
+
' This repo looks like a theme project (stores in config or project_type: theme).'
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
console.log(pc.dim(' Use: npx climaybe theme init (or: npx climaybe init)\n'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const existing = readConfig();
|
|
73
|
+
const hasConfig = existing != null && typeof existing === 'object';
|
|
74
|
+
|
|
75
|
+
if (hasConfig && getProjectType() === 'app') {
|
|
76
|
+
const { reinit } = await prompts({
|
|
77
|
+
type: 'confirm',
|
|
78
|
+
name: 'reinit',
|
|
79
|
+
message: 'This repo already has climaybe app config. Re-run setup (merge config and reinstall optional tooling)?',
|
|
80
|
+
initial: false,
|
|
81
|
+
});
|
|
82
|
+
if (!reinit) {
|
|
83
|
+
console.log(pc.dim(' Use "climaybe setup-commitlint" or "climaybe add-cursor" to refresh tooling.\n'));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await runAppInitFlow();
|
|
89
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { readConfig } from '../lib/config.js';
|
|
3
|
+
import { requireThemeProject } from '../lib/theme-guard.js';
|
|
3
4
|
import {
|
|
4
5
|
isGitRepo,
|
|
5
6
|
currentBranch,
|
|
@@ -14,9 +15,11 @@ import {
|
|
|
14
15
|
export async function ensureBranchesCommand() {
|
|
15
16
|
console.log(pc.bold('\n climaybe — Ensure Branches\n'));
|
|
16
17
|
|
|
18
|
+
if (!requireThemeProject()) return;
|
|
19
|
+
|
|
17
20
|
const config = readConfig();
|
|
18
21
|
if (!config?.stores) {
|
|
19
|
-
console.log(pc.red(' No climaybe config found. Run "climaybe init" first.\n'));
|
|
22
|
+
console.log(pc.red(' No climaybe config found. Run "climaybe theme init" (or "climaybe init") first.\n'));
|
|
20
23
|
return;
|
|
21
24
|
}
|
|
22
25
|
|
package/src/commands/init.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
promptSecretValue,
|
|
12
12
|
promptTestThemeToken,
|
|
13
13
|
} from '../lib/prompts.js';
|
|
14
|
-
import { readConfig, writeConfig } from '../lib/config.js';
|
|
14
|
+
import { readConfig, writeConfig, getProjectType } from '../lib/config.js';
|
|
15
15
|
import { ensureGitRepo, ensureInitialCommit, ensureStagingBranch, createStoreBranches, getSuggestedTagForRelease } from '../lib/git.js';
|
|
16
16
|
import { scaffoldWorkflows } from '../lib/workflows.js';
|
|
17
17
|
import { createStoreDirectories } from '../lib/store-sync.js';
|
|
@@ -49,6 +49,7 @@ async function runInitFlow() {
|
|
|
49
49
|
|
|
50
50
|
// 2. Build config
|
|
51
51
|
const config = {
|
|
52
|
+
project_type: 'theme',
|
|
52
53
|
port: 9295,
|
|
53
54
|
default_store: stores[0].domain,
|
|
54
55
|
preview_workflows: enablePreviewWorkflows,
|
|
@@ -236,7 +237,14 @@ async function runInitFlow() {
|
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
export async function initCommand() {
|
|
239
|
-
console.log(pc.bold('\n climaybe — Shopify CI/CD
|
|
240
|
+
console.log(pc.bold('\n climaybe — Shopify theme CI/CD setup\n'));
|
|
241
|
+
|
|
242
|
+
if (getProjectType() === 'app') {
|
|
243
|
+
console.log(pc.red(' This repo is configured as a Shopify app (project_type: app).'));
|
|
244
|
+
console.log(pc.dim(' Use: npx climaybe app init'));
|
|
245
|
+
console.log(pc.dim(' To switch to theme CI/CD, remove or edit package.json → config first.\n'));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
240
248
|
|
|
241
249
|
const existing = readConfig();
|
|
242
250
|
const hasConfig = existing != null && typeof existing === 'object';
|
|
@@ -249,9 +257,9 @@ export async function initCommand() {
|
|
|
249
257
|
initial: false,
|
|
250
258
|
});
|
|
251
259
|
if (!reinit) {
|
|
252
|
-
console.log(pc.dim(' Use "climaybe add-store" to add more stores.'));
|
|
253
|
-
console.log(pc.dim(' Use "climaybe update-workflows" to refresh workflows.'));
|
|
254
|
-
console.log(pc.dim(' Use "climaybe reinit" to reinitialize from scratch.\n'));
|
|
260
|
+
console.log(pc.dim(' Use "climaybe theme add-store" (or "climaybe add-store") to add more stores.'));
|
|
261
|
+
console.log(pc.dim(' Use "climaybe theme update-workflows" (or "climaybe update-workflows") to refresh workflows.'));
|
|
262
|
+
console.log(pc.dim(' Use "climaybe theme reinit" (or "climaybe reinit") to reinitialize from scratch.\n'));
|
|
255
263
|
return;
|
|
256
264
|
}
|
|
257
265
|
}
|
|
@@ -260,6 +268,13 @@ export async function initCommand() {
|
|
|
260
268
|
}
|
|
261
269
|
|
|
262
270
|
export async function reinitCommand() {
|
|
263
|
-
console.log(pc.bold('\n climaybe — Reinitialize CI/CD
|
|
271
|
+
console.log(pc.bold('\n climaybe — Reinitialize theme CI/CD setup\n'));
|
|
272
|
+
|
|
273
|
+
if (getProjectType() === 'app') {
|
|
274
|
+
console.log(pc.red(' This repo is a Shopify app (project_type: app).'));
|
|
275
|
+
console.log(pc.dim(' Use: npx climaybe app init\n'));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
264
279
|
await runInitFlow();
|
|
265
280
|
}
|
package/src/commands/switch.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { getStoreAliases, getMode } from '../lib/config.js';
|
|
3
3
|
import { storesToRoot } from '../lib/store-sync.js';
|
|
4
|
+
import { requireThemeProject } from '../lib/theme-guard.js';
|
|
4
5
|
|
|
5
6
|
export async function switchCommand(alias) {
|
|
6
7
|
console.log(pc.bold('\n climaybe — Switch Store\n'));
|
|
7
8
|
|
|
9
|
+
if (!requireThemeProject()) return;
|
|
10
|
+
|
|
8
11
|
const mode = getMode();
|
|
9
12
|
if (mode !== 'multi') {
|
|
10
13
|
console.log(pc.yellow(' Switch is only available in multi-store mode.\n'));
|
|
@@ -22,6 +25,6 @@ export async function switchCommand(alias) {
|
|
|
22
25
|
if (ok) {
|
|
23
26
|
console.log(pc.bold(pc.green(`\n Switched to store: ${alias}\n`)));
|
|
24
27
|
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'));
|
|
28
|
+
console.log(pc.dim(' Use "climaybe theme sync" (or "climaybe sync") to write changes back.\n'));
|
|
26
29
|
}
|
|
27
30
|
}
|
package/src/commands/sync.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { getStoreAliases, getMode, readConfig } from '../lib/config.js';
|
|
3
3
|
import { rootToStores } from '../lib/store-sync.js';
|
|
4
|
+
import { requireThemeProject } from '../lib/theme-guard.js';
|
|
4
5
|
|
|
5
6
|
export async function syncCommand(alias) {
|
|
6
7
|
console.log(pc.bold('\n climaybe — Sync to Store\n'));
|
|
7
8
|
|
|
9
|
+
if (!requireThemeProject()) return;
|
|
10
|
+
|
|
8
11
|
const mode = getMode();
|
|
9
12
|
if (mode !== 'multi') {
|
|
10
13
|
console.log(pc.yellow(' Sync is only available in multi-store mode.\n'));
|
|
@@ -22,7 +25,7 @@ export async function syncCommand(alias) {
|
|
|
22
25
|
|
|
23
26
|
if (!alias) {
|
|
24
27
|
console.log(pc.red(' No alias provided and no default store found.'));
|
|
25
|
-
console.log(pc.dim(` Usage: climaybe sync <alias>`));
|
|
28
|
+
console.log(pc.dim(` Usage: climaybe theme sync <alias>`));
|
|
26
29
|
console.log(pc.dim(` Available: ${aliases.join(', ')}\n`));
|
|
27
30
|
return;
|
|
28
31
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { getMode, isBuildWorkflowsEnabled, isPreviewWorkflowsEnabled, readConfig } from '../lib/config.js';
|
|
3
3
|
import { scaffoldWorkflows } from '../lib/workflows.js';
|
|
4
|
+
import { requireThemeProject } from '../lib/theme-guard.js';
|
|
4
5
|
|
|
5
6
|
export async function updateWorkflowsCommand() {
|
|
6
7
|
console.log(pc.bold('\n climaybe — Update Workflows\n'));
|
|
7
8
|
|
|
9
|
+
if (!requireThemeProject()) return;
|
|
10
|
+
|
|
8
11
|
const config = readConfig();
|
|
9
12
|
if (!config?.stores) {
|
|
10
|
-
console.log(pc.red(' No climaybe config found. Run "climaybe init" first.\n'));
|
|
13
|
+
console.log(pc.red(' No climaybe config found. Run "climaybe theme init" (or "climaybe init") first.\n'));
|
|
11
14
|
return;
|
|
12
15
|
}
|
|
13
16
|
|
|
@@ -18,7 +18,7 @@ Use the same type labels and emoji for changelog sections so the output matches
|
|
|
18
18
|
|
|
19
19
|
## Workflow
|
|
20
20
|
|
|
21
|
-
1. **Determine range** — Since last tag
|
|
21
|
+
1. **Determine range** — Since last tag: use the **highest semver** among tags merged into `HEAD` (merged-into + `sort -V`), then `git log TAG..HEAD`. Do not rely on `git describe` alone after branch merges—it can return a **lower** version that is graph-closer on the merged branch. Or use a branch/commit the user specifies. If unclear, default to "since last tag".
|
|
22
22
|
2. **Get commits** — Run `git log --oneline [range]` (or with `--pretty=format:...` for message + body). Parse commit messages.
|
|
23
23
|
3. **Group by type** — Map each commit to a type from commit-rules.mdc (fix, feat, refactor, style, remove, wip, docs, ai, chore, upgrade). Ignore merge commits. Put "unknown" or "other" for non-matching messages.
|
|
24
24
|
4. **Format** — Output markdown:
|
package/src/index.js
CHANGED
|
@@ -7,57 +7,79 @@ import { updateWorkflowsCommand } from './commands/update-workflows.js';
|
|
|
7
7
|
import { ensureBranchesCommand } from './commands/ensure-branches.js';
|
|
8
8
|
import { setupCommitlintCommand } from './commands/setup-commitlint.js';
|
|
9
9
|
import { addCursorSkillCommand } from './commands/add-cursor-skill.js';
|
|
10
|
+
import { appInitCommand } from './commands/app-init.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @param {
|
|
14
|
-
* @param {string} [packageDir] - Package root dir (shown with --version so user can see which install is running).
|
|
13
|
+
* Register theme CI/CD commands on a Commander instance (root or `theme` subgroup).
|
|
14
|
+
* @param {import('commander').Command} cmd
|
|
15
15
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const versionDisplay = packageDir ? `${version}\n from: ${packageDir}` : version;
|
|
19
|
-
|
|
20
|
-
program
|
|
21
|
-
.name('climaybe')
|
|
22
|
-
.description('Shopify CI/CD CLI — scaffolds workflows, branch strategy, and store config')
|
|
23
|
-
.version(versionDisplay);
|
|
24
|
-
|
|
25
|
-
program
|
|
16
|
+
function registerThemeCommands(cmd) {
|
|
17
|
+
cmd
|
|
26
18
|
.command('init')
|
|
27
19
|
.description('Initialize CI/CD setup for a Shopify theme repo')
|
|
28
20
|
.action(initCommand);
|
|
29
21
|
|
|
30
|
-
|
|
22
|
+
cmd
|
|
31
23
|
.command('reinit')
|
|
32
|
-
.description('Reinitialize CI/CD
|
|
24
|
+
.description('Reinitialize theme CI/CD (re-scaffold workflows from templates)')
|
|
33
25
|
.action(reinitCommand);
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
cmd
|
|
36
28
|
.command('add-store')
|
|
37
29
|
.description('Add a new store to an existing multi-store config')
|
|
38
30
|
.action(addStoreCommand);
|
|
39
31
|
|
|
40
|
-
|
|
32
|
+
cmd
|
|
41
33
|
.command('switch')
|
|
42
34
|
.argument('<alias>', 'Store alias to switch to')
|
|
43
35
|
.description('Switch local dev environment to a specific store')
|
|
44
36
|
.action(switchCommand);
|
|
45
37
|
|
|
46
|
-
|
|
38
|
+
cmd
|
|
47
39
|
.command('sync')
|
|
48
40
|
.argument('[alias]', 'Store alias to sync to (defaults to active store)')
|
|
49
41
|
.description('Sync root JSON files back to a store directory')
|
|
50
42
|
.action(syncCommand);
|
|
51
43
|
|
|
52
|
-
|
|
44
|
+
cmd
|
|
53
45
|
.command('update-workflows')
|
|
54
46
|
.description('Refresh GitHub Actions workflows from latest bundled templates')
|
|
55
47
|
.action(updateWorkflowsCommand);
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
cmd
|
|
58
50
|
.command('ensure-branches')
|
|
59
51
|
.description('Create missing staging and per-store branches from current HEAD (then push)')
|
|
60
52
|
.action(ensureBranchesCommand);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create the CLI program (for testing and for run).
|
|
57
|
+
* @param {string} [version] - Version string (from bin/cli.js when run as CLI; from package.json in tests).
|
|
58
|
+
* @param {string} [packageDir] - Package root dir (shown with --version so user can see which install is running).
|
|
59
|
+
*/
|
|
60
|
+
export function createProgram(version = '0.0.0', packageDir = '') {
|
|
61
|
+
const program = new Command();
|
|
62
|
+
const versionDisplay = packageDir ? `${version}\n from: ${packageDir}` : version;
|
|
63
|
+
|
|
64
|
+
program
|
|
65
|
+
.name('climaybe')
|
|
66
|
+
.description(
|
|
67
|
+
'Shopify CLI — theme CI/CD (workflows, branches, stores) and app repo helpers (commitlint, Cursor bundle)'
|
|
68
|
+
)
|
|
69
|
+
.version(versionDisplay);
|
|
70
|
+
|
|
71
|
+
const theme = program
|
|
72
|
+
.command('theme')
|
|
73
|
+
.description('Shopify theme CI/CD: workflows, branches, and multi-store config');
|
|
74
|
+
|
|
75
|
+
registerThemeCommands(theme);
|
|
76
|
+
registerThemeCommands(program);
|
|
77
|
+
|
|
78
|
+
const app = program.command('app').description('Shopify app repo helpers (no theme workflows)');
|
|
79
|
+
app
|
|
80
|
+
.command('init')
|
|
81
|
+
.description('Set up commitlint, Cursor rules/skills, and project_type: app in package.json')
|
|
82
|
+
.action(appInitCommand);
|
|
61
83
|
|
|
62
84
|
program
|
|
63
85
|
.command('setup-commitlint')
|
package/src/lib/config.js
CHANGED
|
@@ -39,11 +39,20 @@ export function readConfig(cwd = process.cwd()) {
|
|
|
39
39
|
return pkg?.config ?? null;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {object} WriteConfigOptions
|
|
44
|
+
* @property {string} [defaultPackageName] - When creating package.json, set `name` (default: shopify-theme).
|
|
45
|
+
*/
|
|
46
|
+
|
|
42
47
|
/**
|
|
43
48
|
* Write/merge the climaybe config section into package.json.
|
|
44
49
|
* Creates package.json if it doesn't exist.
|
|
50
|
+
* @param {object} config
|
|
51
|
+
* @param {string} [cwd]
|
|
52
|
+
* @param {WriteConfigOptions} [options]
|
|
45
53
|
*/
|
|
46
|
-
export function writeConfig(config, cwd = process.cwd()) {
|
|
54
|
+
export function writeConfig(config, cwd = process.cwd(), options = {}) {
|
|
55
|
+
const defaultPackageName = options.defaultPackageName ?? 'shopify-theme';
|
|
47
56
|
let pkg = readPkg(cwd);
|
|
48
57
|
if (!pkg) {
|
|
49
58
|
let version = '1.0.0';
|
|
@@ -54,7 +63,7 @@ export function writeConfig(config, cwd = process.cwd()) {
|
|
|
54
63
|
// not a git repo or no tags
|
|
55
64
|
}
|
|
56
65
|
pkg = {
|
|
57
|
-
name:
|
|
66
|
+
name: defaultPackageName,
|
|
58
67
|
version,
|
|
59
68
|
private: true,
|
|
60
69
|
config: {},
|
|
@@ -64,6 +73,34 @@ export function writeConfig(config, cwd = process.cwd()) {
|
|
|
64
73
|
writePkg(pkg, cwd);
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Resolved project kind for guards and init flows.
|
|
78
|
+
* - `app` only when config explicitly sets project_type: app.
|
|
79
|
+
* - Otherwise `theme` (including missing config and legacy theme repos without project_type).
|
|
80
|
+
* @param {string} [cwd]
|
|
81
|
+
* @returns {'theme' | 'app'}
|
|
82
|
+
*/
|
|
83
|
+
export function getProjectType(cwd = process.cwd()) {
|
|
84
|
+
const config = readConfig(cwd);
|
|
85
|
+
if (config?.project_type === 'app') return 'app';
|
|
86
|
+
return 'theme';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* True if this repo is clearly a theme project (explicit project_type or legacy stores).
|
|
91
|
+
* Used to refuse `app init` on theme repos.
|
|
92
|
+
* @param {string} [cwd]
|
|
93
|
+
*/
|
|
94
|
+
export function isThemeProjectForAppInit(cwd = process.cwd()) {
|
|
95
|
+
const config = readConfig(cwd);
|
|
96
|
+
if (!config) return false;
|
|
97
|
+
if (config.project_type === 'theme') return true;
|
|
98
|
+
if (config.stores && typeof config.stores === 'object' && Object.keys(config.stores).length > 0) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
67
104
|
/**
|
|
68
105
|
* Get the list of store aliases from config.
|
|
69
106
|
*/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { getProjectType } from './config.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} [cwd]
|
|
6
|
+
* @returns {boolean} false if the repo is an app project (caller should return early).
|
|
7
|
+
*/
|
|
8
|
+
export function requireThemeProject(cwd = process.cwd()) {
|
|
9
|
+
if (getProjectType(cwd) !== 'app') return true;
|
|
10
|
+
console.log(pc.red(' This command is for theme repos only. This project has project_type: app.'));
|
|
11
|
+
console.log(pc.dim(' Use climaybe app init for app setup; theme stores and workflows do not apply here.\n'));
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
@@ -61,9 +61,15 @@ jobs:
|
|
|
61
61
|
fi
|
|
62
62
|
NEW_VERSION="v${NEW_VERSION}"
|
|
63
63
|
else
|
|
64
|
-
#
|
|
65
|
-
|
|
64
|
+
# Highest semver among tags reachable from HEAD — not `git describe`, which picks
|
|
65
|
+
# the graph-nearest tag and can choose an old tag on the merged branch after staging→main.
|
|
66
|
+
LATEST_TAG=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
67
|
+
# Legacy tags without patch (v1.0) if no strict triple match
|
|
66
68
|
if [ -z "$LATEST_TAG" ]; then
|
|
69
|
+
LATEST_TAG=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
70
|
+
fi
|
|
71
|
+
if [ -z "$LATEST_TAG" ]; then
|
|
72
|
+
# No semver tags: theme_version from settings_schema.json, else v0.0.0
|
|
67
73
|
SCHEMA="config/settings_schema.json"
|
|
68
74
|
if [ -f "$SCHEMA" ]; then
|
|
69
75
|
LATEST_TAG=$(node -e "
|
|
@@ -100,6 +106,26 @@ jobs:
|
|
|
100
106
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
101
107
|
echo "New version: $NEW_VERSION"
|
|
102
108
|
|
|
109
|
+
- name: Refuse non-increasing version
|
|
110
|
+
env:
|
|
111
|
+
NEW_VERSION: ${{ steps.bump.outputs.new_version }}
|
|
112
|
+
run: |
|
|
113
|
+
git fetch --tags origin
|
|
114
|
+
MAX_EXISTING=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
115
|
+
if [ -z "$MAX_EXISTING" ]; then
|
|
116
|
+
MAX_EXISTING=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
117
|
+
fi
|
|
118
|
+
if [ -z "$MAX_EXISTING" ]; then
|
|
119
|
+
echo "No merged semver tags; skipping monotonic check."
|
|
120
|
+
exit 0
|
|
121
|
+
fi
|
|
122
|
+
HI=$(printf '%s\n' "$MAX_EXISTING" "$NEW_VERSION" | sort -V | tail -1)
|
|
123
|
+
if [ "$HI" != "$NEW_VERSION" ] || [ "$NEW_VERSION" = "$MAX_EXISTING" ]; then
|
|
124
|
+
echo "::error::Refusing to release ${NEW_VERSION}: latest merged tag is ${MAX_EXISTING}; new tag must be strictly higher (semver)."
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
echo "OK: ${NEW_VERSION} > ${MAX_EXISTING}"
|
|
128
|
+
|
|
103
129
|
- name: Update settings_schema.json and package.json
|
|
104
130
|
env:
|
|
105
131
|
NEW_VERSION: ${{ steps.bump.outputs.new_version }}
|
|
@@ -42,7 +42,10 @@ jobs:
|
|
|
42
42
|
env:
|
|
43
43
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
44
44
|
run: |
|
|
45
|
-
LATEST_TAG=$(git
|
|
45
|
+
LATEST_TAG=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
46
|
+
if [ -z "$LATEST_TAG" ]; then
|
|
47
|
+
LATEST_TAG=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
48
|
+
fi
|
|
46
49
|
if [ -z "$LATEST_TAG" ]; then
|
|
47
50
|
SCHEMA="config/settings_schema.json"
|
|
48
51
|
if [ -f "$SCHEMA" ]; then
|
|
@@ -77,7 +77,10 @@ jobs:
|
|
|
77
77
|
- name: Get latest tag (or create from settings_schema.json)
|
|
78
78
|
id: tag
|
|
79
79
|
run: |
|
|
80
|
-
LAST_TAG=$(git
|
|
80
|
+
LAST_TAG=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
81
|
+
if [ -z "$LAST_TAG" ]; then
|
|
82
|
+
LAST_TAG=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
83
|
+
fi
|
|
81
84
|
if [ -z "$LAST_TAG" ]; then
|
|
82
85
|
SCHEMA="config/settings_schema.json"
|
|
83
86
|
if [ -f "$SCHEMA" ]; then
|
|
@@ -56,7 +56,10 @@ jobs:
|
|
|
56
56
|
- name: Create pre-release patch tag
|
|
57
57
|
id: tag
|
|
58
58
|
run: |
|
|
59
|
-
LATEST_TAG=$(git
|
|
59
|
+
LATEST_TAG=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
60
|
+
if [ -z "$LATEST_TAG" ]; then
|
|
61
|
+
LATEST_TAG=$(git tag --merged HEAD | grep -E '^v[0-9]+\.[0-9]+$' | sort -V | tail -1)
|
|
62
|
+
fi
|
|
60
63
|
if [ -z "$LATEST_TAG" ]; then
|
|
61
64
|
# No tags: use theme_version from config/settings_schema.json and push as initial tag
|
|
62
65
|
SCHEMA="config/settings_schema.json"
|