climaybe 3.0.0 → 3.0.2

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/bin/version.txt CHANGED
@@ -1 +1 @@
1
- 3.0.0
1
+ 3.0.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "climaybe",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Shopify CLI by Electric Maybe for theme CI/CD workflows, branch orchestration, app setup, and dev tooling",
5
5
  "type": "module",
6
6
  "bin": {
@@ -81,14 +81,14 @@
81
81
  "@shopify/prettier-plugin-liquid": "^1.6.3",
82
82
  "@tailwindcss/cli": "^4.1.17",
83
83
  "@tailwindcss/typography": "^0.5.16",
84
+ "commander": "^14.0.3",
84
85
  "eslint": "^9.11.0",
86
+ "picocolors": "^1.1.1",
85
87
  "prettier": "^3.4.2",
88
+ "prompts": "^2.4.2",
86
89
  "stylelint": "^16.9.0",
87
90
  "stylelint-config-standard": "^36.0.1",
88
- "tailwindcss": "^4.1.18",
89
- "commander": "^14.0.3",
90
- "picocolors": "^1.1.1",
91
- "prompts": "^2.4.2"
91
+ "tailwindcss": "^4.1.18"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@commitlint/cli": "^20.4.4",
@@ -36,20 +36,15 @@ export async function ensureBranchesCommand() {
36
36
  console.log(pc.dim(` Mode: ${mode}-store (${aliases.length} store(s))\n`));
37
37
 
38
38
  ensureStagingBranch();
39
-
40
- if (mode === 'multi') {
41
- for (const alias of aliases) {
42
- createStoreBranches(alias);
43
- }
39
+ for (const alias of aliases) {
40
+ createStoreBranches(alias);
44
41
  }
45
42
 
46
43
  console.log(pc.bold(pc.green('\n Branches ensured.\n')));
47
44
  console.log(pc.dim(' Push them so CI can run:'));
48
45
  console.log(pc.dim(' git push origin staging'));
49
- if (mode === 'multi') {
50
- for (const alias of aliases) {
51
- console.log(pc.dim(` git push origin staging-${alias} live-${alias}`));
52
- }
46
+ for (const alias of aliases) {
47
+ console.log(pc.dim(` git push origin staging-${alias} live-${alias}`));
53
48
  }
54
49
  console.log(pc.dim(' Or push all at once: git push origin --all\n'));
55
50
  }
@@ -1,5 +1,6 @@
1
1
  import prompts from 'prompts';
2
2
  import pc from 'picocolors';
3
+ import { execSync } from 'node:child_process';
3
4
  import {
4
5
  promptStoreLoop,
5
6
  promptPreviewWorkflows,
@@ -36,11 +37,25 @@ import {
36
37
  setGitLabVariable,
37
38
  } from '../lib/github-secrets.js';
38
39
 
40
+ function installThemeDependencies(cwd = process.cwd()) {
41
+ try {
42
+ execSync('npm install', { cwd, stdio: 'inherit' });
43
+ console.log(pc.green(' Installed theme dependencies (npm install).'));
44
+ return true;
45
+ } catch (err) {
46
+ console.log(pc.yellow(` npm install failed or skipped: ${err.message}`));
47
+ return false;
48
+ }
49
+ }
50
+
39
51
  /**
40
52
  * Run the full init flow: prompts, config write, git, branches, workflows.
41
53
  * Used by both init (when not already inited or user confirms reinit) and reinit.
42
54
  */
43
55
  async function runInitFlow() {
56
+ const hasPackageJson = !!readPkg();
57
+ const projectName = !hasPackageJson ? await promptProjectName() : undefined;
58
+
44
59
  // 1. Collect stores from user
45
60
  const stores = await promptStoreLoop();
46
61
  const mode = stores.length > 1 ? 'multi' : 'single';
@@ -48,7 +63,6 @@ async function runInitFlow() {
48
63
  const enableBuildWorkflows = await promptBuildWorkflows();
49
64
  const enableDevKit = await promptDevKit();
50
65
  const enableVSCodeTasks = enableDevKit ? await promptVSCodeDevTasks() : false;
51
- const projectName = enableDevKit && !readPkg() ? await promptProjectName() : undefined;
52
66
  const enableCommitlint = await promptCommitlint();
53
67
  const enableCursorSkills = await promptCursorSkills();
54
68
 
@@ -111,6 +125,7 @@ async function runInitFlow() {
111
125
  // 5. Create branches
112
126
  if (mode === 'single') {
113
127
  ensureStagingBranch();
128
+ createStoreBranches(stores[0].alias);
114
129
  } else {
115
130
  // Multi-store: staging branch + per-store branches
116
131
  ensureStagingBranch();
@@ -158,6 +173,7 @@ async function runInitFlow() {
158
173
  packageName: projectName || undefined,
159
174
  });
160
175
  console.log(pc.green(' Theme dev kit installed (local config + ignore defaults).'));
176
+ installThemeDependencies();
161
177
  }
162
178
 
163
179
  // Done
@@ -165,6 +181,7 @@ async function runInitFlow() {
165
181
 
166
182
  if (mode === 'single') {
167
183
  console.log(pc.dim(' Branches: main, staging'));
184
+ console.log(pc.dim(` staging-${stores[0].alias}, live-${stores[0].alias}`));
168
185
  console.log(pc.dim(' Workflow: staging → main with versioning + nightly hotfix tagging'));
169
186
  } else {
170
187
  console.log(pc.dim(' Branches: main, staging'));
@@ -106,6 +106,24 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = true } =
106
106
  })
107
107
  : null;
108
108
 
109
+ let themeCheckRunning = false;
110
+ let themeCheckQueued = false;
111
+ const runThemeCheck = () => {
112
+ if (themeCheckRunning) {
113
+ themeCheckQueued = true;
114
+ return;
115
+ }
116
+ themeCheckRunning = true;
117
+ const child = runShopify(['theme', 'check'], { cwd, name: 'theme-check' });
118
+ child.on('exit', () => {
119
+ themeCheckRunning = false;
120
+ if (themeCheckQueued) {
121
+ themeCheckQueued = false;
122
+ runThemeCheck();
123
+ }
124
+ });
125
+ };
126
+
109
127
  const themeCheckWatch =
110
128
  includeThemeCheck
111
129
  ? watchTree({
@@ -118,11 +136,15 @@ export function serveAssets({ cwd = process.cwd(), includeThemeCheck = true } =
118
136
  p.includes('/_styles/'),
119
137
  debounceMs: 800,
120
138
  onChange: () => {
121
- runShopify(['theme', 'check'], { cwd, name: 'theme-check' });
139
+ runThemeCheck();
122
140
  },
123
141
  })
124
142
  : null;
125
143
 
144
+ if (includeThemeCheck) {
145
+ runThemeCheck();
146
+ }
147
+
126
148
  const cleanup = () => {
127
149
  safeClose(scriptsWatch);
128
150
  safeClose(themeCheckWatch);
@@ -1,11 +1,12 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
3
  import { readPkg, writePkg, writeClimaybeConfig } from './config.js';
4
+ import { getLatestTagVersion } from './git.js';
4
5
 
5
6
  const DEV_KIT_FILES = {
6
7
  '.theme-check.yml': `root: .
7
8
 
8
- extends: :nothing
9
+ extends: theme-check:recommended
9
10
 
10
11
  ignore:
11
12
  - node_modules/*
@@ -109,6 +110,7 @@ const VSCODE_TASKS_CONTENT = `{
109
110
 
110
111
  const GITIGNORE_BLOCK = `# climaybe: theme dev kit (managed)
111
112
  .vscode
113
+ node_modules/
112
114
  assets/style.css
113
115
  assets/index.js
114
116
  .shopify
@@ -126,19 +128,50 @@ function mergeGitignore(cwd = process.cwd()) {
126
128
  return;
127
129
  }
128
130
  const current = readFileSync(path, 'utf-8');
129
- if (current.includes('# climaybe: theme dev kit (managed)')) return;
130
- const next = `${current.trimEnd()}\n\n${GITIGNORE_BLOCK}`;
131
+ const marker = '# climaybe: theme dev kit (managed)';
132
+ if (current.includes(marker)) {
133
+ const lines = current.split('\n');
134
+ const start = lines.findIndex((line) => line.trim() === marker);
135
+ if (start >= 0) {
136
+ let end = start + 1;
137
+ while (end < lines.length && lines[end].trim() !== '') end += 1;
138
+ const before = lines.slice(0, start).join('\n').trimEnd();
139
+ const after = lines.slice(end).join('\n').trim();
140
+ const segments = [before, GITIGNORE_BLOCK.trimEnd(), after].filter(Boolean);
141
+ writeFileSync(path, `${segments.join('\n\n')}\n`, 'utf-8');
142
+ return;
143
+ }
144
+ }
145
+ const next = `${current.trimEnd()}\n\n${GITIGNORE_BLOCK.trimEnd()}`;
131
146
  writeFileSync(path, `${next}\n`, 'utf-8');
132
147
  }
133
148
 
134
149
  function mergePackageJson({ packageName = 'shopify-theme', cwd = process.cwd() } = {}) {
135
- const pkg = readPkg(cwd) || { name: packageName, version: '1.0.0', private: true };
150
+ let pkg = readPkg(cwd);
151
+ if (!pkg) {
152
+ let version = '0.1.0';
153
+ try {
154
+ const fromTags = getLatestTagVersion(cwd);
155
+ if (fromTags) version = fromTags;
156
+ } catch {
157
+ // not a git repo or no semver tags found
158
+ }
159
+ pkg = { name: packageName, version, private: true };
160
+ }
161
+ if (!pkg.description) {
162
+ pkg.description = 'Customizable modular development environment for blazing-fast Shopify theme creation';
163
+ }
164
+ if (!pkg.author) {
165
+ pkg.author = 'Electric Maybe <hello@electricmaybe.com>';
166
+ }
167
+ pkg.dependencies = { ...(pkg.dependencies || {}) };
136
168
  pkg.devDependencies = { ...(pkg.devDependencies || {}) };
137
169
 
138
170
  // Ensure teammates can run climaybe + Tailwind after plain npm install.
139
171
  const cliVersion = process.env.CLIMAYBE_PACKAGE_VERSION;
140
172
  const climaybeRange = /^\d+\.\d+\.\d+/.test(String(cliVersion || '')) ? `^${cliVersion}` : 'latest';
141
- if (!pkg.devDependencies.climaybe) pkg.devDependencies.climaybe = climaybeRange;
173
+ if (!pkg.dependencies.climaybe) pkg.dependencies.climaybe = climaybeRange;
174
+ if (pkg.devDependencies.climaybe) delete pkg.devDependencies.climaybe;
142
175
  if (!pkg.devDependencies.tailwindcss) pkg.devDependencies.tailwindcss = 'latest';
143
176
 
144
177
  writePkg(pkg, cwd);
@@ -1,6 +1,8 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { stdin as input, stdout as output } from 'node:process';
3
3
  import { createInterface } from 'node:readline/promises';
4
+ import { existsSync, readFileSync } from 'node:fs';
5
+ import { join, resolve } from 'node:path';
4
6
  import pc from 'picocolors';
5
7
 
6
8
  const NPM_REGISTRY_BASE = 'https://registry.npmjs.org';
@@ -43,8 +45,40 @@ function canPromptForUpdate() {
43
45
  return Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true');
44
46
  }
45
47
 
46
- function runGlobalUpdate(packageName) {
47
- execSync(`npm install -g ${packageName}@latest`, { stdio: 'inherit' });
48
+ export function resolveInstallScope({ packageDir, cwd = process.cwd() } = {}) {
49
+ try {
50
+ const globalRoot = execSync('npm root -g', { encoding: 'utf-8', stdio: 'pipe' }).trim();
51
+ if (packageDir && resolve(packageDir).startsWith(resolve(globalRoot))) return 'global';
52
+ } catch {
53
+ // ignore and fallback to local checks
54
+ }
55
+
56
+ if (existsSync(join(cwd, 'package.json'))) return 'local';
57
+ return 'global';
58
+ }
59
+
60
+ function getLocalInstallFlag({ packageName, cwd = process.cwd() } = {}) {
61
+ try {
62
+ const pkgPath = join(cwd, 'package.json');
63
+ if (!existsSync(pkgPath)) return '--save-dev';
64
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
65
+ if (pkg?.dependencies?.[packageName]) return '--save';
66
+ if (pkg?.devDependencies?.[packageName]) return '--save-dev';
67
+ return '--save-dev';
68
+ } catch {
69
+ return '--save-dev';
70
+ }
71
+ }
72
+
73
+ function runUpdate(packageName, { packageDir, cwd = process.cwd() } = {}) {
74
+ const scope = resolveInstallScope({ packageDir, cwd });
75
+ if (scope === 'global') {
76
+ execSync(`npm install -g ${packageName}@latest`, { stdio: 'inherit' });
77
+ return 'global';
78
+ }
79
+ const flag = getLocalInstallFlag({ packageName, cwd });
80
+ execSync(`npm install ${packageName}@latest ${flag}`, { cwd, stdio: 'inherit' });
81
+ return 'local';
48
82
  }
49
83
 
50
84
  export async function maybeOfferCliUpdate({
@@ -68,8 +102,8 @@ export async function maybeOfferCliUpdate({
68
102
  if (!shouldUpdate) return;
69
103
 
70
104
  try {
71
- runGlobalUpdate(packageName);
72
- console.log(pc.green(`Updated ${packageName} to latest. Restarting command...`));
105
+ const updatedScope = runUpdate(packageName, { packageDir, cwd: process.cwd() });
106
+ console.log(pc.green(`Updated ${packageName} (${updatedScope}) to latest. Restarting command...`));
73
107
  process.chdir(packageDir || process.cwd());
74
108
  } catch (err) {
75
109
  console.log(pc.red('Update failed. Continuing with current version.'));
@@ -22,7 +22,6 @@ jobs:
22
22
  uses: actions/setup-node@v4
23
23
  with:
24
24
  node-version: "24"
25
- cache: "npm"
26
25
 
27
26
  - name: Detect build entrypoints
28
27
  id: detect