climaybe 1.5.2 → 1.6.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 +44 -18
- package/bin/version.txt +1 -1
- package/package.json +1 -1
- package/src/commands/add-cursor-skill.js +17 -0
- package/src/commands/init.js +23 -0
- package/src/commands/setup-commitlint.js +21 -0
- package/src/index.js +12 -0
- package/src/lib/commit-tooling.js +110 -0
- package/src/lib/config.js +16 -0
- package/src/lib/prompts.js +28 -0
- package/src/workflows/build/build-pipeline.yml +15 -15
- package/src/workflows/multi/main-to-staging-stores.yml +40 -69
- package/src/workflows/multi/multistore-hotfix-to-main.yml +15 -4
- package/src/workflows/multi/root-to-stores.yml +63 -16
- package/src/workflows/multi/stores-to-root.yml +77 -43
- package/src/workflows/shared/version-bump.yml +19 -2
- package/src/workflows/single/nightly-hotfix.yml +43 -23
- package/src/workflows/single/post-merge-tag.yml +67 -89
- package/src/workflows/single/release-pr-check.yml +30 -4
package/README.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Shopify CI/CD CLI — scaffolds GitHub Actions workflows, branch strategy, and store config for single-store and multi-store theme repositories.
|
|
4
4
|
|
|
5
|
+
**Commit linting and AI-assisted commits are available as optional setup steps:**
|
|
6
|
+
|
|
7
|
+
- **Conventional commit linting:** During `climaybe init`, you can choose to automatically install and configure [commitlint](https://commitlint.js.org/) and [Husky](https://typicode.github.io/husky) to enforce [Conventional Commits](https://www.conventionalcommits.org/) in your theme repository.
|
|
8
|
+
- **Cursor AI commit skill:** You can also opt-in to installing the [Cursor AI commit skill](https://cursor.so/) (`.cursor/skills/commit/SKILL.md`) for AI-assisted, conventional commit message support in your project.
|
|
9
|
+
|
|
10
|
+
Both options streamline commit message quality and team workflows but are fully optional during setup.
|
|
11
|
+
|
|
5
12
|
## Install
|
|
6
13
|
|
|
7
14
|
Install in your theme repo (project-only, no global install):
|
|
@@ -34,10 +41,13 @@ Interactive setup that configures your repo for CI/CD.
|
|
|
34
41
|
3. Asks if you want to add more stores
|
|
35
42
|
4. Asks whether to enable optional **preview + cleanup** workflows
|
|
36
43
|
5. Asks whether to enable optional **build + Lighthouse** workflows
|
|
37
|
-
6.
|
|
38
|
-
7.
|
|
39
|
-
8.
|
|
40
|
-
9.
|
|
44
|
+
6. Asks whether to enable **commitlint + Husky** (enforce [conventional commits](https://www.conventionalcommits.org/) on `git commit`)
|
|
45
|
+
7. Asks whether to add **Cursor commit skill** to the project (`.cursor/skills/commit/SKILL.md`) for AI-assisted conventional commits
|
|
46
|
+
8. Based on store count, sets up **single-store** or **multi-store** mode
|
|
47
|
+
9. Writes `package.json` config
|
|
48
|
+
10. Scaffolds GitHub Actions workflows
|
|
49
|
+
11. Creates git branches and store directories (multi-store)
|
|
50
|
+
12. Optionally installs commitlint, Husky, and the Cursor skill
|
|
41
51
|
|
|
42
52
|
### `climaybe add-store`
|
|
43
53
|
|
|
@@ -91,6 +101,22 @@ npx climaybe update-workflows
|
|
|
91
101
|
|
|
92
102
|
Useful after updating the CLI to get the latest workflow improvements.
|
|
93
103
|
|
|
104
|
+
### `climaybe setup-commitlint`
|
|
105
|
+
|
|
106
|
+
Set up **only** commitlint + Husky (conventional commits enforced on `git commit`). Use this if you skipped it at init or want to add it later.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx climaybe setup-commitlint
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `climaybe add-cursor-skill`
|
|
113
|
+
|
|
114
|
+
Add **only** the Cursor commit skill to this project (`.cursor/skills/commit/SKILL.md`). Use this if you skipped it at init or want to add it later.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx climaybe add-cursor-skill
|
|
118
|
+
```
|
|
119
|
+
|
|
94
120
|
## Configuration
|
|
95
121
|
|
|
96
122
|
The CLI writes config into the `config` field of your `package.json`:
|
|
@@ -102,6 +128,8 @@ The CLI writes config into the `config` field of your `package.json`:
|
|
|
102
128
|
"default_store": "voldt-staging.myshopify.com",
|
|
103
129
|
"preview_workflows": true,
|
|
104
130
|
"build_workflows": true,
|
|
131
|
+
"commitlint": true,
|
|
132
|
+
"cursor_skills": true,
|
|
105
133
|
"stores": {
|
|
106
134
|
"voldt-staging": "voldt-staging.myshopify.com",
|
|
107
135
|
"voldt-norway": "voldt-norway.myshopify.com"
|
|
@@ -149,19 +177,19 @@ Direct pushes to `staging-<store>` or `live-<store>` are automatically synced ba
|
|
|
149
177
|
|
|
150
178
|
| Workflow | Trigger | What it does |
|
|
151
179
|
|----------|---------|-------------|
|
|
152
|
-
| `release-pr-check.yml` | PR from `staging` to `main` |
|
|
153
|
-
| `post-merge-tag.yml` | Push to `main` (merged PR) | Staging→main: minor bump (e.g. v3.2.0).
|
|
154
|
-
| `nightly-hotfix.yml` | Cron 02:00 US Eastern |
|
|
180
|
+
| `release-pr-check.yml` | PR from `staging` to `main` | Finds latest tag on main, AI changelog to PR head, creates pre-release patch tag (e.g. v3.1.13) to lock state; posts changelog comment |
|
|
181
|
+
| `post-merge-tag.yml` | Push to `main` (merged PR) | Staging→main only: minor bump from latest tag (e.g. v3.1.13 → v3.2.0). No version in PR title |
|
|
182
|
+
| `nightly-hotfix.yml` | Cron 02:00 US Eastern | Collects commits since latest tag (incl. hotfix backports), AI changelog, patch bump and tag |
|
|
155
183
|
|
|
156
184
|
### Multi-store (additional)
|
|
157
185
|
|
|
158
186
|
| Workflow | Trigger | What it does |
|
|
159
187
|
|----------|---------|-------------|
|
|
160
|
-
| `main-to-staging-stores.yml` (main-to-staging-<store>) | Push to `main` |
|
|
161
|
-
| `stores-to-root.yml` | Push to `staging-*` |
|
|
188
|
+
| `main-to-staging-stores.yml` (main-to-staging-<store>) | Push to `main` | Merges main into each `staging-<alias>`; root JSONs ignored (restored from stores/). Skips on hotfix/version/sync commits |
|
|
189
|
+
| `stores-to-root.yml` | Push to `staging-*` | From main merge: stores→root. From elsewhere (e.g. Shopify): root→stores |
|
|
162
190
|
| `pr-to-live.yml` | After stores-to-root | Opens PR from `staging-<alias>` to `live-<alias>` |
|
|
163
|
-
| `root-to-stores.yml` | Push to `live-*`
|
|
164
|
-
| `multistore-hotfix-to-main.yml` | Push to `staging-*` or `live-*` (and after root-to-stores) |
|
|
191
|
+
| `root-to-stores.yml` | Push to `live-*` | From main merge: stores→root. From elsewhere: root→stores (same as stores-to-root on staging-*) |
|
|
192
|
+
| `multistore-hotfix-to-main.yml` | Push to `staging-*` or `live-*` (and after root-to-stores) | Merges store branch into main (no PR). Skips when push is a merge from main (avoids loop) |
|
|
165
193
|
|
|
166
194
|
### Optional preview + cleanup package
|
|
167
195
|
|
|
@@ -189,13 +217,11 @@ Enabled via `climaybe init` prompt (`Enable build + Lighthouse workflows?`).
|
|
|
189
217
|
|
|
190
218
|
## Versioning
|
|
191
219
|
|
|
192
|
-
- **Version format**: Always three-part (e.g. `v3.2.0`). No
|
|
193
|
-
- **
|
|
194
|
-
- **
|
|
195
|
-
- **
|
|
196
|
-
-
|
|
197
|
-
|
|
198
|
-
All version bumps update `config/settings_schema.json` automatically.
|
|
220
|
+
- **Version format**: Always three-part (e.g. `v3.2.0`). No version in code or PR title; the system infers from tags.
|
|
221
|
+
- **No tags yet?** The system uses `theme_version` from `config/settings_schema.json` (`theme_info`), creates that tag on main (e.g. `v1.0.0`), and continues from there.
|
|
222
|
+
- **Staging → main**: On PR, a pre-release patch tag (e.g. v3.1.13) locks the current minor line; on merge, **minor** bump (e.g. v3.1.13 → v3.2.0).
|
|
223
|
+
- **Non-staging to main** (hotfix backports, direct commits): **Patch** bump only, via **nightly workflow** at 02:00 US Eastern (not at commit time).
|
|
224
|
+
- All version bumps update `config/settings_schema.json` automatically.
|
|
199
225
|
|
|
200
226
|
**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)**.
|
|
201
227
|
|
package/bin/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.6.0
|
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { writeConfig } from '../lib/config.js';
|
|
3
|
+
import { scaffoldCursorCommitSkill } from '../lib/commit-tooling.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Add only the Cursor commit skill to this project (.cursor/skills/commit/SKILL.md).
|
|
7
|
+
* Can be run standalone or after init without having chosen Cursor skills at init.
|
|
8
|
+
*/
|
|
9
|
+
export async function addCursorSkillCommand() {
|
|
10
|
+
console.log(pc.bold('\n climaybe — Add Cursor commit skill\n'));
|
|
11
|
+
|
|
12
|
+
writeConfig({ cursor_skills: true });
|
|
13
|
+
|
|
14
|
+
scaffoldCursorCommitSkill();
|
|
15
|
+
console.log(pc.green(' Cursor commit skill added to .cursor/skills/commit/SKILL.md'));
|
|
16
|
+
console.log(pc.dim(' Use "commit" or "group and commit" in Cursor to get conventional-commit assistance.\n'));
|
|
17
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
promptStoreLoop,
|
|
5
5
|
promptPreviewWorkflows,
|
|
6
6
|
promptBuildWorkflows,
|
|
7
|
+
promptCommitlint,
|
|
8
|
+
promptCursorSkills,
|
|
7
9
|
promptConfigureCISecrets,
|
|
8
10
|
promptUpdateExistingSecrets,
|
|
9
11
|
promptSecretValue,
|
|
@@ -13,6 +15,7 @@ import { readConfig, writeConfig } from '../lib/config.js';
|
|
|
13
15
|
import { ensureGitRepo, ensureInitialCommit, ensureStagingBranch, createStoreBranches, getSuggestedTagForRelease } from '../lib/git.js';
|
|
14
16
|
import { scaffoldWorkflows } from '../lib/workflows.js';
|
|
15
17
|
import { createStoreDirectories } from '../lib/store-sync.js';
|
|
18
|
+
import { scaffoldCommitlint, scaffoldCursorCommitSkill } from '../lib/commit-tooling.js';
|
|
16
19
|
import {
|
|
17
20
|
isGhAvailable,
|
|
18
21
|
hasGitHubRemote,
|
|
@@ -38,6 +41,8 @@ async function runInitFlow() {
|
|
|
38
41
|
const mode = stores.length > 1 ? 'multi' : 'single';
|
|
39
42
|
const enablePreviewWorkflows = await promptPreviewWorkflows();
|
|
40
43
|
const enableBuildWorkflows = await promptBuildWorkflows();
|
|
44
|
+
const enableCommitlint = await promptCommitlint();
|
|
45
|
+
const enableCursorSkills = await promptCursorSkills();
|
|
41
46
|
|
|
42
47
|
console.log(pc.dim(`\n Mode: ${mode}-store (${stores.length} store(s))`));
|
|
43
48
|
|
|
@@ -47,6 +52,8 @@ async function runInitFlow() {
|
|
|
47
52
|
default_store: stores[0].domain,
|
|
48
53
|
preview_workflows: enablePreviewWorkflows,
|
|
49
54
|
build_workflows: enableBuildWorkflows,
|
|
55
|
+
commitlint: enableCommitlint,
|
|
56
|
+
cursor_skills: enableCursorSkills,
|
|
50
57
|
stores: {},
|
|
51
58
|
};
|
|
52
59
|
|
|
@@ -80,6 +87,20 @@ async function runInitFlow() {
|
|
|
80
87
|
includeBuild: enableBuildWorkflows,
|
|
81
88
|
});
|
|
82
89
|
|
|
90
|
+
// 7. Optional commitlint + Husky and Cursor commit skill
|
|
91
|
+
if (enableCommitlint) {
|
|
92
|
+
console.log(pc.dim(' Setting up commitlint + Husky...'));
|
|
93
|
+
if (scaffoldCommitlint()) {
|
|
94
|
+
console.log(pc.green(' commitlint + Husky installed (conventional commits enforced on git commit).'));
|
|
95
|
+
} else {
|
|
96
|
+
console.log(pc.yellow(' commitlint setup failed or skipped (run npm install manually).'));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (enableCursorSkills) {
|
|
100
|
+
scaffoldCursorCommitSkill();
|
|
101
|
+
console.log(pc.green(' Cursor commit skill added to .cursor/skills/commit/SKILL.md'));
|
|
102
|
+
}
|
|
103
|
+
|
|
83
104
|
// Done
|
|
84
105
|
console.log(pc.bold(pc.green('\n Setup complete!\n')));
|
|
85
106
|
|
|
@@ -95,6 +116,8 @@ async function runInitFlow() {
|
|
|
95
116
|
}
|
|
96
117
|
console.log(pc.dim(` Preview workflows: ${enablePreviewWorkflows ? 'enabled' : 'disabled'}`));
|
|
97
118
|
console.log(pc.dim(` Build workflows: ${enableBuildWorkflows ? 'enabled' : 'disabled'}`));
|
|
119
|
+
console.log(pc.dim(` commitlint + Husky: ${enableCommitlint ? 'enabled' : 'disabled'}`));
|
|
120
|
+
console.log(pc.dim(` Cursor commit skill: ${enableCursorSkills ? 'added' : 'skipped'}`));
|
|
98
121
|
|
|
99
122
|
const suggestedTag = getSuggestedTagForRelease();
|
|
100
123
|
const tagLabel = suggestedTag === 'v1.0.0' ? 'Tag your first release' : 'Tag your next release';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { writeConfig } from '../lib/config.js';
|
|
3
|
+
import { scaffoldCommitlint } from '../lib/commit-tooling.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Set up commitlint + Husky only (conventional commits on git commit).
|
|
7
|
+
* Can be run standalone or after init without having chosen commitlint at init.
|
|
8
|
+
*/
|
|
9
|
+
export async function setupCommitlintCommand() {
|
|
10
|
+
console.log(pc.bold('\n climaybe — Setup commitlint + Husky\n'));
|
|
11
|
+
|
|
12
|
+
writeConfig({ commitlint: true });
|
|
13
|
+
|
|
14
|
+
console.log(pc.dim(' Installing commitlint + Husky (conventional commits enforced on git commit)...'));
|
|
15
|
+
const skipInstall = process.env.CLIMAYBE_SKIP_INSTALL === '1';
|
|
16
|
+
if (scaffoldCommitlint(process.cwd(), skipInstall ? { skipInstall: true } : {})) {
|
|
17
|
+
console.log(pc.green('\n commitlint + Husky are set up. Use conventional commits (e.g. feat: add X, fix: resolve Y).\n'));
|
|
18
|
+
} else {
|
|
19
|
+
console.log(pc.yellow('\n Installation failed or skipped. Run npm install in this repo and try again.\n'));
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import { switchCommand } from './commands/switch.js';
|
|
|
5
5
|
import { syncCommand } from './commands/sync.js';
|
|
6
6
|
import { updateWorkflowsCommand } from './commands/update-workflows.js';
|
|
7
7
|
import { ensureBranchesCommand } from './commands/ensure-branches.js';
|
|
8
|
+
import { setupCommitlintCommand } from './commands/setup-commitlint.js';
|
|
9
|
+
import { addCursorSkillCommand } from './commands/add-cursor-skill.js';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Create the CLI program (for testing and for run).
|
|
@@ -57,6 +59,16 @@ export function createProgram(version = '0.0.0', packageDir = '') {
|
|
|
57
59
|
.description('Create missing staging and per-store branches from current HEAD (then push)')
|
|
58
60
|
.action(ensureBranchesCommand);
|
|
59
61
|
|
|
62
|
+
program
|
|
63
|
+
.command('setup-commitlint')
|
|
64
|
+
.description('Set up only commitlint + Husky (conventional commits on git commit)')
|
|
65
|
+
.action(setupCommitlintCommand);
|
|
66
|
+
|
|
67
|
+
program
|
|
68
|
+
.command('add-cursor-skill')
|
|
69
|
+
.description('Add only the Cursor commit skill to this project (.cursor/skills/commit)')
|
|
70
|
+
.action(addCursorSkillCommand);
|
|
71
|
+
|
|
60
72
|
return program;
|
|
61
73
|
}
|
|
62
74
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { readPkg, writePkg } from './config.js';
|
|
5
|
+
|
|
6
|
+
const COMMITLINT_DEPS = {
|
|
7
|
+
'@commitlint/cli': '^20.4.4',
|
|
8
|
+
'@commitlint/config-conventional': '^20.4.4',
|
|
9
|
+
husky: '^9.1.7',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const COMMITLINT_CONFIG = `/** @type {import('@commitlint/types').UserConfig} */
|
|
13
|
+
module.exports = {
|
|
14
|
+
extends: ['@commitlint/config-conventional'],
|
|
15
|
+
rules: {
|
|
16
|
+
'type-enum': [
|
|
17
|
+
2,
|
|
18
|
+
'always',
|
|
19
|
+
['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore'],
|
|
20
|
+
],
|
|
21
|
+
'header-max-length': [2, 'always', 100],
|
|
22
|
+
'body-max-line-length': [2, 'always', 200],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const HUSKY_COMMIT_MSG = `npx --no-install commitlint --edit "$1"
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const CURSOR_COMMIT_SKILL = `---
|
|
31
|
+
name: commit
|
|
32
|
+
description: Groups working-tree changes into logical commits and commits them using conventional commit rules (type, optional scope, subject; commitlint). Use when the user asks to commit, group commits, stage and commit, or write conventional commits.
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# Commit (group + conventional)
|
|
36
|
+
|
|
37
|
+
Group changes by purpose, then commit each group with a valid conventional message so commitlint and semantic-release stay happy.
|
|
38
|
+
|
|
39
|
+
## Workflow
|
|
40
|
+
|
|
41
|
+
1. **Inspect** — Get full picture of changes (git status, git diff).
|
|
42
|
+
2. **Group** — Partition by type: feat, fix, docs, style, refactor, perf, test, build, ci, chore.
|
|
43
|
+
3. **Commit each group** — type(scope): subject, imperative, lowercase, no period, ≤100 chars.
|
|
44
|
+
4. **Validate** — commit-msg hook will reject invalid messages.
|
|
45
|
+
|
|
46
|
+
## Message rules (commitlint)
|
|
47
|
+
|
|
48
|
+
- **Types:** feat, fix, docs, style, refactor, perf, test, build, ci, chore.
|
|
49
|
+
- **Header:** type(scope): subject — subject max 100 chars. Body lines max 200.
|
|
50
|
+
- **Imperative, present tense:** "add feature" not "added feature".
|
|
51
|
+
|
|
52
|
+
## Examples
|
|
53
|
+
|
|
54
|
+
feat(init): add store alias prompt
|
|
55
|
+
fix(sync): prevent overwrite of unchanged files
|
|
56
|
+
docs: add conventional commit section to CONTRIBUTING
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Scaffold commitlint config, Husky commit-msg hook, and add deps to package.json.
|
|
61
|
+
* Runs npm install so husky prepare runs and hooks are installed (unless options.skipInstall).
|
|
62
|
+
* @param {string} [cwd] - Working directory (default process.cwd())
|
|
63
|
+
* @param {{ skipInstall?: boolean }} [options] - skipInstall: true to skip npm install (e.g. in tests)
|
|
64
|
+
* @returns {boolean} - true if scaffolded, false if skipped/failed
|
|
65
|
+
*/
|
|
66
|
+
export function scaffoldCommitlint(cwd = process.cwd(), options = {}) {
|
|
67
|
+
const pkg = readPkg(cwd);
|
|
68
|
+
if (!pkg) return false;
|
|
69
|
+
|
|
70
|
+
const updated = { ...pkg };
|
|
71
|
+
updated.scripts = { ...pkg.scripts };
|
|
72
|
+
if (!updated.scripts.prepare) {
|
|
73
|
+
updated.scripts.prepare = 'husky';
|
|
74
|
+
} else if (!updated.scripts.prepare.includes('husky')) {
|
|
75
|
+
updated.scripts.prepare = `husky && ${updated.scripts.prepare}`;
|
|
76
|
+
}
|
|
77
|
+
updated.devDependencies = { ...pkg.devDependencies, ...COMMITLINT_DEPS };
|
|
78
|
+
writePkg(updated, cwd);
|
|
79
|
+
|
|
80
|
+
writeFileSync(join(cwd, 'commitlint.config.js'), COMMITLINT_CONFIG, 'utf-8');
|
|
81
|
+
|
|
82
|
+
const huskyDir = join(cwd, '.husky');
|
|
83
|
+
if (!existsSync(huskyDir)) {
|
|
84
|
+
mkdirSync(huskyDir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
writeFileSync(join(huskyDir, 'commit-msg'), HUSKY_COMMIT_MSG, 'utf-8');
|
|
87
|
+
|
|
88
|
+
if (options.skipInstall) return true;
|
|
89
|
+
try {
|
|
90
|
+
execSync('npm install', { cwd, stdio: 'inherit' });
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Scaffold Cursor commit skill into .cursor/skills/commit/SKILL.md.
|
|
99
|
+
* @param {string} [cwd] - Working directory (default process.cwd())
|
|
100
|
+
* @returns {boolean} - true if written
|
|
101
|
+
*/
|
|
102
|
+
export function scaffoldCursorCommitSkill(cwd = process.cwd()) {
|
|
103
|
+
const skillDir = join(cwd, '.cursor', 'skills', 'commit');
|
|
104
|
+
if (!existsSync(skillDir)) {
|
|
105
|
+
mkdirSync(skillDir, { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
const skillPath = join(skillDir, 'SKILL.md');
|
|
108
|
+
writeFileSync(skillPath, CURSOR_COMMIT_SKILL, 'utf-8');
|
|
109
|
+
return true;
|
|
110
|
+
}
|
package/src/lib/config.js
CHANGED
|
@@ -97,6 +97,22 @@ export function isBuildWorkflowsEnabled(cwd = process.cwd()) {
|
|
|
97
97
|
return config?.build_workflows === true;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Whether commitlint + Husky was enabled at init.
|
|
102
|
+
*/
|
|
103
|
+
export function isCommitlintEnabled(cwd = process.cwd()) {
|
|
104
|
+
const config = readConfig(cwd);
|
|
105
|
+
return config?.commitlint === true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Whether Cursor commit skill was added at init.
|
|
110
|
+
*/
|
|
111
|
+
export function isCursorSkillsEnabled(cwd = process.cwd()) {
|
|
112
|
+
const config = readConfig(cwd);
|
|
113
|
+
return config?.cursor_skills === true;
|
|
114
|
+
}
|
|
115
|
+
|
|
100
116
|
/**
|
|
101
117
|
* Add a store entry to the config.
|
|
102
118
|
* Returns the updated config.
|
package/src/lib/prompts.js
CHANGED
|
@@ -148,6 +148,34 @@ export async function promptBuildWorkflows() {
|
|
|
148
148
|
return !!enableBuildWorkflows;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Ask whether to set up commitlint + Husky (conventional commits enforced on git commit).
|
|
153
|
+
*/
|
|
154
|
+
export async function promptCommitlint() {
|
|
155
|
+
const { enableCommitlint } = await prompts({
|
|
156
|
+
type: 'confirm',
|
|
157
|
+
name: 'enableCommitlint',
|
|
158
|
+
message: 'Enable commitlint + Husky? (enforce conventional commits on git commit)',
|
|
159
|
+
initial: true,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return !!enableCommitlint;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Ask whether to add Cursor commit skill to the project (.cursor/skills/commit).
|
|
167
|
+
*/
|
|
168
|
+
export async function promptCursorSkills() {
|
|
169
|
+
const { enableCursorSkills } = await prompts({
|
|
170
|
+
type: 'confirm',
|
|
171
|
+
name: 'enableCursorSkills',
|
|
172
|
+
message: 'Add Cursor commit skill to this project? (AI-assisted conventional commits)',
|
|
173
|
+
initial: true,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return !!enableCursorSkills;
|
|
177
|
+
}
|
|
178
|
+
|
|
151
179
|
/**
|
|
152
180
|
* Prompt for a single new store (used by add-store command).
|
|
153
181
|
* Takes existing aliases to prevent duplicates.
|
|
@@ -10,43 +10,43 @@ jobs:
|
|
|
10
10
|
build:
|
|
11
11
|
uses: ./.github/workflows/reusable-build.yml
|
|
12
12
|
|
|
13
|
-
lighthouse-
|
|
13
|
+
lighthouse-gate:
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
needs: [build]
|
|
16
|
+
outputs:
|
|
17
|
+
run_lighthouse: ${{ steps.check.outputs.run_lighthouse }}
|
|
16
18
|
env:
|
|
17
19
|
SHOPIFY_STORE_URL: ${{ secrets.SHOPIFY_STORE_URL }}
|
|
18
20
|
SHOP_ACCESS_TOKEN: ${{ secrets.SHOP_ACCESS_TOKEN }}
|
|
19
21
|
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
|
|
20
22
|
steps:
|
|
21
|
-
- name: Check
|
|
23
|
+
- name: Check if Lighthouse CI should run
|
|
24
|
+
id: check
|
|
22
25
|
run: |
|
|
23
26
|
SKIP_REASONS=()
|
|
24
|
-
|
|
25
27
|
if [ "${{ github.ref }}" != "refs/heads/main" ] && [ "${{ github.ref }}" != "refs/heads/staging" ]; then
|
|
26
28
|
SKIP_REASONS+=("Not on main or staging branch (current: ${{ github.ref }})")
|
|
27
29
|
fi
|
|
28
|
-
|
|
29
30
|
if [ -z "$SHOPIFY_STORE_URL" ] || [ -z "$SHOP_ACCESS_TOKEN" ] || [ -z "$LHCI_GITHUB_APP_TOKEN" ]; then
|
|
30
31
|
SKIP_REASONS+=("Missing required secrets: SHOPIFY_STORE_URL, SHOP_ACCESS_TOKEN, or LHCI_GITHUB_APP_TOKEN")
|
|
31
32
|
fi
|
|
32
|
-
|
|
33
33
|
if [ ${#SKIP_REASONS[@]} -gt 0 ]; then
|
|
34
|
-
echo "
|
|
35
|
-
echo "
|
|
36
|
-
for reason in "${SKIP_REASONS[@]}"; do
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
exit 0
|
|
34
|
+
echo "run_lighthouse=false" >> $GITHUB_OUTPUT
|
|
35
|
+
echo "Lighthouse CI skipped:"
|
|
36
|
+
for reason in "${SKIP_REASONS[@]}"; do echo " - $reason"; done
|
|
37
|
+
else
|
|
38
|
+
echo "run_lighthouse=true" >> $GITHUB_OUTPUT
|
|
40
39
|
fi
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
lighthouse-ci:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
needs: [build, lighthouse-gate]
|
|
44
|
+
if: needs.lighthouse-gate.outputs.run_lighthouse == 'true'
|
|
45
|
+
steps:
|
|
44
46
|
- name: Checkout code
|
|
45
|
-
if: env.SHOPIFY_STORE_URL != '' && env.SHOP_ACCESS_TOKEN != '' && env.LHCI_GITHUB_APP_TOKEN != '' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
|
46
47
|
uses: actions/checkout@v4
|
|
47
48
|
|
|
48
49
|
- name: Lighthouse
|
|
49
|
-
if: env.SHOPIFY_STORE_URL != '' && env.SHOP_ACCESS_TOKEN != '' && env.LHCI_GITHUB_APP_TOKEN != '' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
|
50
50
|
uses: shopify/lighthouse-ci-action@v1
|
|
51
51
|
with:
|
|
52
52
|
store: ${{ secrets.SHOPIFY_STORE_URL }}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# climaybe — Main to Staging <store> (Multi-store)
|
|
2
|
-
# When
|
|
3
|
-
#
|
|
4
|
-
#
|
|
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
|
+
# Skips [hotfix-backport], version-bump, and store-sync commits so hotfix syncs do not re-trigger release to stores.
|
|
5
6
|
|
|
6
7
|
name: Main to Staging Stores
|
|
7
8
|
|
|
@@ -27,12 +28,12 @@ jobs:
|
|
|
27
28
|
COMMIT_MSG="${{ github.event.head_commit.message }}"
|
|
28
29
|
|
|
29
30
|
if echo "$COMMIT_MSG" | grep -q "\[hotfix-backport\]"; then
|
|
30
|
-
echo "Skipping — hotfix backport commit"
|
|
31
|
+
echo "Skipping — hotfix backport commit (do not re-release to stores)"
|
|
31
32
|
echo "should_run=false" >> $GITHUB_OUTPUT
|
|
32
33
|
elif echo "$COMMIT_MSG" | grep -q "chore(release): bump version"; then
|
|
33
34
|
echo "Skipping — version bump commit"
|
|
34
35
|
echo "should_run=false" >> $GITHUB_OUTPUT
|
|
35
|
-
elif echo "$COMMIT_MSG" | grep -
|
|
36
|
+
elif echo "$COMMIT_MSG" | grep -qE "\[skip-store-sync\]|\[stores-to-root\]|\[root-to-stores\]"; then
|
|
36
37
|
echo "Skipping — store sync commit"
|
|
37
38
|
echo "should_run=false" >> $GITHUB_OUTPUT
|
|
38
39
|
else
|
|
@@ -60,7 +61,7 @@ jobs:
|
|
|
60
61
|
echo "stores=$STORES" >> $GITHUB_OUTPUT
|
|
61
62
|
echo "Store aliases: $STORES"
|
|
62
63
|
|
|
63
|
-
#
|
|
64
|
+
# Merge main into each staging-<alias>; root JSONs ignored (restored from stores/<alias>/)
|
|
64
65
|
sync:
|
|
65
66
|
needs: [gate, config]
|
|
66
67
|
if: needs.gate.outputs.should_run == 'true'
|
|
@@ -80,80 +81,50 @@ jobs:
|
|
|
80
81
|
with:
|
|
81
82
|
fetch-depth: 0
|
|
82
83
|
|
|
83
|
-
- name: Sync main to staging-${{ matrix.store }}
|
|
84
|
+
- name: Sync main to staging-${{ matrix.store }} (root JSONs ignored)
|
|
84
85
|
run: |
|
|
85
86
|
BRANCH="staging-${{ matrix.store }}"
|
|
87
|
+
ALIAS="${{ matrix.store }}"
|
|
88
|
+
STORE_DIR="stores/${ALIAS}"
|
|
86
89
|
|
|
87
|
-
# Check if branch exists
|
|
88
90
|
if ! git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
|
|
89
91
|
echo "Branch $BRANCH does not exist on remote, skipping."
|
|
90
92
|
exit 0
|
|
91
93
|
fi
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
git config user.name "github-actions[bot]"
|
|
96
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
git fetch origin "$BRANCH"
|
|
99
|
+
git checkout "$BRANCH"
|
|
100
|
+
git merge origin/main --no-ff -m "Sync main → $BRANCH" || true
|
|
101
|
+
if [ $? -ne 0 ]; then
|
|
102
|
+
echo "Merge had conflicts; aborting. Manual resolution may be needed."
|
|
103
|
+
git merge --abort 2>/dev/null || true
|
|
104
|
+
exit 1
|
|
100
105
|
fi
|
|
101
106
|
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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)"
|
|
107
|
+
# Ignore root JSONs: restore from stores/<alias>/ so main's versions are not kept
|
|
108
|
+
if [ -d "$STORE_DIR" ]; then
|
|
109
|
+
for DIR in config templates sections; do
|
|
110
|
+
SRC="${STORE_DIR}/${DIR}"
|
|
111
|
+
if [ -d "$SRC" ]; then
|
|
112
|
+
find "$SRC" -name "*.json" | while read -r FILE; do
|
|
113
|
+
REL_PATH="${FILE#$STORE_DIR/}"
|
|
114
|
+
if [ "$REL_PATH" = "config/settings_schema.json" ]; then
|
|
115
|
+
continue
|
|
148
116
|
fi
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
117
|
+
mkdir -p "$(dirname "$REL_PATH")"
|
|
118
|
+
cp "$FILE" "$REL_PATH"
|
|
119
|
+
done
|
|
120
|
+
fi
|
|
121
|
+
done
|
|
122
|
+
git add config templates sections 2>/dev/null || true
|
|
123
|
+
if ! git diff --cached --quiet; then
|
|
124
|
+
git commit -m "chore: keep store root JSONs from stores/$ALIAS/ [stores-to-root]"
|
|
156
125
|
fi
|
|
157
|
-
else
|
|
158
|
-
echo "No changes to sync for $BRANCH"
|
|
159
126
|
fi
|
|
127
|
+
|
|
128
|
+
git push origin "$BRANCH" && echo "Pushed $BRANCH" || { echo "Push failed."; exit 1; }
|
|
129
|
+
|
|
130
|
+
gh workflow run "PR to Live" --ref "$BRANCH" 2>/dev/null || echo "Failed to trigger PR to Live for $BRANCH"
|